# Trabajar con archivos grandes

Qué cambiar cuando los PDF se vuelven grandes: streaming, tiempos de espera, reintentos, los límites de tamaño y cuándo conviene dividir antes.

Los PDF grandes son la misma solicitud que los pequeños (entra un archivo,
sale un archivo), pero el enfoque ingenuo de leer todo el documento en
memoria y esperar con un tiempo de espera predeterminado se cae a medida que
los archivos crecen. Transmita la carga y la descarga en streaming, dele a la
solicitud margen para terminar y reintente los fallos transitorios. Como la
API es *stateless*, una solicitud que falla no deja nada detrás, así que
reintentar es seguro.

## Transmita en streaming, no acumule en memoria

Lea la entrada del disco por trozos a medida que la envía, y escriba la
respuesta en disco por trozos a medida que llega. Nunca mantenga el documento
completo en memoria. Todos los ejemplos que siguen transmiten en ambos
sentidos y estampan una marca de agua en `big.pdf` como sustituto de
cualquier acción de salida única.

<CodeGroup>

```bash title="cURL"
# curl streams the upload from disk and the response to disk by default.
curl https://api.pdfblocks.com/v1/add_text_watermark \
  -H 'X-API-Key: your_api_key' \
  -F file=@big.pdf \
  -F line_1='CONFIDENTIAL' \
  --max-time 600 \
  --retry 3 --retry-delay 2 --retry-all-errors \
  -o watermarked.pdf
```

```python title="Python"
# pip install requests requests-toolbelt
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

with open('big.pdf', 'rb') as file:
    form = MultipartEncoder(fields={
        'file': ('big.pdf', file, 'application/pdf'),  # streamed from disk
        'line_1': 'CONFIDENTIAL',
    })
    with requests.post(
        'https://api.pdfblocks.com/v1/add_text_watermark',
        headers={'X-API-Key': 'your_api_key', 'Content-Type': form.content_type},
        data=form,
        stream=True,
        timeout=(10, 600),  # 10s to connect, 600s to read
    ) as response:
        response.raise_for_status()
        with open('watermarked.pdf', 'wb') as output:
            for chunk in response.iter_content(chunk_size=65536):
                output.write(chunk)
```

```javascript title="Node.js"
// npm install form-data
import { createReadStream, createWriteStream } from 'node:fs';
import { request } from 'node:https';
import FormData from 'form-data';

const form = new FormData();
form.append('file', createReadStream('big.pdf')); // streamed from disk
form.append('line_1', 'CONFIDENTIAL');

const req = request(
  'https://api.pdfblocks.com/v1/add_text_watermark',
  {
    method: 'POST',
    headers: { 'X-API-Key': 'your_api_key', ...form.getHeaders() },
    timeout: 600000,
  },
  (response) => {
    if (response.statusCode !== 200) {
      throw new Error(`Request failed: ${response.statusCode}`);
    }
    response.pipe(createWriteStream('watermarked.pdf')); // streamed to disk
  },
);

form.pipe(req); // stream the multipart body
```

```go title="Go"
package main

import (
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

func main() {
	// Generate the multipart body on the fly with io.Pipe: the file is never
	// held in memory in full.
	pr, pw := io.Pipe()
	form := multipart.NewWriter(pw)

	go func() {
		defer pw.Close()
		defer form.Close()
		file, _ := os.Open("big.pdf")
		defer file.Close()
		part, _ := form.CreateFormFile("file", "big.pdf")
		io.Copy(part, file) // streamed, not buffered
		form.WriteField("line_1", "CONFIDENTIAL")
	}()

	req, _ := http.NewRequest("POST",
		"https://api.pdfblocks.com/v1/add_text_watermark", pr)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")

	client := &http.Client{Timeout: 10 * time.Minute}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, _ := os.Create("watermarked.pdf")
	defer out.Close()
	io.Copy(out, res.Body) // streamed to disk
}
```

```csharp title="C#"
using var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

using var fileStream = File.OpenRead("big.pdf");
using var form = new MultipartFormDataContent
{
    { new StreamContent(fileStream), "file", "big.pdf" }, // streamed from disk
    { new StringContent("CONFIDENTIAL"), "line_1" },
};

// ResponseHeadersRead returns as soon as the headers arrive, so the body isn't
// buffered before you read it.
using var response = await client.PostAsync(
    "https://api.pdfblocks.com/v1/add_text_watermark",
    form,
    HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();

await using var output = File.Create("watermarked.pdf");
await response.Content.CopyToAsync(output); // streamed to disk
```

</CodeGroup>

## Ponga tiempos de espera generosos

Un documento grande tarda más en cargarse y en procesarse, así que un tiempo
de espera predeterminado del cliente, a menudo de 30 segundos o menos,
cortará una solicitud que iba bien. Reparta el presupuesto:

- Un tiempo de espera de **conexión** corto (unos pocos segundos) falla
  rápido cuando el host no está accesible.
- Un tiempo de espera de **lectura** largo le da al procesamiento tiempo para
  terminar. Los ejemplos anteriores usan diez minutos; dimensiónelo según el
  documento más grande que espere.

No establezca un tiempo de espera sin límite: conviene que una conexión
atascada se rinda en algún momento para que un reintento tome el relevo.

## Reintente los fallos transitorios

Los reinicios de red, los tiempos de espera de conexión y (cuando se
apliquen) las respuestas `429` o `5xx` son transitorios: es muy posible que
la misma solicitud tenga éxito un momento después. Reintente con espera
exponencial y algo de variación aleatoria. Reintentar aquí es seguro
precisamente porque la API es *stateless*: cada llamada es una función pura
de sus entradas, sin escrituras parciales que deshacer.

```python title="Python"
import random
import time
import requests


def send_with_retries(build_request, attempts=4):
    for attempt in range(attempts):
        try:
            response = build_request()
            # Retry only on server-side / throttling responses.
            if response.status_code < 500 and response.status_code != 429:
                return response
        except requests.RequestException:
            if attempt == attempts - 1:
                raise
        # Back off: 1s, 2s, 4s, … plus jitter.
        time.sleep(2 ** attempt + random.uniform(0, 1))
    return response
```

No reintente un `400`: un error de validación no se arregla solo, y el cuerpo
`application/problem+json` le dice qué campo corregir. Consulte
[Errores](/docs/api/errors) para ver el catálogo. Desde la línea de comandos,
cURL ya lo trae incorporado: `--retry 3 --retry-delay 2 --retry-all-errors`.

## Límites de tamaño y rate limits

<Note>
  Los límites de tamaño de las solicitudes, los rate limits y sus códigos de
  estado `413`, `429` y afines son una parte reservada del contrato de la API
  y **todavía no se aplican**. No se publican umbrales fijos: diseñe para el
  contrato, no para un número concreto.
</Note>

Para mantener la robustez a medida que los límites entren en vigor, mantenga
cada solicitud en un tamaño razonable, trate `413` y `429` como reintentables
con espera exponencial (o, en el caso de `413`, como una señal para dividir la
entrada) y respete la cabecera `Retry-After` cuando esté presente. Consulte
[Rate limits y uso](/docs/api/rate-limits-and-usage) para ver cómo se mide el
uso.

## Divida primero los archivos muy grandes

Si un documento es demasiado grande o demasiado lento para procesarlo en una
sola llamada, pártalo en partes más pequeñas, procese cada una por separado
y, si necesita un solo archivo de vuelta, una los resultados. Así se acota la
memoria y el tiempo que necesita cualquier solicitud individual, y permite
procesar las partes en paralelo.

<Steps>

<Step title="Divida en partes">
  Use [Dividir por tamaño de archivo](/docs/api/split-pdf-by-file-size) o
  [Dividir por número de páginas](/docs/api/split-pdf-by-page-count) para
  producir partes manejables. Consulte [Dividir un
  PDF](/docs/api/splitting-a-pdf) para saber cómo desempaquetar la salida.
</Step>

<Step title="Procese cada parte">
  Ejecute su acción sobre cada parte, en paralelo si quiere, ya que las
  llamadas son independientes.
</Step>

<Step title="Una las partes si hace falta">
  Vuelva a combinar las partes procesadas con [Unir documentos
  PDF](/docs/api/merge-pdf-documents).
</Step>

</Steps>

## Relacionado

<CardGroup cols={2}>

<Card title="Rate limits y uso" href="/docs/api/rate-limits-and-usage">
  Cómo se mide el uso y el contrato de límites reservados.
</Card>

<Card title="Errores" href="/docs/api/errors">
  Códigos de estado, la forma de problem+json y qué reintentar.
</Card>

<Card title="Dividir un PDF" href="/docs/api/splitting-a-pdf">
  Parta primero un documento enorme en partes más pequeñas.
</Card>

<Card title="Encadenar acciones" href="/docs/api/chaining-actions">
  Ejecute un pipeline de varios pasos en streaming, sin archivos temporales.
</Card>

</CardGroup>
