Pregunta y contexto
Esta pregunta evalúa si un ingeniero de backend puede manejar al mismo tiempo el rendimiento en páginas profundas, un ordenamiento estable bajo escrituras concurrentes y un contrato de cursor explícito. Supón que un cliente navega por órdenes o un feed cambiante en orden de tiempo descendente, 50 filas por página, con nuevas filas llegando continuamente; en su mayoría necesita la página siguiente en lugar de saltos de página arbitrarios.
Se adapta a roles de backend, servicios de datos y plataforma. No anuncies que un cursor es siempre más rápido. Primero aclara el ordenamiento, los saltos de página, las exportaciones, la consistencia, la semántica de eliminación, las réplicas y los índices, ya que cada restricción cambia la elección.
Qué está evaluando el entrevistador
Una respuesta sólida explica por qué un OFFSET profundo escanea y descarta filas anteriores, por qué una clave de ordenamiento no única se desvía bajo escrituras concurrentes y por qué la paginación por keyset necesita un orden total estable y un índice coincidente. También separa un token de keyset sin estado de un cursor de base de datos que mantiene una transacción, y propone pruebas para duplicados, huecos, una fila límite eliminada y tokens falsificados.
Preguntas aclaratorias para hacer
- ¿Los usuarios deben saltar a la página N o ver un conteo total? La paginación por keyset pura no proporciona eso directamente.
- ¿Cuál es el orden de clasificación? ¿La marca de tiempo es única e inmutable, o necesita un ID único para desempate?
- ¿El cliente desea una vista en vivo o una instantánea para un solo recorrido? Las inserciones y eliminaciones se ven diferentes bajo cada una.
- ¿La consulta abarca réplicas, shards o filtros de inquilinos? El índice y el token deben vincular esas condiciones.
- ¿Los clientes pueden decodificar, modificar o reproducir un cursor? Eso determina la firma, la expiración y el control de versiones.
Un marco de respuesta de 30 segundos
“Primero aclaro el ordenamiento, los saltos de página y la semántica de consistencia. Para una lista grande y cambiante, utilizaría paginación por keyset con un desempate único, como created_at DESC, id DESC, respaldada por un índice compuesto coincidente; la siguiente solicitud transporta las claves de ordenamiento de la última fila en lugar de un OFFSET profundo. El cursor codifica los filtros, la versión de ordenamiento y el límite, luego se firma y se le asigna una expiración. Si el recorrido necesita una instantánea fija, consideraría una marca de tiempo o un cursor de base de datos transaccional y explicaría su costo de recursos. Validaría el contrato con inserciones concurrentes, eliminaciones, marcas de tiempo duplicadas y tokens manipulados.”
Respuesta paso a paso
Paso 1: Definir el contrato de paginación
Especifica el límite de limit, el orden predeterminado, next_cursor, has_more y los filtros. Un ordenamiento estable requiere un orden total; ordenar solo por created_at es ambiguo cuando las filas comparten una marca de tiempo, así que añade un ID único inmutable. Si el cliente necesita una página anterior, diseña la comparación inversa y el manejo de límites explícitamente en lugar de simplemente invertir la consulta de la página siguiente.
Paso 2: Explicar el rendimiento de OFFSET y la desviación
OFFSET k LIMIT n comúnmente requiere que la base de datos localice y omita k filas precedentes, por lo que el trabajo de páginas profundas crece con k. Si ocurre una inserción o eliminación antes de una página ya leída, los offsets posteriores se mueven y pueden producir duplicados o huecos. Una lista de administración pequeña y mayoritariamente estática que necesita saltos de página puede aceptar OFFSET, pero limita su profundidad y verifica el costo con un plan de ejecución.
Paso 3: Implementar keyset sobre un orden total estable
Para (created_at, id) descendente, la primera página no tiene límite y la siguiente página utiliza las claves de la última fila:
-- first page
SELECT id, title, created_at
FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 50;
-- next page: boundary comes from the last returned row
SELECT id, title, created_at
FROM posts
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 50;La consulta busca a partir de un límite de índice y escanea un número acotado de filas en lugar de omitir cada página anterior. El orden de las columnas del índice compuesto, los filtros y la dirección deben coincidir con la consulta; de lo contrario, una consulta keyset teóricamente buena aún puede escanear un rango grande.
Paso 4: Diseñar un token de cursor verificable
No trates un offset interno como un cursor. Incluye claves de límite, un resumen de filtros, versión de ordenamiento y expiración; firma el token o utiliza cifrado autenticado para que los clientes no puedan modificarlo. Al recibirlo, valida la versión, el inquilino y los filtros contra la solicitud. Si el ordenamiento cambia, rechaza la versión anterior o reinicia explícitamente en lugar de interpretar el mismo token bajo un orden diferente.
Paso 5: Elegir semántica en vivo o de instantánea
La paginación keyset en vivo permite que aparezcan nuevas filas en la parte superior; las filas que ya están más allá del límite normalmente no se repiten. Eliminar la fila límite no hace que vuelva a aparecer, aunque el conteo total cambie. Para una exportación o auditoría que necesita un conjunto fijo, incluye una marca de tiempo de lectura, versión de instantánea o cursor de base de datos en el contrato. Un cursor de base de datos puede retener una transacción y recursos, por lo que no es automáticamente adecuado para paginación HTTP de larga duración.
Paso 6: Manejar réplicas, shards y filas límite
El retraso de replicación puede ocultar temporalmente una fila devuelta por el primario. Fija la ruta, transporta un límite de tiempo confirmado o documenta la consistencia eventual. A través de shards, cada shard puede producir un keyset local y el servicio puede realizar una fusión k-way consciente del cursor. Prueba la eliminación de la fila límite final, marcas de tiempo iguales y cambios de filtros.
Paso 7: Verificar rendimiento y exactitud
Las comprobaciones de rendimiento incluyen escaneos de filas en páginas profundas, latencia P95 y uso de índices. Las pruebas de exactitud insertan, eliminan y actualizan filas entre solicitudes y aseguran el comportamiento prometido de duplicados y huecos. Registra los filtros de cada página, la versión de ordenamiento, las primeras y últimas claves y la versión del token para que la desviación en producción pueda asignarse a un límite concreto.
Ejemplo de una respuesta sólida
“Primero aclararía si los usuarios necesitan saltos de página, conteos totales o una instantánea fija, y si el campo de ordenamiento es único. Para una lista escrita continuamente que se navega principalmente hacia adelante, elegiría paginación por keyset: usar created_at inmutable más id único para un orden total e índice compuesto, luego usar las dos claves de la última fila como el siguiente límite de rango. Las páginas profundas no escanean y descartan todas las filas anteriores, y una inserción antes del límite no desplaza cada página posterior.
Codificaría filtros, versión de ordenamiento, claves de límite y expiración en un token firmado, luego requeriría que el inquilino y los filtros del token coincidan con la solicitud. Bajo semántica en vivo, las nuevas filas aparecen en la parte superior; para resultados fijos a nivel de auditoría, consideraría una marca de tiempo o un cursor transaccional controlado y explicaría los costos de transacciones largas. Con réplicas, fijaría el enrutamiento o documentaría el límite de visibilidad.
Finalmente, verificaría planes de ejecución y pruebas de concurrencia que cubran escaneos profundos, marcas de tiempo duplicadas, inserciones, eliminaciones, actualizaciones de filas límite, retraso de replicación y manipulación de tokens. Una lista de administración pequeña que deba saltar páginas puede mantener OFFSET, pero limitaría la profundidad y monitorearía la latencia.”
Errores comunes
- Afirmar que los cursores siempre son más rápidos → ignora los saltos y el costo de transacciones largas → compara OFFSET, keyset y cursores de base de datos según las restricciones.
- Ordenar solo por tiempo → los empates tienen un orden inestable → añade un ID único inmutable.
- Poner un número de offset en el token → las páginas profundas aún se desvían y cuestan más → transporta el límite de ordenamiento y el resumen de filtros.
- Dejar la semántica en vivo/instantánea sin definir → los clientes no pueden interpretar inserciones o eliminaciones → establece la semántica de visibilidad en el contrato de la API.
- Ignorar la dirección del índice y los filtros → keyset aún escanea muchas filas → verifica el índice compuesto con un plan de ejecución.
- Aceptar cualquier cursor del cliente → se pueden falsificar inquilinos o límites obsoletos → firma, valida la versión y expira los tokens.
Preguntas de seguimiento y respuestas
Pregunta de seguimiento 1: Los usuarios deben saltar a la página 500. ¿Qué haces?
Para una lista de administración pequeña o mayoritariamente estática, un OFFSET acotado es razonable. Para una lista grande, usa límites de página precalculados o filtros de búsqueda y ordenamiento para que los usuarios ubiquen la región deseada en lugar de prometer baja latencia a una profundidad arbitraria. Documenta los límites de costo y consistencia de cualquiera de las opciones.
Pregunta de seguimiento 2: created_at se puede editar. ¿El cursor es estable?
No. Utiliza el tiempo de creación inmutable más un ID único, o congela el campo de ordenación mutable en una versión de instantánea. Si el negocio debe ordenar por un campo mutable, acepta el movimiento en una vista en vivo o utiliza una versión fija con costo adicional de almacenamiento y lectura.
Pregunta de seguimiento 3: Una réplica con retraso hace que la página siguiente sea corta. ¿Cómo lo manejas?
Fija las lecturas al primario o a la misma réplica según el requisito de consistencia, o transporta un límite de “no antes de tiempo/LSN” y devuelve un estado explícito tras un tiempo de espera. No trates silenciosamente las filas faltantes como el fin de la lista; distingue la invisibilidad temporal de has_more=false.
Pregunta de seguimiento 4: ¿Cómo demuestras que no hay duplicados ni huecos?
Crea pruebas de concurrencia controladas: lee la página uno, inserta antes del límite, elimina la fila límite, inserta claves de ordenación iguales y actualiza filas; luego inspecciona el conjunto de IDs únicos, el ordenamiento y la visibilidad prometida. Registra los límites de página, las versiones de token y los identificadores de instantánea para que un fallo pueda reproducirse en el límite exacto.