Planteamiento y contexto
Asuma que cada trabajo invoca unas 25 acciones y que la salida nueva promedio es de 300 MB. Las compilaciones repetidas a menudo comparten entradas, pero un acierto incorrecto puede entregar un artefacto compilado con la cadena de herramientas (toolchain) incorrecta o con un secreto expuesto. La caché es una optimización: un fallo de caché o una interrupción debe recurrir a la ejecución, mientras que un acierto falso constituye un incidente de corrección. Debe separar la búsqueda de resultados de acciones del almacenamiento de artefactos inmutables y explicar cómo la ejecución remota modifica el modelo de confianza y capacidad.
Qué evalúa el entrevistador
- Si modela una clave de acción a partir de cada entrada que afecte un resultado determinista.
- Si separa los metadatos de acción mutables de un almacén direccionado por contenido (CAS) inmutable.
- Si hace que las escrituras sean atómicas, verifica los resúmenes (digests) y previene el envenenamiento de la caché entre inquilinos.
- Si la capacidad, la recolección de elementos no utilizados, la observabilidad, la degradación ante interrupciones y la migración son explícitas.
- Si distingue una caché de compilación de una caché genérica de clave-valor: la corrección supera a la tolerancia a lecturas obsoletas.
Preguntas de aclaración para hacer
Pregunte si las compilaciones son herméticas, qué sistemas operativos y arquitecturas se admiten y si la ejecución remota es obligatoria u opcional. Aclare el objetivo de retención, los límites de inquilinos y repositorios, el tamaño máximo de artefacto, el objetivo de tasa de aciertos esperada, la residencia de datos y si pueden entrar secretos o código fuente propietario en una salida. Confirme si un resultado puede compartirse entre ramas, versiones de toolchains o solo dentro de una tupla de commit y plataforma. Pregunte también si CI puede escribir mientras las máquinas de los desarrolladores son de solo lectura.
Estructura de respuesta en 30 segundos
Generaría un hash de una descripción canónica de la acción que contenga el comando, la identidad del toolchain y de la plataforma, la raíz de Merkle de las entradas declaradas, el entorno relevante y la configuración de compilación. La caché de acciones mapea esa clave a los digests de salida y metadatos del resultado; el CAS almacena blobs inmutables por digest. Los lectores verifican los metadatos y cada blob antes de materializarlos. Una ejecución local o remota exitosa sube primero los blobs y publica el resultado de la acción al final, evitando que el trabajo parcial se convierta en un acierto. Los espacios de nombres, las escrituras autenticadas, las cuotas y el sandboxing evitan fugas entre inquilinos. Los fallos de la caché devuelven misses y recurren a una ejecución controlada, mientras que las métricas y las recompilaciones limpias por muestreo detectan aciertos falsos.
Análisis detallado paso a paso
1. Definir claves y límites de almacenamiento
Canonice el comando de la acción, las versiones del compilador y enlazador, la plataforma, las entradas declaradas, los flags relevantes, el entorno permitido en lista blanca y los archivos de bloqueo (lockfiles) de dependencias externas. Genere un hash del árbol de entrada como una raíz de Merkle. Excluya secretos y marcas de tiempo no deterministas; si una acción no es hermética, márquela como no almacenable en caché o asígnele un alcance deliberadamente estrecho. Almacene los resultados de las acciones de forma separada del CAS: el resultado contiene nombres de archivos de salida, digests, tamaños, código de salida y digests opcionales de stdout/stderr. Los objetos del CAS son inmutables y se direccionan únicamente por su digest.
2. Diseñar rutas de acierto, fallo y publicación
En una lectura, enrute el espacio de nombres del inquilino y la clave de acción a un servicio de metadatos replicado, obtenga el resultado y luego descargue en paralelo los blobs del CAS faltantes. Verifique el tamaño y el digest antes de exponer los archivos. En caso de miss, ejecute localmente o en un worker en sandbox. Suba los blobs verificados con operaciones de digest idempotentes, y luego confirme el resultado de la acción en una única publicación condicional. Varios escritores concurrentes pueden subir el mismo blob, pero solo un resultado completo con todos los blobs referenciados será visible. Un blob corrupto o una discrepancia de metadatos equivale a un miss y genera una alerta, nunca un acierto exitoso.
3. Añadir distribución, aislamiento y seguridad
Utilice hashing consistente o un mapa de partición del servicio de metadatos para las búsquedas de resultados de acciones, con réplicas a través de zonas de falla. Aloje los datos grandes del CAS en un almacenamiento de objetos o en un nivel de blobs particionado (sharded) y mantenga los metadatos calientes en almacenamiento de baja latencia. Autentique cada solicitud, autorice los espacios de nombres de repositorios e inquilinos, y configure las máquinas de desarrolladores como de solo lectura por defecto. Cifre en tránsito y en reposo, aplique cuotas por inquilino y ejecute las acciones remotas en sandboxes. No desduplique entre inquilinos a menos que una política lo permita explícitamente; un digest por sí solo no debe eludir la autorización.
4. Planificar capacidad y gestión del ciclo de vida
La carga de trabajo indicada es de 20,000 trabajos/día × 25 acciones = 500,000 búsquedas/día, aproximadamente 5.8 solicitudes/segundo en promedio. Un pico de 20× representa aproximadamente 120 solicitudes/segundo, antes de las lecturas de blobs en paralelo. Si el 10% de las acciones crea una nueva salida de 300 MB, el ingreso sin comprimir es de 1.5 TB/día; la compresión y la desduplicación reducen el almacenamiento, pero el diseño debe reservar capacidad de objetos de varios terabytes y margen de ancho de banda. La recolección de elementos no utilizados comienza desde los resultados de acciones y manifiestos activos, sigue sus referencias de digests, aplica un período de gracia y luego combina cuotas de tamaño con políticas de LRU o antigüedad. Nunca elimine un blob alcanzable y aplique cuotas justas por inquilino.
5. Hacer que las interrupciones y la corrección sean observables
Trate el tiempo de espera de la caché, los fallos de permisos, los blobs faltantes, las discrepancias de digest y la no disponibilidad del backend como resultados distintos. Un miss puede ejecutarse; una interrupción prolongada requiere control de admisión, preferencia por la caché local y reintentos limitados para evitar transformar CI en una tormenta de reintentos. Rastree la tasa de aciertos por repositorio, clase de acción, plataforma y toolchain; la latencia de búsqueda, el ancho de banda de blobs, las cancelaciones de subidas, las expulsiones, la corrupción y las denegaciones por inquilino. Periódicamente realice una recompilación limpia eliminando las cachés locales y compare los registros de ejecución o los digests de salida para detectar no determinismo y aciertos falsos.
Ejemplo de una respuesta sólida
Expondría dos servicios: un índice autenticado de caché de acciones y un CAS inmutable. Una clave de acción cubre el comando canónico, la identidad del compilador y la plataforma, la raíz de Merkle de las entradas declaradas, el entorno en lista blanca, los flags y las dependencias externas bloqueadas. Un acierto devuelve digests de salida; los clientes verifican y descargan esos blobs antes de materializarlos. Un miss ejecuta en un sandbox, sube los blobs de forma idempotente, los verifica y publica el resultado solo después de que existan todas las referencias. Los metadatos de las acciones se replican por inquilino y repositorio, mientras que los datos del CAS se particionan o se colocan en almacenamiento de objetos a través de zonas. Las lecturas pueden degradar a ejecución durante fallos de la caché; las escrituras se restringen a CI de confianza, con cuotas, cifrado y sin reutilización entre inquilinos por defecto. Dimensionaría para un pico de unas 120 búsquedas/segundo y almacenamiento de varios terabytes, y luego validaría la tasa de aciertos, discrepancias de digest, acciones no deterministas, escritores concurrentes, actualizaciones de toolchains, alcanzabilidad de GC y recuperación ante interrupciones.
Errores comunes
- Aplicar hash solo a los archivos fuente omitiendo el compilador, los flags, la plataforma, el entorno o las dependencias bloqueadas.
- Tratar un resultado de acción y sus blobs de salida como un único registro mutable, permitiendo publicaciones parciales.
- Compartir un digest entre inquilinos sin verificar la autorización del espacio de nombres.
- Diseñar la no disponibilidad de la caché como una interrupción total de compilación en lugar de un miss controlado con degradación alternativa.
- Usar un único LRU global y eliminar blobs sin rastrear referencias desde resultados de acciones activos.
- Tomar el volumen de stdout o stderr como métrica de acierto de caché; se necesitan la estrategia de ejecución y contadores explícitos de aciertos.
- Afirmar una alta tasa de aciertos sin probar compilaciones limpias, reproducibilidad entre máquinas y acciones no deterministas.
Preguntas de seguimiento y respuestas
¿Qué ocurre si se actualiza el toolchain del compilador pero la línea de comandos permanece sin cambios?
La identidad del toolchain debe formar parte de la clave de acción, generalmente mediante un digest fijado o una imagen de ejecución con versiones. Una migración puede realizar lecturas duales de espacios de nombres antiguos para rollback, pero debe escribir en el nuevo espacio de nombres y medir los misses. Nunca reutilice salidas antiguas solo porque el código fuente y los flags coincidan.
¿Cómo se recupera el sistema tras un envenenamiento de la caché?
Detenga las escrituras no confiables, ponga en cuarentena el espacio de nombres afectado e identifique los resultados de acciones incorrectos y los blobs alcanzables mediante registros de auditoría. Invalide el índice de acciones, recompile salidas confiables y vuelva a poblar a partir de ejecuciones verificadas. Mantenga el uso de la caché como opcional durante la recuperación y preserve la evidencia para la revisión del incidente.
¿Qué sucede si una acción de compilación descarga una dependencia no fijada durante la ejecución?
Es no hermética: márquela como no almacenable en caché hasta que la dependencia esté fijada y sus bytes descargados estén representados en la clausura de entrada. Un TTL corto con alcance de repositorio puede ser una excepción explícita de emergencia, pero no debe presentarse como una reutilización determinista.
¿Cómo migraría desde cachés locales sin una interrupción en CI?
Ejecute lecturas remotas en modo shadow, compare claves y digests de salida locales y remotos, y luego habilite aciertos remotos para un grupo pequeño de repositorios. Mantenga la ejecución local y la degradación a caché local, limite las escrituras a CI de confianza y expanda solo después de que se superen las comprobaciones de tasa de aciertos, latencia y aciertos falsos.