As razões que dei a mim próprio estavam erradas
Não me propus construir uma plataforma de dados. Propus-me deixar de montar uma.
Já tinha trabalhado em analítica, e fazê-la de ponta a ponta era penoso de uma maneira muito concreta: não havia no mercado nada que resolvesse o caminho todo de forma simples. Escolhias uma ferramenta de ingestão, um armazém, uma camada de transformação e um produto de dashboards, e depois passavas o tempo nas costuras entre eles. Às vezes as peças nem sequer falavam umas com as outras. O trabalho que produzia informação útil era uma fração do trabalho que mantinha a montagem de pé.
Comecei então a construir a coisa que queria. O que se segue é a parte que ninguém põe num blogue de produto: as três coisas de que eu tinha a certeza, e o que cada uma me custou quando finalmente a medi.
"Uma base de dados escala em cluster, e as respostas vêm em menos de um segundo"
Foi a primeira, e é a crença mais confortável da indústria, porque muda o problema de sítio. Se as respostas são lentas, acrescenta-se máquinas.
O que aprendi é que o cluster não é a pergunta. A pergunta é o que a query tem de ler. Um motor distribuído pode espalhar uma varredura por mais máquinas e continuar a ler cem vezes mais dados do que precisa, e nessa altura estás a pagar um cluster para fazer o trabalho errado mais depressa. O sub-segundo é uma propriedade da disposição do armazenamento e da query, não do número de máquinas por baixo.
O corolário é incómodo para quem já dimensionou um cluster para resolver um problema de latência, e eu incluo-me: andava a tratar hardware como resposta a uma questão de desenho.
"Os cubos OLAP resolvem a performance, porque têm cache"
A segunda. Pré-calculas os agregados, manténs a cache quente, serves depressa. Funciona, e construí isso.
O que não te dizem à cabeça é o custo de te enganares no grão. Um cubo é a resposta às perguntas em que pensaste quando o definiste. No momento em que alguém pede uma quebra que não pré-calculaste, ou voltas aos dados em bruto com toda a latência de que estavas a fugir, ou alargas o cubo e pagas isso em manutenção, em disco e em tempo de refresh. A cache não é grátis, é pré-paga, e pagas todas as combinações mesmo aquelas que ninguém pede.
Acabei por apagar uma hierarquia inteira de camadas pré-calculadas e substituí-la por uma só, grosseira, mantida pelo próprio motor em vez de por um processo que eu tinha de vigiar. Menos respostas pré-calculadas, e as que ficaram são realmente usadas.
"O ClickHouse faz biliões de registos em milissegundos"
A terceira. É a razão por que muita gente chega aos motores colunares, e é verdade da mesma maneira que "um carro faz 200 à hora" é verdade. É verdade sobre o motor, na estrada certa, na mudança certa.
Ler biliões de linhas em milissegundos exige que as linhas estejam dispostas de forma a que o motor possa saltar quase todas, e exige que os valores estejam tipados em vez de serem interpretados. Descobri quanto custa essa segunda metade fazendo-a mal primeiro. Servir uma query convertendo uma coluna JSON para texto e interpretando-a demorava 106 segundos; a mesma query contra colunas tipadas pré-classificadas demorava 4 segundos sobre 40 milhões de linhas, ambas com FINAL. Isto não é uma diferença de afinação. É outro produto.
O motor nunca foi o que tornou aquilo rápido. O que o tornou rápido foi dar ao motor alguma coisa que ele pudesse saltar.
O arco do armazenamento, e o que matou cada etapa
Por baixo daquelas três crenças há uma sequência, e cada etapa acabou porque alguma coisa mensurável disse que tinha de acabar.
Primeiro EAV. Uma linha por atributo. É o esquema para que se olha quando não se sabe o que os clientes vão enviar, e é lindamente flexível até ao momento em que é preciso remontar um evento a partir das suas partes, em escala. Não escalava, e abandonei-o.
Depois uma tabela larga por cliente. Colunas tipadas a sério, uma tabela física por cliente e por conjunto de dados. Rápida, limpa, e falha de uma maneira que não se vê chegar: o tecto não é o número de linhas, é o número de tabelas. Cada tabela custa disco e, muito pior, custa débito de manutenção. Alguns milhares delas e a plataforma passa a vida a manter vivo o seu próprio catálogo.
Hoje, uma tabela partilhada com Maps tipados. Os eventos de todos os clientes na mesma tabela, ordenada primeiro pelo cliente, para que a query de um nunca leia o armazenamento de outro, com as métricas e as dimensões pré-classificadas em colunas Map tipadas e uma coluna JSON como recurso para tudo o resto. Uma tabela, seja qual for o número de clientes.
Três esquemas em três anos, e não mudei nenhum deles por ter lido alguma coisa. Mudei-os porque alguma coisa que eu tinha construído deixou de responder.
A parte que me custa mais admitir
Quando decidi mudar o motor de analítica para uma base de dados colunar, escrevi as razões. Depois, porque a migração era grande o suficiente para meter medo, medi-as.
Todas falharam.
O problema de escala que eu atribuía ao modelo de jobs era uma variável de configuração mal dimensionada: corrigida, o motor antigo aguentava muito mais do que eu lhe estava a pedir. A ingestão nativa que eu ia ganhar não removia serviço nenhum e teria acrescentado um. O tecto de débito de que eu estava a fugir não era a base de dados, que corria a pouco mais de um por cento da capacidade dela, mas um único processo da aplicação a saturar um core. E a camada de dicionários que eu tinha a certeza de precisar era mais lenta do que o join que eu já tinha, além de custar gigabytes de memória que o join não precisava.
Quatro argumentos, quatro refutações. E fiz a migração à mesma, porque as medições trouxeram outras razões, melhores do que aquelas com que eu tinha começado: a latência das queries, o tamanho em disco, o comportamento do catálogo sob desgaste e quantas varreduras podiam correr ao mesmo tempo.
A lição é toda essa, e levei anos a aprendê-la como deve ser. A crença que deves medir primeiro é aquela de que tens mais certeza, porque é a que já deixaste de questionar e é, por isso, a que está silenciosamente a moldar todas as decisões que vêm a seguir.
Valeu a pena
Quando me perguntam como tem sido construir isto, a resposta honesta é curta e pouco vendável: tem sido um inferno. Anos a repensar uma arquitetura, três modelos de armazenamento, e a descoberta repetida de que aquilo de que eu tinha mais certeza era o que me estava a custar mais caro.
Voltaria a fazê-lo, e não por teimosia. A razão é que a alternativa era a montagem de que eu estava a fugir, e cada uma daquelas suposições falhadas é hoje uma propriedade do produto em vez de uma lição que eu guardo na cabeça. As camadas são mantidas pelo motor porque me cansei de vigiar um processo. As colunas são tipadas porque medi quanto custava interpretá-las. A tabela é partilhada e ordenada por cliente porque vi o modelo por-cliente bater no tecto.
Nada disso lá estaria se eu tivesse acertado à primeira.