A principios de este año, nos embarcamos en el mayor proyecto de nuestra empresa.
Naturalmente, elegimos Timescale, nuestra plataforma en la nube madura con TimescaleDB como núcleo. Estamos acostumbrados a trabajar con PostgreSQL y creamos TimescaleDB para hacer que PostgreSQL sea más rápido y escalable. ¿Qué es mejor que vivir según nuestro propio ejemplo?
La forma más sencilla de describir este experimento de dogfooding es con números que ayudan a cuantificar su escala. Para crear Insights, necesitábamos recopilar información de consultas en toda nuestra flota de bases de datos de producción en funcionamiento continuo. Rápidamente recopilamos más de 1 billón de registros sobre consultas individuales (desinfectadas) en la plataforma.
Ahora que Insights está en producción, estamos ingiriendo más de 10 mil millones de registros nuevos por día . El conjunto de datos atendido por un único servicio Timescale crece aproximadamente 3 TB por día y actualmente suma más de 350 TB , y el mismo servicio de base de datos impulsa los paneles de control en tiempo real de todos nuestros clientes.
Esta publicación de blog ofrece un vistazo entre bastidores al proceso de creación de Insights. Operar a esta escala significaba superar los límites de un único servicio Timescale y escalar no solo PostgreSQL sino también nuestra empatía con los desarrolladores. Descubrimos que Timescale está más que a la altura de la tarea, ¡pero también hay áreas que queremos mejorar!
Para que Insights suceda, tuvimos que ponernos nuestro sombrero de administrador de bases de datos 🤠 y abordar algunos desafíos técnicos para escalar PostgreSQL a muchos terabytes de datos. Queríamos utilizar un servicio Timescale como nuestra base de datos central, alojado en nuestra plataforma sin infraestructura "especial". Esto significó lo siguiente:
Tuvimos que construir una canalización capaz de incorporar miles de millones de registros por día en un único servicio Timescale. Timescale puede manejar altas tasas de ingesta y lo hace regularmente para nuestros clientes, pero este nivel de escala bajo cargas de consultas de producción siempre sorprende.
Nuestros clientes tenían que poder consultar esta base de datos con la flexibilidad necesaria para potenciar todos los análisis que ofrece Insights, ¡y no queríamos hacerlos esperar minutos para recibir una respuesta!
Necesitábamos almacenar cientos de TB en un único servicio Timescale ya que cada día agregamos varios TB. Los datos más antiguos (es decir, de más de unas pocas semanas) debían ser accesibles, pero no necesariamente rápidos de consultar.
Desde el punto de vista de la recopilación de datos , aprovechamos la arquitectura de la plataforma Timescale. Timescale se ejecuta en Kubernetes (k8s) y tenemos varios clústeres de k8s ejecutándose en diferentes regiones geográficas. Esos clústeres tienen nodos que contienen uno o más servicios de bases de datos de clientes. Para recopilar las ejecuciones de consultas para todas esas bases de datos, pasamos de esa base de datos al nivel regional y luego tenemos un escritor regional que almacena lotes de registros en el servicio de base de datos Timescale que impulsa Insights.
Perdón por la agitación de las manos que evita algunos detalles sangrientos de bajo nivel, pero en términos generales, así es como funcionan las cosas: cada base de datos que se ejecuta en la flota está instrumentada para crear un registro (desinfectado para privacidad y seguridad) después de cada consulta, incluido el consulta en sí y las estadísticas que nos interesan.
Esos registros se recopilan a nivel de nodo, se etiquetan con etiquetas para mantenerlos asociados con el servicio de base de datos del que provienen y se agrupan para enviarlos al escritor regional. El servicio de escritura regional se replica según sea necesario para manejar la carga en cada región. Cada escritor recopila lotes de los nodos de cada clúster y crea lotes aún más grandes.
Luego, esos lotes grandes se escriben primero usando "COPIAR" en una tabla temporal (sin registro de escritura anticipada = rápido). Las entradas de esa tabla temporal se utilizan luego para actualizar las tablas necesarias (ver más abajo). La tabla temporal nos permite usar "COPIAR" sin preocuparnos por los duplicados, que se manejan mediante operaciones posteriores que eliminan los registros de la tabla temporal.
Resumiendo:
Acerquémonos a la base de datos que impulsa Insights. Estamos ejecutando Insights en un servicio de escala de tiempo "disponible en el mercado" con una
La base de datos que impulsa Insights tiene bastantes partes, pero intentaremos resaltar las más importantes.
Primero, tenemos dos tablas PostgreSQL normales que sirven como "tablas de referencia". Estas tablas contienen metadatos de bases de datos de información y metadatos de cadenas de consulta. Aquí están sus (pseudo)esquemas:
Metadatos de la base de datos
Table "insights.cloud_db" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- id | bigint | | not null | nextval('cloud_db_id_seq'::regclass) service_id | text | | not null | project_id | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "cloud_db_pkey" PRIMARY KEY, btree (id) "cloud_db_project_id_service_id_key" UNIQUE CONSTRAINT, btree (project_id, service_id)
Metadatos de consulta
Table "insights.queries" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- hash | text | | not null | normalized_query | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "queries_pkey" PRIMARY KEY, btree (hash)
Cada vez que se comiencen a ejecutar consultas en una nueva base de datos, se agregará a `insights.cloud_db`. Cada vez que se ejecuta una nueva consulta normalizada, se agregará a `insights.queries`.
(¿Qué es una consulta normalizada? Es una consulta donde todas las constantes han sido reemplazadas con marcadores de posición: $1 para la primera, $2 para la segunda, y así sucesivamente, por lo que solo vemos la “forma” de la consulta, no sus valores. .)
Hasta este punto, solo estamos usando Postgres normal sin salsa secreta de escala de tiempo. Pero los otros objetos importantes en la base de datos son exclusivos de TimescaleDB, lo que ayuda a escalar PostgreSQL a otro nivel. Aquí es donde ocurre la magia: hipertablas y agregados continuos.
Las hipertablas son tablas particionadas automáticamente de Timescale. Dividen automáticamente los datos por una dimensión mientras se ingieren, lo que hace que sea mucho más fácil escalar tablas PostgreSQL a escalas grandes. Las hipertablas son los componentes básicos de Timescale. Estamos almacenando nuestras métricas de estadísticas de consulta en una hipertabla enorme, como veremos más adelante.
Los agregados continuos son la versión mejorada de Timescale de las vistas materializadas de PostgreSQL, que permiten la materialización incremental y automática , lo que resultó muy útil al crear Insights.
Cubramos cómo utilizamos estas funciones para permitir consultas analíticas rápidas por parte de los usuarios.
Como decíamos, utilizamos una gran hipertabla para almacenar información sobre cada ejecución de consulta. Esta hipertabla es nuestra tabla principal, donde se encuentran las métricas sin procesar desinfectadas. Se parece algo a lo siguiente y está configurado para usar su columna de marca de tiempo ( created
) para particionar automáticamente los datos a medida que se ingieren.
Table "insights.records" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time | bigint | | | rows | bigint | | | ...
Hemos omitido un montón de estadísticas para este ejemplo, pero ya entiendes la idea.
Ahora tenemos que permitir consultas rápidas desde el lado del usuario, pero esta tabla es enorme. Para acelerar las cosas, confiamos en gran medida en agregados continuos (usando
Los agregados continuos tienen mucho sentido en un producto que ofrece análisis en tiempo real de cara al usuario como Insights. Para proporcionar información procesable a los usuarios, necesitamos agregar métricas: no mostramos a los usuarios un registro de cada consulta que ejecutaron con estadísticas al lado; algunas bases de datos realizan miles de consultas por segundo, por lo que sería una pesadilla encontrarlas. cualquier cosa útil. En lugar de eso, brindamos servicios agregados a los usuarios.
Por lo tanto, también podríamos aprovechar el hecho de que no mostramos registros individuales sin procesar a los usuarios y conservar el resultado.
Podríamos haber utilizado vistas materializadas de PostgreSQL, pero los agregados continuos de Timescale tienen varias ventajas que nos resultaron especialmente útiles. Actualizamos mucho las vistas y los agregados continuos tienen políticas integradas para actualizaciones automáticas y se actualizan de forma incremental.
Actualizamos las vistas cada cinco minutos, por lo que en lugar de volver a generar toda la información materializada cada cinco minutos, los agregados continuos actualizan incrementalmente la vista mediante el seguimiento de los cambios en la tabla original. A la escala en la que estamos operando, simplemente no podemos darnos el lujo de escanear nuestra hipertabla principal de arriba a abajo cada cinco minutos, por lo que esta funcionalidad de agregados continuos fue un "desbloqueo" fundamental para nosotros.
En estos agregados continuos que impulsan Insights detrás de escena, también agregamos la mayoría de las estadísticas interesantes en un
Aún así, en cierto momento, la base de datos comenzó a trabajar mucho para insertar todos estos registros sin procesar y luego materializarlos para su publicación. Estábamos alcanzando algunas limitaciones sobre la cantidad que podíamos ingerir y mantener el ritmo.
Para aumentar aún más nuestra tasa de ingesta al nivel que necesitábamos, descargamos la generación de UDDSketch de la base de datos a los escritores de la región. Ahora, todavía almacenamos una cierta cantidad de registros como registros "sin procesar", pero también insertamos el resto en bocetos pregenerados que almacenamos en la base de datos:
Table "insights.sketches" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time_dist | uddsketch | | | rows_dist | uddsketch | | | ...
La mejor parte de UDDSketchs es que es muy fácil "enrollar" continuamente los bocetos para admitir rangos de tiempo más amplios. Al utilizar un resumen de este tipo, los bocetos que cubren intervalos de tiempo más estrechos se pueden agregar en un boceto que cubre un rango de tiempo amplio, tanto al crear un agregado continuo jerárquico como en el momento de la consulta.
Otra herramienta que aprovechamos para garantizar que tanto la ingesta rápida como las consultas sean réplicas de lectura. En nuestro caso, el uso de la replicación es fundamental tanto para la alta disponibilidad como para el rendimiento, dado que Insights impulsa una característica importante orientada al cliente para la plataforma Timescale.
Nuestra instancia de base de datos principal está bastante ocupada con trabajo masivo, escritura de datos, materialización de agregados continuos, ejecución de compresión y más. (Más información sobre la compresión en un minuto). Para aliviar parte de su carga, permitimos que el cliente del servicio de réplica lea las solicitudes desde la consola de Insights.
Por último, necesitábamos incluir cómodamente cientos de TB en un único servicio Timescale. La base de datos de Insights está creciendo rápidamente: cuando comenzamos tenía alrededor de 100 TB y ahora supera los 350 TB (y contando).
Para almacenar esa cantidad de datos de manera eficiente, habilitamos
Estamos presenciando tasas de compresión de más de 20 veces en nuestra hipermesa principal.
Otra gran ventaja al gestionar una hipertabla muy grande fue la mutabilidad del esquema de los datos comprimidos. Describimos nuestro esquema aproximado en una sección anterior, pero como puede imaginar, lo cambiamos con frecuencia para agregar más estadísticas, etc.; es muy útil poder hacer esto directamente en la hipertabla comprimida.
También somos grandes usuarios de la clasificación de datos por niveles de Timescale. Esta función entró en acceso temprano a principios de este año (espere las noticias de GA pronto 🔥) y nos permite mantener cientos de TB accesibles a través de nuestra base de datos Timescale. La organización de datos en niveles también ha demostrado ser muy eficiente: aquí también vemos tasas de compresión sorprendentes, con 130 TB reduciéndose a 5 TB, altamente eficientes en recursos.
El proceso de creación de Insights nos mostró hasta dónde puede llegar nuestro producto, pero lo mejor fue caminar unos kilómetros en el lugar de nuestros clientes. Aprendimos mucho sobre la experiencia del usuario al escalar PostgreSQL con Timescale y agregamos algunas cosas a nuestra lista de tareas pendientes como ingenieros detrás del producto.
Repasemos todo: lo bueno y lo regular.
Perdón por nuestra inmodestia, pero a veces nos sentimos bastante orgullosos de nuestro producto. Ingerir decenas de miles de millones de registros diariamente en una única base de datos PostgreSQL que ya tiene cientos de TB no es nada despreciable . Pasamos un par de semanas ajustando la base de datos cuando comenzó a funcionar, pero ahora simplemente funciona , sin cuidados ni monitoreo constante. (Tenga en cuenta que esto es diferente a no estar monitoreado, ¡definitivamente está monitoreado!)
Nuestro
La compresión funcionó muy bien para nosotros. Como compartimos en la sección anterior, obtuvimos tasas de compresión impresionantes (¡20x!) usando una opción simple y única "segmentby". Para nosotros, la experiencia de establecer y ajustar la política no fue difícil, aunque, por supuesto, construimos esta característica... se podría decir que tenemos una ligera ventaja. 🙂 Además, la capacidad de agregar sin problemas nuevas columnas a datos comprimidos mejoró aún más la flexibilidad y adaptabilidad de nuestra base de datos. Utilizamos esta capacidad sin complicaciones.
Los agregados continuos simplificaron la lógica en la construcción de diferentes períodos de tiempo, simplificando el análisis y el procesamiento de datos. Usamos toneladas de agregados continuos jerárquicos.
Los algoritmos de aproximación incluidos en las hiperfunciones de Timecale simplificaron nuestra implementación y ampliaron enormemente nuestro análisis. La capacidad de acumular bocetos fácilmente también fue clave para admitir de manera eficiente diferentes rangos de tiempo y granularidades de períodos de tiempo en nuestros paneles de Insights orientados al cliente.
El almacenamiento cálido “infinito” que una base de datos Timescale tiene a su disposición a través de niveles de datos fue fundamental para escalar a cientos de TB, con mucho margen para crecer. Nuestra actual __ política de niveles de datos __ conserva tres semanas de registros en almacenamiento activo.
Finalmente, utilizamos la capacidad de crear trabajos personalizados para mejorar la observabilidad (como monitorear el historial de trabajos) e implementar estrategias de actualización experimentales.
Después de contarte todas las cosas buenas, es hora de reconocer las no tan buenas. Nada es perfecto, incluido Timescale. Enfrentamos algunos desafíos al implementar nuestro proyecto, y no los consideramos quejas:
La observabilidad de la base de datos podría mejorarse en la plataforma Timescale, particularmente en lo que respecta a los trabajos y el rendimiento de la materialización continua de agregados.
TimescaleDB proporciona principalmente vistas basadas en instantáneas, lo que dificulta comprender el rendimiento y las tendencias a lo largo del tiempo. Por ejemplo, no hay disponible una tabla de "historial de trabajos" lista para usar. Al principio, notamos que la materialización incremental de nuestros agregados continuos aparentemente tomaba cada vez más tiempo, lo que finalmente llevó al descubrimiento de un error, pero no teníamos forma de confirmar o cuantificar el alcance.
Como señalamos anteriormente, la capacidad de definir trabajos personalizados y ejecutarlos dentro del marco de trabajo de Timescale nos permitió crear una versión "suficientemente buena" de esto. Consultaríamos continuamente las vistas que queríamos monitorear a lo largo del tiempo e insertaríamos cualquier cambio en una hipertabla. Esto funciona para Insights por ahora, pero también estamos trabajando para convertir algunas de estas cosas en funcionalidades integradas porque creemos que son cruciales una vez que escalas Timescale más allá del punto en el que todo es rápido todo el tiempo. .
Los agregados continuos pueden ser difíciles de lograr cuando los datos subyacentes son grandes .
Usar la opción `__ SIN DATOS` al crear agregados continuos __ es un salvavidas. También es importante ser prudente con las compensaciones de la política de actualización, de modo que la cantidad de datos que actualiza gradualmente no crezca demasiado accidentalmente.
Incluso si sigue este consejo, aún podría terminar con un agregado continuo que tarde más en actualizarse que la cantidad de datos que intenta materializar; por ejemplo, tardará 30 minutos en materializar 15 minutos de datos. Esto sucede porque, a veces, la tarea subyacente agregada continua es demasiado grande para caber en la memoria y se derrama en el disco.
Nos encontramos con este problema, que se vio agravado debido a un error que encontramos (ahora solucionado) que causaba que se incluyeran fragmentos adicionales en el plan de consulta incluso cuando en última instancia no aportaban datos a la materialización. Encontrar este error fue en realidad un caso de "dogfoodception": descubrimos este problema de rendimiento mientras usábamos Insights mientras lo construíamos 🤯. La información de tiempo que vimos en Insights sugirió que algo andaba mal aquí, y descubrimos el problema usando EXPLAIN y mirando los planes. ¡Así que podemos decirte que funciona!
Para acelerar la materialización, terminamos creando una política de actualización incremental personalizada que limitaba el tamaño de los incrementos a actualizar. Estamos trabajando para ver si esto es algo que podemos generalizar correctamente en TimescaleDB.
El cambio es difícil a escala .
Una vez que sus datos hayan alcanzado un cierto tamaño, algunas operaciones DDL (modificación de esquema) en TimescaleDB pueden llevar más tiempo del ideal. Ya hemos experimentado esto de varias maneras.
Por ejemplo, agregar nuevos índices a hipertablas grandes se convierte en un ejercicio de sincronización. Debido a que TimescaleDB actualmente no admite el uso "CONCURRENTLY" con "CREATE INDEX", la siguiente mejor opción es usar su método integrado para crear el índice un fragmento a la vez. En nuestro caso, tenemos que iniciarlo inmediatamente después de crear un nuevo fragmento, por lo que el bloqueo del fragmento "activo" es mínimo. Es decir, crear un índice cuando un fragmento es nuevo significa que está (casi) vacío y, por lo tanto, puede completarse rápidamente y no bloquear nuevas inserciones.
Otra forma en que el cambio es difícil es cuando se actualizan agregados continuos para agregar nuevas métricas (columnas). Actualmente, los agregados continuos no admiten "ALTER". Entonces, cuando queremos exponer una nueva métrica a los usuarios, creamos una “versión” completamente nueva del agregado continuo, es decir, para el agregado continuo “foo” tendríamos “foo_v2”, “foo_v3”, etc. menos que ideal pero actualmente está funcionando.
Finalmente, alterar la configuración de compresión es bastante difícil a escala. De hecho, en realidad no es posible para nosotros en este momento, ya que requeriría descomprimir todos los fragmentos comprimidos, alterar la configuración y luego recomprimirlos, lo cual no es factible en nuestra escala actual.
Seguimos intercambiando ideas con nuestros colegas para encontrar soluciones viables para todas estas cosas. No sólo para nosotros, sino para todos los usuarios de Timescale.
Esa fue bastante información para incluirla en una sola publicación. Pero si necesitas un
Building Insights fue una experiencia profunda para nuestro equipo. Hemos visto de primera mano hasta dónde podemos llevar Timescale, llevándolo a cifras de escala impresionantes. Los puntos débiles que encontramos a lo largo del proceso nos han dado mucha empatía con el cliente; esa es la belleza del dogfooding.
El año que viene, espero escribir otra publicación de blog sobre cómo estamos monitoreando otro orden de magnitud de bases de datos y cómo hemos seguido mejorando la experiencia de trabajar con Timescale a escala.
¡Hasta entonces! 👋
También publicado aquí.