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.
# 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# 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)// 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 bodypackage 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
}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 diskPonga 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.
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 responseNo 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 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
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.
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 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.
Use Dividir por tamaño de archivo o Dividir por número de páginas para producir partes manejables. Consulte Dividir un PDF para saber cómo desempaquetar la salida.
Ejecute su acción sobre cada parte, en paralelo si quiere, ya que las llamadas son independientes.
Vuelva a combinar las partes procesadas con Unir documentos PDF.
Relacionado
Cómo se mide el uso y el contrato de límites reservados.
Códigos de estado, la forma de problem+json y qué reintentar.
Parta primero un documento enorme en partes más pequeñas.
Ejecute un pipeline de varios pasos en streaming, sin archivos temporales.