Tema representativo de entrevista

Entrevista de backend: ¿Cómo diseñarías una API por lotes (batch) con fallas parciales?

BackendDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Necesitas una API de actualización por lotes que acepte hasta 1,000 recursos por solicitud. Algunos elementos pueden fallar en la validación, autorización o debido a una dependencia temporal. ¿Cómo diseñarías las solicitudes, respuestas, idempotencia, reintentos y observabilidad para que los clientes nunca confundan un éxito parcial con un éxito completo?

Planteamiento y alcance

Esta pregunta evalúa si un ingeniero de backend puede otorgar a los procesos "por lotes" (batch) una semántica precisa de ejecución y resultados. Supón que un cliente desea actualizar etiquetas de pedidos o configuraciones de usuario en masa; los elementos pueden ser independientes o compartir cuotas, versiones o dependencias, y puede ocurrir un tiempo de espera de red (timeout) después de que el servidor haya completado algunos elementos. Debes definir los límites sincrónicos y asíncronos para que un cliente pueda continuar de forma segura.

Es adecuada para ingenieros de backend, diseñadores de API e ingenieros de plataforma. Concéntrate en la atomicidad, el estado por elemento, la identidad de la solicitud, el reintento idempotente, la autorización y los límites de recursos, la compatibilidad de respuestas y la recuperación, en lugar de una elección específica de REST, gRPC o colas. Establece el comportamiento predeterminado, cómo se selecciona explícitamente el éxito parcial y la diferencia entre una falla completa, una falla parcial y un resultado desconocido.

Qué evalúa el entrevistador

Una respuesta sólida indaga si los elementos dependen entre sí antes de elegir un procesamiento atómico, un éxito parcial opcional o una operación asíncrona. No se limita a devolver un HTTP 200 y un recuento de fallas; asigna cada entrada a un resultado estable, una clase de error y su capacidad de reintento. Google AIP-234 señala que cambiar un método síncrono por lotes existente a éxito parcial puede romper los clientes y recomienda un estado de falla detallado por índice; la guía de idempotencia de Stripe vincula los reintentos a los mismos parámetros y preserva el primer resultado. Cubre también los límites, la auditoría y el monitoreo.

Preguntas clarificadoras para hacer

  • ¿Existen dependencias de ordenamiento o transaccionales entre los elementos? ¿El negocio debe ser de todo o nada?
  • ¿El procesamiento genera efectos secundarios externos y puede una clave de idempotencia hacer que la repetición sea segura?
  • ¿El cliente necesita un resultado sincrónico o puede recibir una operación y sondear (poll) o suscribirse al progreso?
  • ¿Cuáles son los requisitos de límite de lote, tamaño del cuerpo, tiempo de espera, cuota por inquilino (tenant) y equidad (fairness)?
  • ¿Qué errores son reintentables, cuáles requieren cambios en los datos de entrada y cómo se puede consultar un resultado desconocido?

Un marco de respuesta en 30 segundos

“Separaría los lotes atómicos de los lotes con éxito parcial y adoptaría por defecto el comportamiento más seguro; el éxito parcial se habilita solo cuando el cliente lo solicita explícitamente y el negocio lo permite. Cada entrada obtiene un índice estable o un ID de solicitud del cliente, mientras que el servidor registra el estado de idempotencia a nivel de lote y de elemento, el resultado y la clase de error. El trabajo síncrono está acotado; el trabajo más grande crea una operación que se ejecuta en fragmentos (shards) y expone su progreso. La respuesta distingue entre éxito, falla permanente, falla transitoria y estado desconocido, y los reintentos reutilizan la misma clave de elemento. Validaría el diseño con límites de tasa (rate limits), registros de auditoría, métricas e inyección de fallas para demostrar que los efectos secundarios no se duplican ni se pierden.”

Respuesta paso a paso

Paso 1: Elegir el modelo de atomicidad

Si los elementos comparten una invariante de negocio indivisible, como que ambos lados de una transferencia tengan éxito juntos, utiliza una transacción por lotes atómica o rechaza la interfaz por lotes. Si los elementos son independientes, considera el éxito parcial. No ocultes el modelo detrás de un "mejor esfuerzo" (best effort); el cliente debe saber si todos tuvieron éxito, todos fallaron, algunos se completaron o si el servidor no puede confirmar el resultado.

Paso 2: Definir la identidad de la solicitud y del elemento

Incluye un ID de lote, un arreglo de elementos y un ID de solicitud de elemento generado por el cliente. Un ID de elemento es estable dentro de su alcance de negocio. Un envío duplicado con el mismo ID debe comparar los parámetros importantes; diferentes parámetros deberían generar un conflicto en lugar de una sobrescritura silenciosa. Un ID de lote ayuda al rastreo pero no puede reemplazar la idempotencia del elemento, ya que un reintento parcial puede contener únicamente los elementos fallidos del lote original.

Paso 3: Establecer el límite sincrónico/asíncrono

Los lotes pequeños pueden devolver resultados finales por elemento de forma sincrónica, con un presupuesto total de tiempo y recursos. Los lotes grandes o las operaciones que llaman a sistemas externos deben devolver un ID de operación, ejecutarse en fragmentos acotados y persistir el progreso. El cliente consulta los recuentos de completados, en procesamiento, reintentables y fallas permanentes; tras una desconexión, consulta el estado en lugar de iniciar efectos secundarios nuevamente.

Paso 4: Diseñar una respuesta analizable (parseable)

La respuesta debe permitir que el cliente encuentre un resultado mediante la identidad de la entrada, incluso si el servidor reordena el trabajo. Utiliza un índice estable y un ID de cliente; incluye un código de error legible por máquina, capacidad de reintento, posible cambio de estado al reintentar y un mensaje seguro para el usuario. Por ejemplo:

json
{
  "batch_id": "b_123",
  "status": "PARTIAL",
  "results": [
    {"index": 0, "request_id": "r_0", "status": "SUCCEEDED"},
    {"index": 1, "request_id": "r_1", "status": "FAILED", "error": {"code": "VERSION_CONFLICT", "retryable": false}}
  ],
  "next_page_token": null
}

Google AIP-234 recomienda un mapa failed_requests de índice de entrada a estado detallado para actualizaciones asíncronas por lotes. Evita obligar al cliente a mantener un mapa de ID de solicitud a solicitud y evita hacer eco de cuerpos de solicitud sensibles. Si una API sincrónica existente necesita semántica de éxito parcial, publica una nueva versión o negocia con un campo explícito para que los clientes antiguos no interpreten un estado de éxito como la finalización de cada elemento.

Paso 5: Manejar idempotencia, reintentos y resultados desconocidos

El servidor puede rechazar parámetros inválidos antes de que comiencen los efectos secundarios sin guardar un resultado idempotente. Una vez que inicia la ejecución, guarda el resultado o un estado en progreso consultable. Tras un tiempo de espera de red, el cliente no debe adivinar ni repetir todo el lote; reutiliza las claves de lote y de elemento, consulta los elementos desconocidos y reintenta únicamente los elementos claramente reintentables. Usa retroceso exponencial (backoff) y variación aleatoria (jitter) para errores transitorios, exige cambios de entrada para errores permanentes y devuelve un conflicto cuando una clave reutilizada contiene parámetros diferentes.

Paso 6: Aislar recursos y ordenamiento

Divide el trabajo en fragmentos acotados y limita la concurrencia por inquilino, lote y dependencia. Ejecuta los elementos dependientes mediante una topología o fase explícita; ejecuta los elementos independientes en paralelo con un presupuesto de reintentos compartido para evitar una tormenta de fallas. Verifica la autorización y la cuota antes de cada elemento, de modo que un endpoint por lotes no pueda eludir la política de la API de elemento individual.

Paso 7: Definir la semántica de errores, cancelación y recuperación

Clasifica los errores de validación de entrada, autorización, conflicto de versiones, cuota, dependencia transitoria y resultado desconocido. La cancelación detiene los elementos que no han comenzado; no se puede fingir la reversión (rollback) de efectos secundarios completados. Si el negocio requiere reversión, proporciona una operación de compensación separada. Persiste las tareas en segundo plano, los resultados y la solicitud original para que un reinicio se reanude desde el estado del elemento en lugar de ejecutar cada elemento de nuevo.

Paso 8: Verificar la consistencia y la visibilidad operativa

Prueba escenarios de todo exitoso, todo fallido, resultados mixtos, solicitudes duplicadas, conflictos de parámetros, tiempo de espera y posterior consulta, fluctuación de dependencias (flapping), carreras de cancelación y reinicios de workers. Monitorea el éxito del lote, las clases de error por elemento, los estados desconocidos, la amplificación de reintentos, la antigüedad de la cola, la latencia de procesamiento, los rechazos por cuota y los envíos duplicados. Audita el lote, el elemento, el actor, la decisión de autorización y el resultado final para que el personal de soporte pueda explicar con precisión qué elementos se completaron.

Compensaciones de diseño y límites

El éxito parcial no es automáticamente la opción más avanzada. Se adapta a elementos independientes que los clientes pueden reparar uno por uno; los saldos, inventarios y las invariantes entre recursos son más seguros con atomicidad o un flujo de trabajo explícito. HTTP 207 Multi-Status puede transportar varios estados de recursos, pero RFC 4918 lo define para WebDAV. Una API JSON general no debe asumir que los clientes entienden resultados parciales simplemente porque devuelve 207. Coloca la semántica de los elementos en un cuerpo de respuesta estable y elige los códigos de estado teniendo en cuenta la compatibilidad con los clientes existentes.

¿Cuándo deberías elegir lotes atómicos?

Elige la atomicidad cuando cualquier falla de un elemento invalide el estado agregado o cuando la compensación sea inaceptable. Utiliza una transacción, una verificación previa (preflight) o un flujo de trabajo, reconociendo que el trabajo fragmentado y los efectos secundarios externos no comparten una transacción de base de datos; pueden requerir fases de reserva, confirmación (commit) y compensación.

¿Cuándo deberías elegir una operación asíncrona?

Devuelve una operación cuando la duración sea impredecible, el lote sea grande, involucre llamadas externas o cuando el cliente no deba mantener una conexión abierta. Su estado debe poder reconsultarse y los resultados deben estar paginados. El progreso no debe llamar "completado" a lo que solo está "aceptado".

Simulacros de fallas y plan de evolución

Realiza una prueba piloto con un conjunto limitado de inquilinos, registra los estados de los elementos y el comportamiento de reintento, y luego aumenta el límite del lote gradualmente. Inyecta una interrupción de red después de que el elemento 30 tenga éxito, una dependencia que devuelva 503 continuamente, la misma clave con diferentes parámetros y el reinicio de un worker de operaciones. Amplía las cuotas o habilita el éxito parcial solo después de que los clientes puedan consultar resultados desconocidos y el servidor demuestre que no duplica efectos secundarios.

¿Cómo evolucionar una API sincrónica a éxito parcial?

Mantén la semántica atómica de la versión anterior y agrega una versión que devuelva una operación y fallas por elemento. Como alternativa, exige un campo explícito return_partial_success y preserva el comportamiento anterior cuando esté ausente. Documenta los códigos de estado, los campos de respuesta, las reglas de reintento y las fechas de desaprobación (deprecation) para que los clientes no cambien silenciosamente su interpretación.

¿Cómo evalúas la corrección del cliente?

Observa si los clientes persisten los ID de los elementos, reintentan solo las fallas reintentables, consultan resultados desconocidos y evitan efectos secundarios duplicados y reintentos inválidos. Proporciona asistentes de análisis y consulta en los SDK principales, mientras el servidor aún tolera campos desconocidos y solicitudes duplicadas.

Errores comunes y preguntas de seguimiento

Devolver HTTP 200 con un recuento de fallas

Un cliente antiguo puede tratar la finalización parcial como completa, y aún no sabrá qué elementos reintentar. La respuesta necesita el estado general, la identidad del elemento, la clase de error y la siguiente acción.

Reintentar todo el lote ante cualquier falla

Esto puede repetir efectos secundarios que ya tuvieron éxito. Consulta primero el estado de idempotencia del lote y del elemento, reintenta solo las fallas transitorias explícitas y utiliza un nuevo ID de solicitud de negocio cuando cambien los parámetros.

¿Cómo deben ordenarse los resultados?

Asocia los resultados por índice de entrada o ID de solicitud estable, no por orden de finalización. Mantén la identidad cuando los resultados estén paginados para que el cliente pueda fusionar las páginas de forma segura.

¿Se puede hacer una reversión (rollback) después de que un elemento tiene éxito y el lote se cancela?

La cancelación afecta solo a los elementos que no han comenzado. Los efectos secundarios externos ya realizados necesitan una API de compensación o manejo manual; "lote cancelado" no es una garantía de reversión.

¿Cómo evitas que el endpoint por lotes eluda la autorización?

Verifica los permisos del inquilino y de la operación a nivel de lote, luego vuelve a verificar la propiedad del recurso, la versión y los permisos de campo antes de cada elemento. El procesamiento por lotes cambia la programación, no el alcance de la autorización.

¿Cómo explicas la falla parcial final?

Proporciona un ID de lote auditable, ID de elemento, estado, código de error, capacidad de reintento y marca de tiempo. Ofrece a soporte una explicación de negocio segura y retén los errores de seguimiento y dependencias para el diagnóstico de ingeniería.

Fuentes públicas

Preguntas relacionadas