Tema representativo de entrevista

Entrevista de diseño de sistemas: Manejo de cachés desactualizadas de controladores de Kubernetes

Diseño de sistemasDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Un controlador de Kubernetes puede actuar sobre datos de caché desactualizados tras una sobrecarga o un reinicio. Diseñe la detección, el comportamiento de omisión y reencolamiento (skip-and-requeue), y la observabilidad sin bloquear todo el controlador.

Planteamiento y contexto

Un controlador de Kubernetes lee objetos de una caché de Informer y escribe el estado deseado en el servidor de la API. Bajo carga, retraso de watch o reinicio, la caché puede quedar rezagada respecto al servidor de la API; el controlador puede repetir escrituras, escalar incorrectamente o tratar un Lease antiguo como expirado. Diseñe la detección y mitigación de desactualización que bloquee únicamente los objetos afectados.

Esto es relevante para roles de ingeniería de plataformas, SRE y controladores nativos de la nube. Kubernetes v1.36 y KEP 5647 describen AtomicFIFO, LastStoreSyncResourceVersion(), el seguimiento de la versión de recurso de las escrituras, la omisión y el reencolamiento de claves desactualizadas, y la incorporación de controladores de DaemonSet, StatefulSet, ReplicaSet y Job. Este es un ejercicio de diseño de fuentes públicas, no una afirmación sobre el banco de entrevistas de ninguna empresa.

Qué evalúa el entrevistador

El entrevistador desea ver si comprende los límites de una caché eventualmente consistente y si puede convertir el concepto de "suficientemente reciente" en una condición verificable de versión de recurso. Una respuesta sólida abarca la sincronización de caché, read-after-write, colas por clave, retroceso (backoff), reinicios, monitoreo y feature gates. Una respuesta débil hace que cada llamada de reconciliación invoque directamente al servidor de la API e ignora el costo de carga y consistencia.

Preguntas de aclaración

  • ¿Qué decisiones son destructivas: eliminar Pods, reducir escala (scale down), cambios de líder o actualizaciones de estado ordinarias?
  • ¿Qué ventana de desactualización es aceptable y puede una operación crítica permitirse una lectura del servidor de la API?
  • ¿Necesitamos read-after-write para un solo objeto u ordenamiento causal a través de varios objetos?
  • Tras un reinicio, reconexión de watch o falla del servidor de la API, ¿debería el controlador esperar de forma conservadora o permitir una degradación acotada?

Una respuesta de 30 segundos

“Mantendría la caché del Informer como la ruta de lectura predeterminada y usaría AtomicFIFO junto con la última versión de recurso observada para medir el progreso. Tras escribir un objeto crítico, el controlador registra su versión objetivo; hasta que la caché se ponga al día, omite únicamente esa clave y la reencola con retroceso exponencial. Las acciones destructivas añaden una lectura directa acotada o un disyuntor. Las métricas exponen el retraso de la caché, las reconciliaciones omitidas y la antigüedad de la cola. Al reiniciar, se mantiene la protección, se completa la sincronización de la caché y luego se reanuda.”

Solución paso a paso

Defina la desactualización primero. Un Store de Informer se alimenta de eventos de watch que pueden retrasarse, reordenarse o estar temporalmente incompletos mientras se reconstruye la caché. AtomicFIFO de Kubernetes v1.36 hace que el lote inicial de listado sea atómico respecto a los eventos incrementales, evitando una caché inconsistente causada por intercalarlos. LastStoreSyncResourceVersion() expone la última versión observada por el Store.

Para cada escritura crítica, mantenga objectKey -> resourceVersion. Cuando un DaemonSet actualiza un Pod, registre la versión devuelta por el servidor de la API. Cada evento del informer del Pod avanza la versión más alta observada. El DaemonSet puede ejecutar la siguiente reconciliación que depende del nuevo estado solo cuando la versión observada alcanza la última escritura. De lo contrario, reencole esa clave con el retroceso exponencial normal; nunca detenga todo el grupo de workers.

Las escrituras y comprobaciones deben ser idempotentes. Utilice reintentos por conflicto de versión de recurso y asegúrese de que una reconciliación repetida no tenga efectos secundarios adicionales. Un reinicio pierde las asignaciones en memoria, por lo que el inicio espera la sincronización de la caché del informer antes de reconstruir el estado protegido a partir del estado del objeto o de eventos de cola. Si la asignación está incompleta, retrase el trabajo destructivo en lugar de asumir que los datos son recientes.

Para decisiones sensibles al tiempo, agregue un disyuntor. Use la caché para una ruta rápida; cuando la versión objetivo sea desconocida o el retraso supere un umbral, realice una lectura directa acotada desde el servidor de la API. Si falla, pause únicamente esa clave y registre el motivo. Hacer de las lecturas directas la opción predeterminada amplificaría el QPS del servidor de la API, la latencia y el radio de impacto de fallos.

Exponga la versión de recurso del informer, la versión de escritura objetivo, la duración del retraso, el recuento de reconciliaciones omitidas, la antigüedad de la cola, la tasa de éxito de lecturas directas y el recuento de activaciones del disyuntor por controlador. Los registros incluyen la clave del objeto, las versiones y la acción, nunca valores de Secret. Las alertas distinguen la lentitud del servidor de la API, watches rotos, una clave caliente (hot key) y el procesamiento lento del controlador.

Lance la funcionalidad detrás de un feature gate y un canary. Habilítela para un controlador de alta contienda y un grupo pequeño de nodos, luego verifique la convergencia tras las omisiones, la recuperación tras reinicios y la ausencia de inanición en la cola. Expándala solo después de que la carga del servidor de la API sea aceptable. Revierta deshabilitando la nueva ruta de consistencia mientras conserva las métricas y el estado del objeto; no elimine de forma masiva las asignaciones de protección.

Respuesta modelo

Implementaría cuatro capas: lecturas priorizando la caché, una compuerta por versión de recurso, reencolamiento por clave y un disyuntor para acciones críticas. AtomicFIFO mantiene el procesamiento del listado inicial consistente con los eventos incrementales, y el Store reporta su última versión de recurso observada. Tras una escritura, el controlador registra la versión objetivo; procesa esa clave solo después de que la caché la alcanza; de lo contrario, la reencola con retroceso.

Las eliminaciones, reducciones de escala y decisiones de Lease utilizan una lectura directa acotada cuando la frescura de la caché es desconocida o supera el umbral; un fallo pausa esa clave. Las métricas cubren el retraso de versión, el trabajo omitido, la antigüedad de la cola y la proporción de lecturas directas. El reinicio espera la sincronización de la caché antes de restaurar las asignaciones. El despliegue canary valida la convergencia, la recuperación y la carga del servidor de la API; el estado de protección nunca se borra globalmente.

Errores comunes

  • Error → realizar lectura directa del servidor de la API en cada reconciliación; Por qué falla → aumentan el QPS y la latencia, y la caché pierde su propósito; Solución → realizar lectura directa solo para acciones críticas o versiones desconocidas.
  • Error → pausar a todos los workers cuando un objeto está desactualizado; Por qué falla → una sola clave caliente genera una interrupción global; Solución → omitir y reencolar por clave de objeto.
  • Error → comparar solo marcas de tiempo locales; Por qué falla → el desfase de reloj no puede probar la causalidad de los watches; Solución → comparar las versiones de recurso de la API.
  • Error → borrar toda la protección tras un reinicio; Por qué falla → pueden ejecutarse decisiones mientras se reconstruyen las cachés; Solución → esperar a la sincronización y restaurar el estado con comprobaciones de versión.

Preguntas de seguimiento

¿Por qué es más segura una versión de recurso que una marca de tiempo local?

Proviene de la secuencia de cambios de objetos del servidor de la API y puede demostrar que la caché ha observado una escritura particular. La hora local se ve afectada por el desfase de reloj, el retraso de la red y las pausas del proceso. Una versión de recurso no es una secuencia transaccional entre múltiples objetos, por lo que la consistencia multiobjeto aún requiere un diseño explícito.

¿Cómo se evita la inanición cuando una clave se mantiene desactualizada?

Limite el retroceso exponencial y genere alertas ante el tiempo de espera máximo mientras permite que otras claves se ejecuten. Al superar el umbral, cambie a un sondeo de baja frecuencia o a una lectura directa. Cuente las omisiones consecutivas para que los reintentos rápidos no sobrecarguen el servidor de la API.

¿Cuándo debe rechazarse una operación?

Para decisiones de eliminación, reducción de escala, conmutación por error (failover) o expiración de Lease, pause la clave y mantenga la protección cuando se desconozca la frescura y falle la lectura directa. El reporte de estado ordinario puede continuar dentro de una ventana de desactualización explícita, pero debe marcar el estado degradado en el status y las métricas.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Resolver para una respuesta de diseño de sistemas

Aclara primero los requisitos y luego avanza a través de la escala, la arquitectura, la elección de componentes y las compensaciones (trade-offs).

Ver la herramienta