No início deste ano, embarcamos no maior projeto da nossa empresa
Naturalmente, escolhemos o Timescale, nossa plataforma de nuvem madura com TimescaleDB em seu núcleo. Estamos acostumados a trabalhar com PostgreSQL e construímos o TimescaleDB para tornar o PostgreSQL mais rápido e escalável – o que é melhor do que seguir nosso próprio exemplo?
A maneira mais fácil de descrever esse experimento de dogfooding é com números que ajudam a quantificar sua escala. Para construir o Insights, precisávamos coletar informações de consulta em nossa frota de bancos de dados de produção em execução contínua. Coletamos rapidamente mais de 1 trilhão de registros sobre consultas individuais (higienizadas) na plataforma.
Agora que o Insights está em produção, estamos ingerindo mais de 10 bilhões de novos registros por dia . O conjunto de dados servido por um único serviço Timescale cresce cerca de 3 TB diariamente e atualmente totaliza mais de 350 TB , e o mesmo serviço de banco de dados alimenta painéis em tempo real para todos os nossos clientes.
Esta postagem do blog fornece uma visão dos bastidores do processo de construção de insights. Operar nessa escala significava ultrapassar os limites de um único serviço de escala de tempo e dimensionar não apenas o PostgreSQL, mas também a empatia do desenvolvedor. Achamos que o Timescale está mais do que à altura da tarefa, mas também há áreas que queremos melhorar!
Para que o Insights acontecesse, tivemos que assumir nosso papel de administrador de banco de dados 🤠 e enfrentar alguns desafios técnicos para dimensionar o PostgreSQL para muitos terabytes de dados. Queríamos usar um serviço Timescale como nosso banco de dados central, hospedado em nossa plataforma sem nenhuma infraestrutura “especial”. Isso significava o seguinte:
Tivemos que construir um pipeline capaz de ingerir bilhões de registros por dia em um único serviço Timescale. A escala de tempo pode lidar com altas taxas de ingestão, e faz isso regularmente para nossos clientes, mas esse nível de escala sob cargas de consulta de produção sempre causa espanto.
Nossos clientes precisavam poder consultar esse banco de dados com a flexibilidade necessária para potencializar todas as análises que o Insights oferece, e não queríamos fazê-los esperar minutos por uma resposta!
Precisávamos armazenar centenas de TB em um único serviço de escala de tempo, já que a cada dia adicionamos vários TB. Dados mais antigos (ou seja, com mais de algumas semanas) precisavam ser acessíveis, mas não necessariamente rápidos para consulta.
Do lado da coleta de dados , aproveitamos a arquitetura da plataforma Timescale. A escala de tempo é executada em Kubernetes (k8s) e temos vários clusters k8s em execução em diferentes regiões geográficas. Esses clusters possuem nós que contêm um ou mais serviços de banco de dados de clientes. Para coletar as execuções de consulta para todos esses bancos de dados, passamos desse banco de dados para o nível regional e, em seguida, temos um gravador regional que armazena lotes de registros no serviço de banco de dados Timescale que alimenta o Insights.
Perdoe a hesitação que evita alguns detalhes sangrentos de baixo nível, mas em termos gerais, é assim que as coisas funcionam: cada banco de dados executado na frota é instrumentado para criar um registro (higienizado para privacidade e segurança) após cada consulta, incluindo o consulta em si e estatísticas que nos interessam.
Esses registros são coletados no nível do nó, marcados com rótulos para mantê-los associados ao serviço de banco de dados de onde vieram e agrupados para serem enviados ao gravador regional. O serviço de gravador regional é replicado conforme necessário para lidar com a carga em cada região. Cada gravador coleta lotes dos nós em cada cluster e cria lotes ainda maiores.
Esses lotes grandes são então gravados usando primeiro `COPY` em uma tabela temporária (sem registro Write-Ahead = rápido). As entradas nessa tabela temporária são então usadas para atualizar as tabelas necessárias (veja abaixo). A tabela temporária nos permite usar `COPY` sem nos preocupar com duplicatas, que são tratadas por operações subsequentes de remoção dos registros da tabela temporária.
Resumindo:
Vamos ampliar o banco de dados que alimenta o Insights. Estamos executando o Insights em um serviço de escala de tempo “pronto para uso” com um
O banco de dados que alimenta o Insights tem algumas partes, mas tentaremos destacar as mais importantes.
Primeiro, temos duas tabelas regulares do PostgreSQL que servem como “tabelas de referência”. Essas tabelas contêm metadados de banco de dados de informações e metadados de cadeia de consulta. Aqui estão seus (pseudo)esquemas:
Metadados de banco de dados
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)
Consultar metadados
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)
Sempre que um novo banco de dados começar a ter consultas executadas nele, ele será adicionado a `insights.cloud_db`. Sempre que uma nova consulta normalizada for executada, ela será adicionada a `insights.queries`.
(O que é uma consulta normalizada? É uma consulta onde todas as constantes foram substituídas por espaços reservados: $1 para a primeira, $2 para a segunda e assim por diante, então vemos apenas a “forma” da consulta, não seus valores .)
Até este ponto, estamos usando apenas Postgres normais, sem molho secreto de escala de tempo. Mas os outros objetos importantes no banco de dados são exclusivos do TimescaleDB, ajudando a escalar o PostgreSQL para outro nível. É aqui que a mágica acontece: hipertabelas e agregações contínuas.
Hypertables são tabelas particionadas automaticamente do Timescale. Eles particionam automaticamente os dados por uma dimensão enquanto eles são ingeridos, tornando muito mais fácil dimensionar tabelas PostgreSQL para grandes escalas. Hipertabelas são os blocos de construção da escala de tempo. Estamos armazenando nossas métricas de estatísticas de consulta em uma enorme hipertabela, como veremos mais tarde.
As agregações contínuas são a versão aprimorada das visualizações materializadas do PostgreSQL do Timescale, permitindo a materialização incremental e automática , o que se mostrou muito útil na construção de Insights.
Vamos abordar como usamos esses recursos para permitir consultas analíticas rápidas por parte dos usuários.
Como dissemos, usamos uma grande hipertabela para armazenar informações sobre cada execução de consulta. Esta hipertabela é nossa tabela principal, onde residem as métricas brutas higienizadas. Parece um pouco com o seguinte e está configurado para usar sua coluna de carimbo de data/hora ( created
) para particionar automaticamente os dados à medida que são ingeridos.
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 | | | ...
Omitimos várias estatísticas neste exemplo, mas você entendeu.
Agora, temos que permitir consultas rápidas do lado do usuário — mas esta tabela é enorme. Para acelerar as coisas, contamos muito com agregados contínuos (usando
Agregados contínuos fazem muito sentido em um produto que oferece análises em tempo real voltadas para o usuário, como o Insights. Para fornecer informações acionáveis aos usuários, precisamos agregar métricas: não estamos mostrando aos usuários um registro de cada consulta que eles executaram com estatísticas ao lado – alguns bancos de dados estão fazendo milhares de consultas por segundo, então seria um pesadelo encontrar qualquer coisa útil. Em vez disso, atendemos agregados de usuários.
Portanto, podemos também aproveitar o fato de não mostrarmos registros individuais brutos aos usuários e manter o resultado
Poderíamos ter usado visualizações materializadas do PostgreSQL, mas as agregações contínuas do Timescale têm diversas vantagens que foram especialmente úteis para nós. Atualizamos bastante as visualizações e os agregados contínuos têm políticas integradas para atualizações automáticas e são atualizados de forma incremental.
Atualizamos as visualizações a cada cinco minutos, portanto, em vez de gerar novamente todas as informações materializadas a cada cinco minutos, os agregados contínuos atualizam a visualização de forma incremental, rastreando as alterações na tabela original. Na escala em que operamos, simplesmente não podemos nos dar ao luxo de varrer nossa hipertabela principal de cima para baixo a cada cinco minutos, então essa funcionalidade de agregações contínuas foi um “desbloqueio” fundamental para nós.
Nesses agregados contínuos que alimentam o Insights nos bastidores, também estamos agregando a maioria das estatísticas interessantes em um
Ainda assim, a certa altura, o banco de dados começou a trabalhar muito para inserir todos esses registros brutos e depois materializá-los para servir. Estávamos atingindo algumas limitações sobre quanto poderíamos ingerir e acompanhar.
Para aumentar ainda mais nossa taxa de ingestão até o nível necessário, transferimos a geração do UDDSketch do banco de dados para os gravadores da região. Agora, ainda armazenamos uma certa quantidade de registros como registros “brutos”, mas também colocamos o restante em esboços pré-gerados que armazenamos no banco de dados:
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 | | | ...
A melhor parte do UDDSketchs é que é muito fácil “acumular” continuamente os esboços para suportar intervalos de tempo maiores. Usando esse rollup, os esboços que cobrem intervalos de tempo mais estreitos podem ser agregados em um esboço que cobre um amplo intervalo de tempo, tanto ao construir uma agregação hierárquica contínua quanto no momento da consulta.
Outra ferramenta que utilizamos para garantir a ingestão rápida e as consultas são réplicas de leitura. O uso da replicação é fundamental para alta disponibilidade e desempenho em nosso caso, visto que o Insights fornece um recurso importante voltado para o cliente para a plataforma Timescale.
Nossa principal instância de banco de dados está bastante ocupada com trabalho em massa, gravação de dados, materialização de agregações contínuas, execução de compactação e muito mais. (Mais sobre compactação em um minuto.) Para aliviar parte de sua carga, permitimos que o cliente do serviço de réplica leia as solicitações do console do Insights.
Por último, precisávamos acomodar confortavelmente centenas de TB em um único serviço de escala temporal. O banco de dados do Insights está sendo escalonado rapidamente: era da ordem de 100 TB quando começamos e agora ultrapassa 350 TB (e continua aumentando).
Para armazenar tantos dados de forma eficiente, habilitamos
Estamos testemunhando taxas de compressão de mais de 20x em nossa hipertabela principal.
Outra grande vantagem ao gerenciar uma hipertabela muito grande foi a mutabilidade do esquema dos dados compactados. Descrevemos nosso esquema aproximado em uma seção anterior, mas como você pode imaginar, nós o alteramos frequentemente para adicionar mais estatísticas e assim por diante — é muito útil poder fazer isso diretamente na hipertabela compactada.
Também somos usuários frequentes da classificação de dados do Timescale. Esse recurso entrou em acesso antecipado no início deste ano (aguarde novidades do GA em breve 🔥) e nos permite manter centenas de TBs acessíveis por meio de nosso banco de dados Timescale. A hierarquização de dados também se mostrou muito eficiente: vemos taxas de compressão incríveis aqui também, com 130 TB diminuindo para 5 TB altamente eficientes em termos de recursos.
O processo de construção de Insights nos mostrou até onde nosso produto pode realmente ir, mas o melhor foi caminhar alguns quilômetros no lugar de nossos clientes. Aprendemos muito sobre a experiência do usuário ao dimensionar o PostgreSQL com Timescale e adicionamos algumas coisas à nossa lista de tarefas como engenheiros por trás do produto.
Vamos repassar tudo: o bom e o mais ou menos.
Perdoe nossa imodéstia, mas às vezes ficamos muito orgulhosos de nosso produto. Ingerir dezenas de bilhões de registros diariamente em um único banco de dados PostgreSQL que já possui centenas de TB não é nada desprezível . Passamos algumas semanas ajustando o banco de dados quando ele começou a crescer, mas agora ele simplesmente funciona , sem babá ou monitoramento constante. (Observe que isso é diferente de não ser monitorado, é definitivamente monitorado!)
Nosso
A compressão funcionou muito bem para nós. Como compartilhamos na seção anterior, obtivemos taxas de compactação impressionantes (20x!) Usando uma opção simples `segmentby`. Para nós, a experiência de configurar e ajustar a política não foi difícil – embora, é claro, tenhamos construído esse recurso…pode-se dizer que temos uma ligeira vantagem. 🙂 Além disso, a capacidade de adicionar novas colunas aos dados compactados aumentou ainda mais a flexibilidade e adaptabilidade do nosso banco de dados. Usamos esse recurso sem complicações.
Os agregados contínuos simplificaram a lógica de construção de diferentes períodos de tempo, agilizando a análise e o processamento dos dados. Usamos toneladas de agregados contínuos hierárquicos.
Os algoritmos de aproximação incluídos nas hiperfunções do Timecale simplificaram nossa implementação e ampliaram bastante nossa análise. A capacidade de acumular esboços facilmente também foi fundamental para oferecer suporte eficiente a diferentes intervalos de tempo e granularidades de intervalo de tempo em nossos painéis de Insights voltados para o cliente.
O armazenamento “infinito” que um banco de dados Timescale tem à sua disposição por meio de camadas de dados foi fundamental para escalar para centenas de TBs, com bastante espaço para crescer. Nossa atual __ política de classificação de dados __ retém três semanas de registros em armazenamento ativo.
Por fim, usamos a capacidade de criar trabalhos personalizados para melhorar a observabilidade (como monitorar o histórico de trabalhos) e implementar estratégias experimentais de atualização.
Depois de contar todas as coisas boas, é hora de reconhecer as que não são tão boas. Nada é perfeito, incluindo a escala de tempo. Enfrentamos alguns desafios ao implementar nosso pipeline, e não os consideramos como queixas:
A observabilidade da base de dados poderia ser melhorada na plataforma Timescale, particularmente em torno de empregos e do desempenho da materialização de agregados contínuos.
O TimescaleDB fornece principalmente visualizações baseadas em instantâneos, tornando difícil entender o desempenho e as tendências ao longo do tempo. Por exemplo, não há uma tabela pronta para uso de “histórico de trabalhos” disponível. No início, percebemos que a materialização incremental de nossos agregados contínuos estava aparentemente demorando cada vez mais – eventualmente levando à descoberta de um bug – mas não tínhamos como confirmar ou quantificar o escopo.
Como observamos anteriormente, a capacidade de definir trabalhos personalizados e executá-los dentro da estrutura de trabalho do Timescale nos permitiu criar uma versão “suficientemente boa” disso. Consultaríamos continuamente as visualizações que queríamos monitorar ao longo do tempo e inseriríamos quaisquer alterações em uma hipertabela. Isso funciona para o Insights por enquanto, mas também estamos trabalhando para transformar algumas dessas coisas em funcionalidades integradas porque achamos que elas são cruciais quando você dimensiona a escala de tempo além do ponto em que tudo é rápido o tempo todo .
Pode ser difícil acertar agregações contínuas quando os dados subjacentes são grandes .
Usar a opção `__ WITH NO DATA` ao criar agregações contínuas __é um salva-vidas. Também é importante ser criterioso com suas compensações para a política de atualização, para que a quantidade de dados que você está atualizando de forma incremental não cresça acidentalmente demais.
Mesmo se você seguir esse conselho, ainda poderá acabar com uma agregação contínua que leva mais tempo para ser atualizada do que a quantidade de dados que você está tentando materializar, por exemplo, levando 30 minutos para materializar 15 minutos de dados. Isso acontece porque, às vezes, a tarefa subjacente agregada contínua é muito grande para caber na memória e transborda para o disco.
Encontramos esse problema, que foi agravado devido a um bug isolado que encontramos (agora corrigido) que fazia com que pedaços extras fossem incluídos no plano de consulta, mesmo quando, em última análise, não contribuíssem com dados para a materialização. Encontrar esse bug foi na verdade um caso de “dogfoodception”: descobrimos esse problema de desempenho ao usar o Insights enquanto o desenvolvíamos 🤯. As informações de tempo que vimos no Insights sugeriam que algo estava errado aqui, e descobrimos o problema usando EXPLAIN e analisando os planos. Então podemos dizer que funciona!
Para tornar a materialização mais rápida, acabamos criando uma política de atualização incremental personalizada que limitava o tamanho dos incrementos a serem atualizados. Estamos trabalhando para ver se isso é algo que podemos generalizar de volta ao TimescaleDB de maneira adequada.
A mudança é difícil em grande escala .
Depois que seus dados atingirem um determinado tamanho, algumas operações DDL (modificação de esquema) no TimescaleDB podem levar mais tempo do que o ideal. Já experimentamos isso de algumas maneiras.
Por exemplo, adicionar novos índices a grandes hipertabelas torna-se um exercício de timing. Como o TimescaleDB atualmente não suporta o uso de `CONCURRENTLY` com `CREATE INDEX`, a próxima melhor opção é usar seu método integrado para criar o índice, um pedaço de cada vez. No nosso caso, temos que iniciá-lo logo após a criação de um novo pedaço, para que o bloqueio no pedaço “ativo” seja mínimo. Ou seja, criar um índice quando um pedaço é novo significa que ele está (quase) vazio e, portanto, pode ser concluído rapidamente e não bloquear novas inserções.
Outra maneira pela qual a mudança é difícil é ao atualizar agregados contínuos para adicionar novas métricas (colunas). Agregados contínuos atualmente não suportam `ALTER`. Assim, quando queremos expor uma nova métrica aos usuários, criamos uma “versão” totalmente nova do agregado contínuo, ou seja, para agregado contínuo “foo” teríamos então “foo_v2”, “foo_v3”, etc. menos do que o ideal, mas atualmente está funcionando.
Finalmente, alterar as configurações de compactação é bastante difícil em escala. Na verdade, isso não é possível para nós no momento, pois exigiria a descompactação de todos os pedaços compactados, a alteração das configurações e a recompactação deles, o que não é viável em nossa escala atual.
Continuamos a debater ideias com nossos colegas para obter soluções viáveis para todas essas coisas. Não apenas para nós, mas para todos os usuários do Timescale.
Foi muita informação para colocar tudo em um só post. Mas se você precisar de um
Building Insights foi uma experiência profunda para nossa equipe. Vimos em primeira mão até onde podemos levar o Timescale, alcançando números de escala impressionantes. Os pontos problemáticos que encontramos ao longo do processo nos deram muita empatia com o cliente – essa é a beleza do dogfooding.
No próximo ano, espero escrever outra postagem no blog sobre como estamos monitorando mais bancos de dados e como continuamos a melhorar a experiência de trabalhar com o Timescale em escala.
Vejo você então! 👋
Também publicado aqui.