Cuando tienes una base de código grande, las cadenas de dependencias pueden volverse muy profundas. Incluso los objetos binarios simples pueden depender de decenas de miles de objetivos de compilación. En a esta escala, es imposible completar una construcción en una cantidad razonable de tiempo en una sola máquina: ningún sistema de compilación puede sortear los conceptos leyes de la física impuestas en el hardware de una máquina. La única forma de hacer que esto funcione es con un sistema de compilación que admite compilaciones distribuidas en las que las unidades de el trabajo que realiza el sistema se distribuyen en una arquitectura la cantidad de máquinas. Suponiendo que dividimos el trabajo del sistema en un tamaño lo suficientemente pequeño, unidades (más adelante hablaremos de esto), esto nos permitiría completar cualquier compilación de tan rápido como estemos dispuestos a pagar. Esta escalabilidad es lo santo grial hemos estado trabajando definiendo un sistema de compilación basado en artefactos.
Almacenamiento en caché remota
El tipo de compilación distribuida más simple es el que solo aprovecha las compilaciones remotas el almacenamiento en caché, como se muestra en la Figura 1.
Figura 1. Una compilación distribuida que muestra el almacenamiento en caché remoto
Todos los sistemas que realizan compilaciones, incluidas las estaciones de trabajo para desarrolladores y sistemas de integración continua, comparte una referencia a una caché remota común servicio. Este servicio puede ser un sistema de almacenamiento rápido y local a corto plazo, como Redis o un servicio en la nube como Google Cloud Storage. Cada vez que un usuario necesite compilar un artefacto, ya sea directamente o como una dependencia, el sistema primero comprueba con la caché remota para ver si ese artefacto ya existe. Si es así, puedes descargar el artefacto en lugar de compilarlo. De lo contrario, el sistema crea el artefacto y subirá el resultado de nuevo a la caché. Esto significa que Las dependencias de bajo nivel que no cambian con mucha frecuencia se pueden compilar una vez y compartir entre los usuarios en lugar de tener que reconstruirlos. En Google, muchos los artefactos se entregan desde una caché en lugar de compilarse desde cero, lo que reduce el costo de ejecutar nuestro sistema de compilación.
Para que funcione un sistema de almacenamiento en caché remoto, el sistema de compilación debe garantizar que se compilen sean completamente reproducibles. Es decir, para cualquier destino de compilación, debe ser posible para determinar el conjunto de entradas a ese objetivo, de modo que el mismo conjunto de entradas producirá exactamente el mismo resultado en cualquier máquina. Esta es la única forma de asegurarte de que los resultados de la descarga de un artefacto sean los mismos que de construirlo uno mismo. Ten en cuenta que esto requiere que cada artefacto de la caché en su objetivo y en un hash de sus entradas; de esa manera, los ingenieros podrían realizar distintas modificaciones en el mismo objetivo sin modificar tiempo, y la caché remota almacenaría todos los artefactos resultantes y serviría de forma adecuada sin que haya conflictos.
Por supuesto, para que exista algún beneficio de la caché remota, descargar una el artefacto debe ser más rápido que compilarlo. No siempre es así, sobre todo si el servidor de caché está lejos de la máquina que realiza la compilación. Principios de la IA la red y el sistema de compilación están cuidadosamente ajustados para poder compartir rápidamente las resultados.
Ejecución remota
El almacenamiento en caché remoto no es una compilación distribuida verdaderamente. Si se pierde la caché o si hacer un cambio de bajo nivel que requiera que todo se reconstruya, de todas formas, para realizar la compilación de forma local en tu máquina. El verdadero objetivo es respaldar ejecución remota, en la que el trabajo real de compilación se puede distribuir en cualquier cantidad de trabajadores. En la figura 2, se muestra un sistema de ejecución remota.
Figura 2. Un sistema de ejecución remota
La herramienta de compilación que se ejecuta en la máquina de cada usuario (en la que los usuarios son humanos (ingenieros o sistemas de compilación automatizados) envía solicitudes a una instancia principal de compilación central. La instancia principal de compilación divide las solicitudes en los programas y las acciones de los componentes la ejecución de esas acciones en un grupo escalable de trabajadores. Cada trabajador realiza las acciones solicitadas con las entradas especificadas por el usuario y escribe los artefactos resultantes. Estos artefactos se comparten que ejecutan acciones que las requieren hasta que el resultado final se pueda producidos y enviados al usuario.
La parte más complicada de implementar este sistema es administrar la comunicación entre los trabajadores, la instancia principal y la máquina local del usuario. Los trabajadores pueden depender de artefactos intermedios producidos por otros trabajadores y del resultado final se debe devolver a la máquina local del usuario. Para ello, podemos desarrollarnos superior de la caché distribuida descrita anteriormente solicitando a cada trabajador que escriba sus resultados y leer sus dependencias desde la caché. Los bloques maestro que los trabajadores no continúen hasta que haya finalizado todo lo que dependen, en caso de que pueda leer sus entradas desde la caché. El producto final es también almacenados en caché, lo que permite que la máquina local los descargue. Ten en cuenta que también necesitamos medios independientes para exportar los cambios locales en el árbol de fuentes del usuario, de modo que los trabajadores pueden aplicar esos cambios antes de compilar.
Para que esto funcione, todas las partes de los sistemas de compilación basados en artefactos descritos necesitan confluir. Los entornos de compilación deben estar completamente autodescriptivo para que podamos iniciar trabajadores sin intervención humana. Compilación procesos en sí mismos deben ser completamente independientes, ya que cada paso puede ejecutarse en una máquina diferente. Los resultados deben ser completamente deterministas, por lo que para que cada trabajador pueda confiar en los resultados que recibe de otros trabajadores. Tales de seguridad son extremadamente difíciles de proporcionar para un sistema basado en tareas hace casi imposible crear un sistema de ejecución remota confiable uno.
Compilaciones distribuidas en Google
Desde 2008, Google utiliza un sistema de compilación distribuido que emplea el almacenamiento en caché y la ejecución remota, que se ilustran en la Figura 3
Figura 3. Sistema de compilación distribuido de Google
La caché remota de Google se llama ObjFS. Consiste en un backend que almacena compilar resultados en Bigtables distribuidos en toda nuestra flota de producción y un daemon FUSE de frontend llamado objfsd que se ejecuta en la interfaz máquina. El daemon del FUSE permite a los ingenieros explorar los resultados de la compilación como si eran archivos normales almacenados en la estación de trabajo, pero con el contenido descargados a pedido solo para algunos archivos que solicita directamente el usuario. La entrega de contenido de archivos a pedido reduce en gran medida la red y el disco y el sistema es capaz de compilar el doble de rápido en comparación con cuando lo almacenábamos y todas las salidas de compilación en el disco local del desarrollador.
El sistema de ejecución remota de Google se llama Forge. Un cliente de Forge en Blaze (equivalente interno de Bazel) llamado el distribuidor envía solicitudes para cada acción a un trabajo que se ejecuta en nuestra centros de datos llamados el programador. El programador mantiene una caché de acciones. resultados, lo que le permite devolver una respuesta de inmediato si la acción ya creado por cualquier otro usuario del sistema. Si no, ubica la acción en una fila. Un gran grupo de trabajos del Ejecutor lee continuamente acciones de esta cola, ejecutarlas y almacenar los resultados directamente en Bigtable de ObjFS. Estos los resultados están disponibles para que los ejecutores realicen acciones futuras o se descarguen por el usuario final a través de objfsd.
El resultado final es un sistema que escala para admitir de manera eficiente todas las compilaciones que se llevan a cabo en Google. Y la escala de las construcciones de Google es realmente masiva: Google Se ejecutan millones de compilaciones que ejecutan millones de casos de prueba y producen petabytes. de resultados de compilaciones de miles de millones de líneas de código fuente todos los días. No solo permite que nuestros ingenieros compilen bases de código complejas rápidamente. También permite para implementar una gran cantidad de herramientas y sistemas automatizados que dependen compilar.