NOVO: Helius adquire a Light Protocol
Do ClickHouse ao RocksDB: como reconstruímos a camada de arquivamento da Solana
Blog/Engenharia

Do ClickHouse ao RocksDB: como reconstruímos a camada de arquivamento da Solana

Developer Experience Engineer0xIchigo no X0xIchigo no LinkedIn0xIchigo no GitHub
Engenheiro de SoftwareConnor Peticca no LinkedIn
19 min de leitura

Quando contamos que migramos mais de 300 terabytes de dados de arquivamento da Solana do ClickHouse para o RocksDB, a reação é quase sempre a mesma: Por que vocês fariam isso?

O ClickHouse é a escolha óbvia para cargas de trabalho analíticas na escala de petabytes, enquanto o RocksDB não é.

Alguns dos maiores consumidores de dados do mundo confiam no ClickHouse. 

Por exemplo, a Cloudflare o executa em mais de mil réplicas para processar centenas de milhões de inserções por segundo.

A Anthropic executa uma implantação personalizada e isolada do ClickHouse para viabilizar a observabilidade do Claude. Uber, eBay e Bloomberg também o utilizam em produção há anos.

O RocksDB, por outro lado, é um armazenamento chave-valor incorporado com documentação limitada, usado principalmente como mecanismo dentro de outros bancos de dados (por exemplo, CockroachDB, TiKV e MyRocks), e não como base direta para um serviço de dados históricos voltado aos clientes.

No entanto, a migração reduziu nosso volume de armazenamento comprimido de ~330 TB para ~190 TB, eliminou a cauda longa das nossas consultas mais lentas (por exemplo, a latência p95 das chamadas getTransactionsForAddress caiu de 350 ms para 30 ms) e nos colocou em posição de inovar na camada de leitura da Solana — algo que não teria sido possível com o ClickHouse diante do desempenho e da escala exigidos na Helius.

Este artigo explica por que migramos do ClickHouse, o que aprendemos sobre a carga de trabalho ao longo do caminho e como usamos o RocksDB em produção em grande escala. 

O que é o arquivamento da Solana e por que ele é importante

Em sua essência, a Solana é uma rede de nós que se comunicam para chegar a um consenso sobre novas informações sem precisar confiar uns nos outros.

Um nó é um computador na Solana que executa um cliente (por exemplo, Agave ou Firedancer) e segue um conjunto específico de regras para ajudar a viabilizar o consenso sobre novas informações. 

Um validador é um nó da Solana que protege a rede produzindo blocos (ou seja, adicionando grupos de transações ao ledger da Solana) e votando na validade de outros blocos. 

Os nós RPC são validadores que não participam da produção de blocos nem da votação. Em vez disso, observam a rede e acompanham todas as novas informações que ela produz. Os RPCs permitem que os usuários consultem dados específicos da rede usando a especificação JSON-RPC. 

No entanto, esses nós não mantêm esses dados para sempre. 

Para permanecer dentro dos requisitos de hardware da Solana, blocos, transações e estados de contas mais antigos são removidos, de modo que os nós conservem apenas a visão mais recente do histórico da Solana. 

Isso é problemático quando se tenta buscar a assinatura de uma transação de um ano atrás, obter todas as assinaturas que já interagiram com determinada carteira durante toda a sua existência ou examinar o histórico completo de execução de um programa.

Arquivamento se refere, de modo geral, a toda a camada que armazena os dados da Solana desde o bloco gênese. Ela registra e indexa tudo o que a blockchain produz — cada bloco, transação, interação com conta, Cross Program Invocation (CPI) e log de transação — e mantém esses dados consultáveis além da janela padrão de armazenamento de curto prazo de ~2 dias (ou seja, 1 época). 

É o arquivamento que possibilita consultas históricas.

A abordagem padrão na Solana para armazenar dados de arquivamento tem sido o Google BigTable. A Anza mantém uma instância do BigTable que os provedores de RPC podem acessar sob demanda para atender a consultas históricas.

Ela funciona, mas o BigTable é caro, os custos de saída de dados tornam a manutenção de uma cópia própria penosa e quase não há trabalho de engenharia que possa ser feito do seu lado para torná-lo mais rápido — você fica à mercê do armazenamento do Google, com toda a estrutura de custos e nenhum controle. 

Old Faithful é um conjunto de ferramentas mantido pela Triton One que pode produzir Content Addressable Archives (CARs) a partir de arquivos do ledger no RocksDB e disponibilizá-los pelas interfaces RPC e gRPC padrão da Solana. É um passo importante rumo à descentralização da camada de arquivamento da Solana, oferecendo uma fonte valiosa de histórico redundante e verificável.

No entanto, suas concessões em termos de experiência do desenvolvedor e desempenho (por exemplo, para começar é necessário usar ferramentas personalizadas em vez de interfaces sobre as quais as equipes possam desenvolver, e a solução é otimizada para arquivamento durável, não para desempenho e escala) impedem que seja uma alternativa relevante para cargas de trabalho sensíveis à latência. 

O problema central do arquivamento é que você tem N petabytes de dados brutos. Com mais de 500 bilhões de transações, ~1,3 trilhão de linhas em índices de conta para transação e padrões de acesso aleatório, como realizar buscas em menos de 10 ms? Como seriam consultas de ponta a ponta em menos de 50 ms? Nem o Google BigTable nem o Old Faithful oferecem uma solução para isso.

Além disso, se você quisesse criar opções personalizadas de filtragem e ordenação ou melhorar a experiência do desenvolvedor oferecendo algo mais avançado do que os métodos JSON-RPC padrão, precisaria do seu próprio índice de arquivamento. 

Então, criamos um.

ClickHouse: a primeira aposta pragmática

O ClickHouse foi o ponto de partida mais pragmático para desenvolver nosso novo sistema de arquivamento. Era o caminho mais rápido para lançar um produto que já fosse melhor do que um construído sobre o BigTable.

O ClickHouse é um banco de dados colunar maduro, com excelentes ferramentas e documentação. Ele comprime bem dados de séries temporais, o que é especialmente útil para a Solana, já que os dados de seus blocos são, fundamentalmente, ordenados por tempo.

É simples criar um banco de dados, escrever SQL para ele, iterar sobre esquemas e otimizar o tempo de lançamento, permitindo que os clientes tenham algo disponível com relativa rapidez.

O ClickHouse não atendia ao tráfego sozinho. Em vez disso, era a camada inferior da nossa pilha de armazenamento, organizada de acordo com a atualidade dos dados. 

Os dados mais recentes (ou seja, aproximadamente o último minuto ou dois, ou algumas centenas de slots) ficavam em um armazenamento em memória. Cerca das duas últimas semanas de dados ficavam no Postgres. O ClickHouse armazenava o restante do histórico da Solana.

Um roteador inteligente ficava à frente das três fontes, para que buscas pontuais fossem enviadas à camada mais ativa que contivesse os dados, enquanto consultas de intervalo eram divididas entre as camadas e depois reunidas em um único resultado.

O ClickHouse funcionava relativamente bem para as cargas de trabalho que foi projetado para processar. Por exemplo, “forneça todos os blocos neste intervalo de slots” ou “examine a atividade desta conta ao longo do tempo”. 

As consultas de séries temporais funcionavam bem. Podíamos preencher dados retroativamente, executar análises ad hoc e responder à maioria das perguntas que os provedores de RPC historicamente precisavam fazer. A ingestão nunca chegou perto do limite; gravávamos apenas ~10 MB/s em nosso índice por meio do LaserStream.

O erro ao escolher o ClickHouse não foi ter escolhido o ClickHouse. Foi presumir que nossa carga de trabalho permaneceria em um formato que ele conseguiria processar em grande escala. 

Onde o ClickHouse deixou de funcionar

Os métodos históricos da Solana que nos interessam não são todos de séries temporais. Uma assinatura na Solana (ou seja, o identificador universal de uma transação) consiste essencialmente em 64 bytes de dados aleatórios. 

Quando alguém chama getTransaction para determinada assinatura, trata-se de uma busca pontual uniformemente aleatória. Não há slot, intervalo de tempo ou qualquer tipo de localidade que possa ser explorada. 

O mesmo vale para outras consultas, como “obtenha todas as assinaturas que interagiram com esta conta”, porque a chave da conta também é efetivamente aleatória. Esse problema ocorre sempre que uma chave primária é um UUID, um hash ou outro identificador de alta entropia.

Mecanismos colunares não foram criados para resolver esse problema. 

O ClickHouse armazena dados em partes ordenadas e usa índices primários esparsos. Isso significa que há pouco a eliminar em uma busca por chave aleatória. Em algum momento, você acaba lendo mais grânulos do que gostaria. Uma única busca de assinatura podia acessar 20 grânulos — na ordem de 10.000 linhas e ~60 operações de E/S em disco — antes de qualquer busca binária ou sobrecarga de CPU. 

Considerando apenas as operações de E/S, isso representa aproximadamente 5 ms para uma única busca. Como o armazenamento é colunar, ler uma transação significa ler ~15 colunas como operações de E/S separadas em um grânulo de 512 linhas.

Isso significa que uma chamada a getTransactionsForAddress solicitando os detalhes completos das transações, por exemplo, se desdobra em até 100 dessas buscas, o que eleva o piso de latência para cerca de 100 ms antes mesmo que um único byte seja serializado. 

Um lote grande de getTransaction (ou seja, até 1.000) elevava esse piso para quase um segundo inteiro.

As abstrações que tornam as varreduras rápidas (por exemplo, execução vetorizada e materialização tardia) não resolvem completamente esse problema. As buscas já estavam otimizadas ao máximo possível. Não faltava um índice. Não havia nenhuma projeção que pudéssemos adicionar. 

Em resumo, o layout fundamental dos dados estava errado para o que estávamos exigindo dele.

Os métodos mais caros que havíamos adicionado (por exemplo, getTransactionsForAddress e getTransfersByAddress) usavam os padrões de chave aleatória que o ClickHouse processava pior. 

Algumas dessas consultas levavam de 2 a 3 segundos, embora devessem levar apenas milissegundos. Recebíamos alertas de degradação de desempenho em cargas de trabalho que não podíamos corrigir em sua essência. 

Como instituições e clientes Enterprise usam a Helius em grande escala, isso é totalmente inaceitável no longo prazo.

Escalar o ClickHouse para essa carga de trabalho significava aumentar o número de máquinas e, na nossa escala, multiplicar várias vezes a carga de trabalho. Gastar mais dinheiro apenas com hardware não resolveria o problema.

A pilha de RPC da Solana está amadurecendo. O ecossistema chegou a um ponto de inflexão em que os métodos JSON-RPC padrão já não são suficientes. Os clientes exigem consultas históricas avançadas, e ninguém mais as desenvolveria sobre o BigTable. 

Se quiséssemos avançar essa fronteira, a camada de armazenamento teria que mudar. 

RocksDB: não é um banco de dados

Apesar do nome, o RocksDB não é um banco de dados. É uma biblioteca.

Não há linguagem de consulta, protocolo cliente/servidor, mecanismo SQL, joins ou índices no sentido tradicional de um banco de dados. É uma interface que diz: “Aqui está uma chave (alguns bytes), aqui está um valor (também alguns bytes), persista-os no disco; depois, devolva o valor dessa chave”. Só isso.

É um elemento primitivo. Pode-se dizer que é o elemento primitivo para construir mecanismos de armazenamento. 

Tudo o que se espera de um banco de dados tradicional (por exemplo, planejador de consultas, protocolo de comunicação, replicação e observabilidade) precisa ser construído.

Isso parece uma desvantagem até você entender o que essa abordagem oferece. Um armazenamento chave-valor puro, respaldado por uma árvore Log-Structured Merge (LSM) no disco, tem exatamente o formato certo para buscas por chaves uniformemente aleatórias com alta taxa de transferência. Não há um planejador de consultas adicionando sobrecarga, pressupostos de layout colunar a conciliar nem um frontend SQL cujo custo precise ser considerado.

Você persiste bytes, faz a leitura deles e posiciona filtros de Bloom e caches nos lugares certos para manter as leituras aleatórias baratas.

É exatamente aqui que a ideia de que isso seria um antipadrão deixa de fazer sentido. 

Não substituímos o ClickHouse pelo RocksDB — substituímos o ClickHouse por um banco de dados personalizado cujo mecanismo de armazenamento é o RocksDB. Essa é uma diferença importante.

Esse também é o mesmo padrão que a maioria dos bancos de dados em produção usa internamente. Por exemplo, CockroachDB, TiKV, MyRocks e os armazenamentos de estado do Kafka Streams são todos construídos sobre o RocksDB.

A diferença é que eles primeiro o envolvem em outro banco de dados, enquanto nós mesmos construímos o banco de dados ao redor dele e o ajustamos para os padrões exatos de acesso exigidos pelo arquivamento.

Como o RocksDB resolve as limitações do ClickHouse

Então, como exatamente um armazenamento chave-valor resolve o problema das chaves aleatórias que um mecanismo colunar não conseguia resolver? Tudo se resume à forma como os bytes são armazenados no disco.

O RocksDB usa compactação por níveis. Novas gravações chegam ao nível 0, uma área de despejo não ordenada — efetivamente a mesma situação do ClickHouse — em que as partes dentro de uma partição não são ordenadas globalmente. 

No entanto, os níveis 1 até N são sequências totalmente ordenadas. Depois que os dados são compactados, os metadados de mínimo/máximo realmente eliminam partes da busca, mesmo para chaves uniformemente aleatórias, porque o nível é ordenado globalmente. De certa forma, tudo no ClickHouse se comporta como o nível 0 do RocksDB.

Aproveitamos isso em nossos preenchimentos retroativos. 

Quando construímos o índice de assinaturas, ordenamos todo o histórico previamente e o carregamos diretamente no nível inferior. A partir daí, apenas os dados recentes chegam ao nível 0, e eles são mesclados rapidamente com os níveis inferiores. 

O resultado é que quase todas as buscas leem uma única sequência ordenada. Na prática, ordenamos dados aleatórios com o RocksDB.

O que construímos sobre ele

O ClickHouse oferece muita coisa pronta: um mecanismo de consulta, um protocolo de comunicação, um cliente e uma forma de disponibilizar dados. O RocksDB não oferece nenhuma dessas opções; ele simplesmente persiste bytes. Tivemos que construir todo o resto.

Assim, construímos nosso próprio banco de dados sobre o RocksDB, especializado nos padrões de acesso ao arquivamento da Solana.

Hoje, dois índices residem no RocksDB:

  1. Assinatura -> Localização (ou seja, o índice de assinaturas, que mapeia a assinatura de 64 bytes de uma transação para o slot e a posição em que ela reside dentro do bloco).
  2. Slot -> Bloco (ou seja, o índice de slot para bloco, que mapeia a localização acima para os dados da transação).

É importante observar que não há um caminho direto de uma assinatura até os dados da transação, e contornar essa limitação é exatamente o motivo da existência desses dois índices. 

Uma assinatura é essencialmente um identificador aleatório de 64 bytes. O que realmente indica onde uma transação reside é seu slot e seu índice dentro do bloco. Portanto, buscar uma transação exigiria dois saltos (um para resolver a assinatura em uma determinada localização e outro para recuperar os detalhes da transação nessa localização), enquanto buscar um bloco exigiria apenas um salto, já que o chamador fornece o slot.

Juntos, esses índices viabilizam alguns dos métodos mais pesados atendidos pela Helius (por exemplo, getBlock, getTransaction e getTransactionsForAddress, especialmente quando details são definidos como full).

O caminho de leitura é muito parecido com o que existia no ClickHouse. Uma solicitação chega, nosso cliente interno faz uma chamada ao banco de dados e o resultado retorna. O que muda é o mecanismo subjacente. Cada método segue um caminho codificado e ajustado manualmente. Quando um usuário consulta uma transação, um caminho getTransaction criado especificamente para essa finalidade vai direto aos bytes.

Esse caminho é rápido porque controlamos todas as suas camadas. Usamos io_uring para operações de E/S de arquivo e rede ao transmitir dados para fora do RocksDB. Quando os dados estão armazenados sem compressão no disco, nós os copiamos diretamente do disco para a placa de rede, sem qualquer desvio pelo espaço do usuário.

Os métodos são codificados, a E/S é ajustada de ponta a ponta e não há componentes desnecessários no meio.

O resultado é evidente em produção. 

Recentemente, sustentamos 150 Gbit/s de tráfego de getBlock por cinco a seis horas seguidas, enquanto saturávamos a maioria das nossas placas de rede. Nenhum problema, nenhum alerta e o mesmo desempenho bruto. 

Em todos os aspectos,

  • O armazenamento comprimido foi reduzido quase pela metade, de ~330 TB para ~190 TB
  • A latência P95 das chamadas getTransaction caiu de 7 ms para 1 ms
  • A latência P95 das chamadas getTransactionsForAddress caiu de 350 ms para 30 ms
  • A latência P95 das chamadas getBlock caiu de 50 ms para 35 ms

Como esperado, as chamadas getBlock foram as que menos melhoraram. O ClickHouse já armazena transações em blocos de 512 linhas, o que amortiza a penalidade colunar para leituras do tamanho de um bloco. Grande parte do tempo de ponta a ponta de getBlock é gasto na codificação Base58 e JSON e na remontagem do bloco, portanto, trocar o mecanismo de armazenamento não acelera esse processo.

A história mais profunda aqui — como fizemos io_uring, Rust assíncrono e uma biblioteca síncrona como o RocksDB trabalharem juntos sob uma carga de rede de alta taxa de transferência — merece uma publicação própria no futuro.

No entanto, a seção a seguir aborda algumas otimizações que consideramos úteis ao trabalhar com o RocksDB. 

Como otimizar o RocksDB para grande escala

Não existe uma única configuração “rápida” do RocksDB. As configurações corretas dependem totalmente do padrão de acesso do índice que está sendo ajustado.

Nosso índice assinatura -> localização e nosso índice slot -> bloco residem no mesmo processo e no mesmo hardware, mas exigem ajustes quase diametralmente opostos.

Portanto, em vez de fornecer um arquivo de configuração com parâmetros que talvez não se aplicassem à sua carga de trabalho, esta seção explica como avaliamos as concessões que podem ser transferidas para outros casos.

Ajuste por índice, não por banco de dados

Cada um dos nossos índices é sua própria família de colunas, com opções próprias. Um atende a buscas pontuais uniformemente aleatórias; o outro contém valores grandes e compressíveis, obtidos em ordem. Tratá-los de forma idêntica teria desperdiçado muitas oportunidades de otimização de desempenho. Quase todas as decisões descritas abaixo devem ser interpretadas como “para este padrão de acesso, faça X”.

Decida se você realmente precisa de WAL

Os dados de arquivamento podem ser reconstruídos. Ou seja, eles chegam por streaming do LaserStream e são derivados da própria blockchain.

Gravamos com o Write Ahead Log (WAL) desativado, para não pagarmos pela durabilidade de gravação antecipada da qual não precisamos. 

A sutileza é que desativar o WAL também desativa a consistência padrão do RocksDB após falhas entre famílias de colunas, então a consistência precisa ser restaurada de outra forma. A lição é que as configurações de durabilidade devem corresponder à capacidade de recuperação dos seus dados. Dados que podem ser reconstruídos a partir de uma fonte upstream exigem um nível de garantia muito diferente de um sistema de registro oficial.

Ajuste os filtros de Bloom à sua proporção de acertos e erros

Os filtros de Bloom justificam o uso de memória ao responder de forma econômica se uma chave não está presente em uma busca, o que significa que só ajudam quando não há correspondência. 

Uma busca de assinatura quase sempre resulta em correspondência, porque o chamador tem uma assinatura e quer os dados da transação correspondente.

O nível mais inferior da LSM contém a grande maioria dos nossos dados. Quando a maior parte dos dados fica em um único nível e sua carga de trabalho é dominada por correspondências, os filtros desse nível consomem mais memória enquanto realizam menos trabalho. Nessa situação, vale questionar se você realmente precisa deles nesse nível.

Comprima o que é compressível, não o que é acessado com frequência

A compressão é uma decisão por índice. Dados de alta entropia, como uma assinatura de 64 bytes, não podem ser comprimidos abaixo de uma proporção de 1,0, o que significa que a compressão apenas acrescentaria o custo de descompressão ao caminho crítico de cada busca. Por isso, definimos a compressão como None.

Em contrapartida, os dados de blocos são bem comprimidos por serem mais volumosos e repetitivos. Por isso, nosso índice slot -> bloco usa zstd. Observe que ambos os índices usam o mesmo banco de dados, mas se beneficiam de escolhas diferentes. Isso é determinado exclusivamente pela possibilidade de comprimir os bytes e por o índice ser limitado por latência ou armazenamento.

Essa divisão por índice é uma parte importante do motivo pelo qual conseguimos reduzir nosso volume de ~330 TB para ~190 TB sem prejudicar a latência das buscas pontuais.

Considere a E/S direta

Fazemos leituras com E/S direta e também a usamos para flush e compactação. Nessa escala, o cache de páginas do sistema operacional compete com nosso próprio cache de blocos pela mesma RAM e, para buscas pontuais aleatórias, esse cache duplicado é em grande parte um desperdício — preferimos manter um único cache de blocos grande que ofereça latência previsível.

Observe que isso depende da carga de trabalho:

A E/S direta pode prejudicar configurações com muitas varreduras ou poucos recursos, então vale a pena realizar testes A/B em vez de adotá-la sem avaliar. 

Escolha um cache que resista à contenção

Sob alta carga simultânea em chaves acessadas com frequência, o cache LRU compartilhado padrão se torna um gargalo de contenção de locks. Usamos o HyperClockCache do RocksDB para os índices mais acessados, que resiste muito melhor quando muitas threads consultam simultaneamente as mesmas entradas populares.

Paralelize buscas de múltiplas chaves em vez de serializá-las

Em vez de serializar N leituras, o RocksDB pode emitir várias operações de E/S simultaneamente por meio de io_uring e permitir que sejam concluídas em paralelo. Para um método como getTransactionsForAddress, que se desdobra em muitas buscas pontuais subjacentes, essa é a diferença entre uma latência que cresce com o número de chaves e uma latência que permanece aproximadamente igual.

O RocksDB oferece todas as possibilidades, mas não presume nada sobre seus dados. Nossos ganhos vêm de entender nossos padrões de acesso e ajustar cada índice ao seu próprio formato, em vez de buscar uma única configuração global que seja boa em tudo.

O que estamos buscando construir

Hoje, o arquivamento é executado em nossas regiões maiores, como EWR, FRA e Tóquio. Agora que o mecanismo de armazenamento retorna buscas em microssegundos e satura nossas placas de rede, o banco de dados não é mais o que nos tira o sono; resolver um gargalo costuma revelar o próximo.

Quando uma busca se torna praticamente gratuita, o custo dominante deixa de ser o software e passa a ser a distância entre o usuário e a máquina. Uma solicitação ainda precisa viajar de onde o usuário estiver até onde reside nosso backend e depois voltar. Nesse ponto, você está lutando contra a física.

Esse é o problema que o Gatekeeper enfrenta.

Gatekeeper é nosso gateway de edge desenvolvido internamente, escrito em Rust sobre o Hyper, que encerra conexões perto dos usuários e roteia cada solicitação para nosso backend pelo caminho disponível mais curto. É onde a batalha pela latência acontece agora: pooling de conexões, ajustes de TLS e sockets, roteamento com reconhecimento de proximidade e integridade e implantações sem indisponibilidade em toda a nossa infraestrutura global — tudo para eliminar milissegundos do caminho até os bytes que o arquivamento já disponibiliza em microssegundos.

Tornar uma única solicitação rápida é um problema de banco de dados. Tornar todas as solicitações rápidas, de qualquer lugar do mundo, sem janelas de manutenção e sem conexões interrompidas, é um problema totalmente diferente. E é nele que estamos focados agora. 

É isso que queremos dizer com inovar na camada de leitura da Solana: acertar em todas as camadas, desde o mecanismo de armazenamento que responde às buscas até o gateway de edge que determina a rapidez com que a resposta chega ao usuário final.

No fim, o arquivamento se resume a buscas pontuais por chaves aleatórias. Esse é o padrão de acesso que compromete um mecanismo colunar por razões estruturais. Não é uma questão de ajuste, sharding ou de como as visualizações são ordenadas. As limitações que encontramos no ClickHouse são inerentes a essa escolha, e não consequências acidentais da configuração. A carga de trabalho histórica da Solana é definida por seu padrão de acesso mais difícil, não pelo mais fácil. 

Na escala de uma rede que busca se tornar a camada de liquidação das finanças globais, a camada de leitura não pode ser apenas uma versão mais bem ajustada daquela que já superamos. Instituições e aplicações que dependem de consultas históricas avançadas precisam dos métodos, da cobertura e das latências que a pilha padrão não consegue oferecer. A camada de leitura da Solana deve ser construída desde o início sobre uma base adequada ao caso mais difícil. Essa foi a aposta que fizemos com o RocksDB, e esse é o padrão que exigimos de todo o resto.

Se você tem interesse em construir o futuro das finanças em grande escala, venha construí-lo conosco. A Solana avança rapidamente para se tornar a camada de liquidação das finanças globais, e o arquivamento é apenas uma peça de um quebra-cabeça muito maior. Se você se interessa por compactação LSM e ajustes por índice ou por resolver problemas complexos de sistemas em alguns dos melhores hardwares que o dinheiro pode comprar, você se sentirá em casa aqui.

Temos vagas abertas em toda a nossa equipe de engenharia. Veja todas as oportunidades em helius.dev/careers.

Assine a Helius

Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos

Imagem ampliada