Planteamiento y alcance
Una clave de configuración de producto de alto tráfico recibe 50.000 lecturas por segundo. La base de datos de origen admite de forma segura hasta 200 consultas por segundo, y reconstruir la caché toma 800 ms en el p95. Los datos en caché son frescos durante 10 minutos y el negocio tolera como máximo 30 segundos de obsolescencia. El servicio se ejecuta en 100 instancias de aplicación sin estado que comparten una caché remota y la misma base de datos de origen.
Diseñe la ruta completa de lectura y actualización. Cubra la expiración suave, la expiración dura, la primera carga en frío, la caída del proceso de actualización, una interrupción de la caché y cambios en los datos de origen durante una actualización. Explique cómo demostraría que el diseño no se limita a trasladar la carga concurrente a la base de datos. Los valores de rendimiento, latencia y expiración son datos de entrada para la entrevista, no afirmaciones de rendimiento sobre un producto.
Esta es una pregunta de confiabilidad del backend. Un conjunto público actual de preguntas de entrevista para SRE de 2026 presenta explícitamente una estampida de caché causada por la expiración de una clave crítica bajo 100.000 solicitudes por segundo; esta versión convierte ese escenario en restricciones de ingeniería medibles. Es diferente de implementar una política de desalojo LRU en proceso porque su problema central radica en la concurrencia entre instancias y la semántica de fallas.
Qué evalúa el entrevistador
Primero, el candidato debe cuantificar el evento de expiración. Si cada llegada elude la caché durante una reconstrucción de 800 ms, aproximadamente 50,000 × 0.8 = 40,000 solicitudes podrían intentar alcanzar el origen. Eso está muy por encima de una tasa segura de 200 consultas por segundo. Agregar una caché no ha resuelto la carga sincronizada cuando la entrada de la caché desaparece.
Segundo, una respuesta sólida separa tres capas de protección. La coalescencia local de solicitudes restringe solo un proceso; si cada una de las 100 instancias elige un actualizador, el sistema aún podría emitir 100 reconstrucciones concurrentes. Una concesión distribuida normalmente reduce las actualizaciones entre instancias a una sola, pero la expiración de la concesión, las pausas del proceso y las particiones aún pueden generar actualizaciones superpuestas. Por lo tanto, un aislamiento de concurrencia (bulkhead) o un límite de tasa en el origen debe permanecer como una defensa final independiente.
Tercero, la semántica de expiración debe ser explícita. Los datos frescos se devuelven de inmediato. Después de la expiración suave, los datos se pueden devolver dentro de la ventana de obsolescencia de 30 segundos mientras se intenta una actualización en segundo plano. Después de la expiración dura, el sistema no puede servir datos obsoletos indefinidamente: las solicitudes que no poseen los derechos de actualización deben esperar durante un período delimitado, degradarse o fallar en lugar de alcanzar todas el origen.
Por último, el entrevistador debe escuchar cómo el diseño evita que un actualizador antiguo sobrescriba un valor más nuevo. Una concesión proporciona exclusión solo durante su ventana de validez; no es una garantía de exactamente una vez. Las entradas de caché necesitan una versión o generación de origen, y las escrituras deben comparar las versiones antes de reemplazar los datos.
Preguntas para clarificar
- ¿Es realmente seguro servir datos obsoletos? Este escenario permite como máximo 30 segundos. Los saldos, la autorización y las deducciones de inventario pueden requerir un comportamiento más estricto.
- ¿Qué mide el límite de origen? Considere 200 consultas por segundo como el límite seguro para toda la base de datos, mientras solicita también los límites de concurrencia y tiempo de espera por clave.
- ¿Puede una carga en frío devolver un valor predeterminado? Por defecto, no existe ningún valor obsoleto, por lo que solo un actualizador llega al origen y los seguidores esperan durante un tiempo delimitado. Un valor predeterminado estático es una degradación de producto explícita si está disponible.
- ¿Cómo se invalidan las entradas? Utilice marcas de tiempo de frescura lógica y expiración dura en lugar de eliminar físicamente cada copia en el mismo segundo. Los eventos de cambio en el origen pueden actualizar o invalidar entradas de manera anticipada.
- ¿Es multirregión? Comience con 100 instancias en una sola región. Múltiples regiones requieren presupuestos de origen asignados; no debe asumirse que un bloqueo interregional resuelva todos los modos de falla.
- ¿Deben saber los clientes que los datos son obsoletos? Las respuestas internas y la telemetría deben registrar al menos
stale_agey el motivo de la degradación. Los requisitos del producto deciden si los usuarios finales lo ven. - ¿Qué sucede si la caché falla? Defina la degradación y los presupuestos de origen por adelantado. Un error de caché no puede significar que cada solicitud consulte la base de datos.
Respuesta de 30 segundos
“Almacenaría en caché fresh_until, stale_until y la versión de origen junto con el valor. Los aciertos frescos se devuelven directamente. Durante la ventana de obsolescencia de 30 segundos, se devuelve el valor antiguo y se elige un actualizador mediante singleflight por proceso más una concesión distribuida con un token de propiedad y TTL. En un fallo en frío o duro, los seguidores solo esperan durante un tiempo delimitado y vuelven a leer con variación aleatoria (jitter). Las escrituras de actualización comparan las versiones de origen y la liberación de la concesión verifica el token. Debido a que la expiración de la concesión aún puede permitir superposiciones, la base de datos también necesita bulkheads por clave y globales. Validaría el QPS de origen, la concurrencia y la antigüedad máxima de obsolescencia con pruebas de carga de expiración, caídas del actualizador, desbordamientos de la concesión y simulacros de cortes de caché.”
Análisis detallado paso a paso
Comience definiendo una entrada de caché en lugar de almacenar solo el valor de negocio:
CacheEntry {
value
source_version
generated_at
fresh_until
stale_until
}fresh_until finaliza el período de frescura de 10 minutos y stale_until lo extiende por no más de 30 segundos. El TTL físico de la clave remota debe cubrir stale_until más un pequeño margen de limpieza; de lo contrario, la caché eliminará un valor que aún es seguro de servir durante la expiración suave. La ventana de obsolescencia es un presupuesto del negocio y no debe extenderse silenciosamente tras fallas repetidas de actualización.
La ruta de lectura se puede expresar en pseudocódigo:
entry = cache.get(key)
now = clock.now()
if entry exists and now < entry.fresh_until:
return entry.value
if entry exists and now < entry.stale_until:
try_refresh_async(key)
return entry.value
return rebuild_or_wait(key, request_deadline)La ruta de expiración suave protege la latencia de las solicitudes. La primera solicitud que detecta la expiración suave intenta una actualización en segundo plano mientras las demás continúan usando el valor antiguo. El patrón singleflight por proceso agrupa las llamadas de actualización por clave; la implementación pública de Go lo define como una ejecución en curso por clave cuyo resultado se comparte con las llamadas duplicadas. No cruza los límites del proceso, por lo que resulta insuficiente por sí solo a través de 100 instancias.
Utilice una concesión con límite de tiempo para la actualización entre instancias. Un contendiente genera un token aleatorio y no reutilizable, y ejecuta:
SET refresh:{key} {token} NX PX {lease_ms}Después de adquirir la concesión, vuelva a leer la caché en caso de que otro actualizador acabe de completar el proceso y consulte el origen solo si aún se necesita una actualización. Libere la concesión únicamente si su valor actual sigue siendo igual al token del propietario. Un simple DEL es inseguro: un actualizador antiguo puede pausarse hasta que expire su concesión, un sucesor puede adquirir una nueva concesión y el proceso antiguo puede reanudarse y eliminar la concesión del sucesor. La guía de bloqueos distribuidos de Redis exige de manera similar un valor único y una verificación de propiedad para una liberación segura.
Establezca lease_ms por encima del p99 de actualización medido más el margen de red y planificación, no mecánicamente en el p95 de 800 ms. Una concesión demasiado corta aumenta la superposición; una demasiado larga retrasa la toma de control tras una caída. La expiración de la concesión solo permite que un nuevo contendiente lo intente; no demuestra que la operación anterior se haya detenido. Por lo tanto, las lecturas de origen aún necesitan un servicio de singleflight por clave o un bulkhead en la base de datos, y las escrituras en caché deben tolerar ejecuciones superpuestas.
Lea una versión de origen monótona con los datos, como una versión de fila o una secuencia de eventos. Reemplace la caché solo cuando new.source_version >= cached.source_version. Si el origen no tiene una versión confiable, asigne generaciones de actualización mediante un incremento atómico en el almacenamiento de coordinación compartido y compárelas atómicamente en la caché. La caché sigue sin ser la fuente de la verdad; una actualización retrasada no debe sobrescribir una versión más nueva que ya haya sido instalada por un evento de cambio.
Una expiración dura o una primera carga no tienen ningún valor antiguo aceptable. El propietario de la concesión realiza la reconstrucción solo después de ingresar al bulkhead de origen. Los seguidores vuelven a leer la caché a intervalos cortos y con jitter dentro del límite de tiempo de la solicitud en lugar de consultar al unísono. Cuando esa espera expire, devuelva una respuesta degradada explícita o un error. Se puede utilizar un valor predeterminado estático si el producto lo permite, pero la disponibilidad aparente no debe enviar las 50.000 solicitudes al origen.
La defensa final del origen debe incluir un límite de concurrencia por clave, un límite de concurrencia de actualización global y un presupuesto de tasa de consultas. En condiciones normales, solo se ejecuta una reconstrucción para una clave. Durante fallas en las concesiones, el bulkhead mantiene el trabajo total del origen dentro de un límite seguro. Cuando se agota la capacidad, las actualizaciones fallan rápidamente o entran en una cola acotada; las solicitudes con expiración suave continúan utilizando valores menores a 30 segundos, mientras que los fallos duros siguen la política de degradación definida.
Cuando la caché no está disponible, las aplicaciones no deben desviar toda la tasa de lectura a la base de datos. Una near-cache de solo lectura de corta duración puede proporcionar valores obsoletos aceptables, pero todas las lecturas necesarias al origen deben seguir pasando por el bulkhead global. Las solicitudes sin un valor en la near-cache deben degradarse o fallar. Precargue las claves calientes a una tasa controlada durante la recuperación en lugar de que todas las instancias las recarguen a la vez. El jitter de TTL aleatorio ayuda cuando muchas claves diferentes expiran juntas, pero no resuelve la reconstrucción concurrente de una sola clave caliente.
Los modos de falla relacionados deben mantenerse separados. Una estampida de caché o ruptura de clave caliente es la reconstrucción concurrente después de que una clave caliente existente deja de estar disponible. Una avalancha de caché es la expiración simultánea de muchas claves o una interrupción generalizada de la caché. La penetración de caché es la búsqueda repetida de datos que no existen. El jitter de TTL ayuda principalmente en avalanchas, mientras que una caché negativa corta o un filtro de Bloom ayuda en la penetración; ninguno de ellos reemplaza la coalescencia de solicitudes para este escenario.
Los datos previsiblemente calientes se pueden actualizar antes de fresh_until. El enfoque probabilístico de revalidación anticipada publicado por Cloudflare aumenta la probabilidad de actualización a medida que se acerca la expiración, reduciendo la contención de bloqueos bajo altas tasas de solicitudes. Una regla fija como “actualizar el 1% de las solicitudes” es insegura porque su comportamiento varía con el tráfico. La ventana de obsolescencia delimitada y la actualización en segundo plano también coinciden con la semántica de HTTP stale-while-revalidate: el contenido obsoleto se permite solo dentro de un intervalo explícito mientras la revalidación ocurre de forma asíncrona.
Para entornos multirregión, prefiera cachés regionales y actualizadores regionales con presupuestos asignados de QPS y concurrencia de origen. Una única concesión global agrega latencia interregional y comportamiento de partición a la ruta de lectura. Si cada región comparte un solo origen, un plano de control puede asignar presupuestos de actualización o el origen puede exponer un servicio centralizado de reconstrucción. En cualquier caso, la suma de los presupuestos regionales debe permanecer dentro de las 200 consultas por segundo.
Monitoree las tasas de aciertos frescos, datos obsoletos servidos y fallos duros; la antigüedad de obsolescencia; los intentos y fallas de actualización; las llamadas compartidas en singleflight; la contención y expiración de concesiones; la latencia de actualización; el QPS, concurrencia y rechazos del origen; así como la latencia y errores de la caché. Alerte sobre presupuestos de origen agotados, valores cercanos a stale_until y fallas sostenidas de actualización en lugar de confiar únicamente en la tasa de aciertos de caché.
Pruebe los invariantes directamente. Fuerce la expiración de la clave bajo 50.000 solicitudes por segundo y asegure una sola reconstrucción de origen por clave en el caso normal. Provoque la caída del actualizador antes de su escritura y verifique que los datos obsoletos sigan disponibles y que un sucesor tome el control tras la expiración de la concesión. Pause un actualizador antiguo más allá de la concesión y verifique que no pueda reemplazar una versión más nueva. Deshabilite la caché y verifique que el origen se mantenga dentro de las 200 consultas por segundo y de su bulkhead de concurrencia. Expire muchas claves juntas y verifique el jitter de TTL junto con el presupuesto global.
Respuesta de ejemplo sólida
“Comenzaría analizando la carga en el peor de los casos. Cincuenta mil solicitudes por segundo multiplicadas por una reconstrucción de 800 ms producen aproximadamente 40.000 llegadas durante la ventana de expiración, mientras que la base de datos es segura solo para 200 consultas por segundo. Ningún seguidor puede derivarse directamente al origen.
Almacenaría en caché el valor con su versión de origen, fresh_until y stale_until. Se devuelve directamente durante 10 minutos y luego se sirve hasta por 30 segundos adicionales mientras se intenta una actualización. Cada proceso primero agrupa el trabajo local con singleflight; luego, los contendientes usan SET lock token NX PX lease para elegir un actualizador entre instancias. El propietario vuelve a verificar la caché antes de consultar la base de datos. La liberación de la concesión compara el token y las escrituras en caché comparan las versiones de origen para que un actualizador antiguo no pueda eliminar una nueva concesión ni sobrescribir datos más recientes.
En una carga en frío o después de 30 segundos, no hay ningún valor obsoleto aceptable. Una sola solicitud realiza la reconstrucción mientras los seguidores vuelven a leer con jitter hasta su tiempo límite, para luego usar una degradación predeterminada explícita o devolver un error. La base de datos también cuenta con bulkheads por clave y globales debido a que la expiración de la concesión puede permitir actualizadores superpuestos; un bloqueo no reemplaza la protección de capacidad.
Probaría la carga exactamente en el límite de expiración e inyectaría una caída del actualizador, una pausa mayor que la concesión, una falla de la caché y una actualización concurrente en el origen. Los criterios de aceptación incluyen un QPS de origen no superior a 200, una reconstrucción normal por clave, una antigüedad de obsolescencia no mayor a 30 segundos y ninguna regresión de versión por un escritor retrasado. Eso establece límites medibles para la latencia, la frescura y la seguridad del origen.”
Errores comunes
- Agregar solo jitter de TTL aleatorio → Esto distribuye la expiración entre diferentes claves, pero no detiene la reconstrucción concurrente de una sola clave caliente → Agrupe el trabajo por clave y conserve un bulkhead en el origen.
- Usar únicamente singleflight en proceso → Cien instancias aún pueden crear 100 actualizadores → Combine la coalescencia local con una concesión entre instancias.
- Leer la base de datos antes de adquirir los derechos de actualización → El pico de concurrencia ya habrá alcanzado el origen → Elija primero, vuelva a verificar la caché y luego ingrese dentro del presupuesto del origen.
- Adquirir un bloqueo
SETNXsin TTL → Un actualizador caído puede bloquear las actualizaciones indefinidamente → Utilice una concesión acotada con comportamiento de toma de control. - Liberar con un simple
DEL→ Un actualizador antiguo puede eliminar la concesión de su sucesor → Libere de forma atómica únicamente cuando el token único aún coincida. - Tratar una concesión como una ejecución de exactamente una vez → Un proceso pausado más allá del TTL puede superponerse con un sucesor → Utilice un bulkhead de origen y escrituras versionadas para tolerar la superposición.
- Servir datos obsoletos indefinidamente tras fallas → La antigüedad de los datos pierde su límite superior → Sirva únicamente antes de
stale_until, luego degrade o falle explícitamente. - Enviar todo el tráfico al origen durante una interrupción de la caché → 50.000 lecturas por segundo saturarán un origen diseñado de forma segura para 200 → Utilice valores de near-cache donde esté permitido y canalice cada lectura de origen a través del presupuesto compartido.
- Confundir estampida, avalancha y penetración → La solución ya no corresponde a la falla → Utilice coalescencia de solicitudes, jitter de TTL y almacenamiento en caché negativo para sus respectivos problemas.
- Monitorear únicamente la tasa de aciertos → Una tasa de aciertos alta puede ocultar actualizaciones fallidas y picos cortos en el origen → Monitoree también la antigüedad de obsolescencia, la concurrencia de actualización, las concesiones y los presupuestos de origen.
Preguntas de seguimiento
Pregunta de seguimiento 1: ¿Qué ocurre si el negocio no puede servir datos obsoletos bajo ninguna circunstancia?
Elimine las respuestas obsoletas y permita que los seguidores esperen únicamente durante un período delimitado. Aprovisione suficiente capacidad de reconstrucción y devuelva fallas explícitas sin comprometer la seguridad del origen.
Pregunta de seguimiento 2: ¿Cuánto debería durar el TTL de la concesión?
Comience a partir del p99 de actualización medido, el tiempo de espera de red y las pausas de planificación; luego agregue margen y observe con qué frecuencia el trabajo sobrevive a la concesión. El p95 de 800 ms es insuficiente por sí solo.
Pregunta de seguimiento 3: ¿Qué sucede si los datos de origen cambian durante la actualización?
Lea y transporte una versión de origen, luego actualice la caché condicionalmente. Una versión más nueva instalada por un evento de cambio no debe ser reemplazada por una actualización retrasada.
Pregunta de seguimiento 4: ¿Qué ocurre si todo el clúster de caché no está disponible?
Sirva valores aceptables de la near-cache, mantenga cada lectura necesaria al origen detrás del bulkhead global, degrada cuando no exista ninguna copia y precargue las claves a una tasa controlada durante la recuperación.
Pregunta de seguimiento 5: ¿Cómo encaja el almacenamiento en caché negativo?
Almacene en caché un resultado confirmado de "no encontrado" durante un TTL corto para evitar la penetración. Es independiente de la coalescencia de actualizaciones para una clave caliente existente.
Pregunta de seguimiento 6: ¿Cuándo es útil la actualización probabilística temprana?
Para datos recalculables, tolerantes a la obsolescencia y de alta tasa de solicitudes. La probabilidad debe depender de la frescura restante y del tráfico observado, manteniendo el control de fallas de actualización y presupuestos de origen.
Pregunta de seguimiento 7: ¿Deben las regiones compartir un solo bloqueo?
Por lo general, no. Actualice regionalmente y asigne presupuestos al origen. La coordinación central se justifica solo cuando se requiere una única actualización global y la latencia interregional y las particiones son tolerables.
Pregunta de seguimiento 8: ¿Cómo demuestra que el diseño funciona?
Aplique una carga de 50.000 solicitudes por segundo durante la expiración suave y dura mientras inyecta caídas, pausas, fallas de caché y carreras de versiones. Verifique una actualización normal por clave, una carga de origen dentro del presupuesto, una obsolescencia de 30 segundos como máximo y ninguna reversión de versión.