17 minutes
Graph Node
O Node do The Graph (Graph Node) é o componente que indexa subgraphs e disponibiliza os dados resultantes a queries (consultas de dados) através de uma API GraphQL. Assim, ele é central ao stack dos indexers, e é crucial fazer operações corretas com um node Graph para executar um indexer com êxito.
Isto fornece um resumo contextual do Graph Node e algumas das opções mais avançadas disponíveis para indexadores. Para mais instruções e documentação, veja o repositório do Graph Node.
Graph Node
O Graph Node é a implementação de referência para indexar Subgraphs na The Graph Network (rede do The Graph); fazer conexões com clientes de blockchain; indexar subgraphs; e disponibilizar dados indexados para queries.
O Graph Node (e todo o stack dos indexadores) pode ser executado em um sistema bare-metal ou num ambiente na nuvem. Esta flexibilidade do componente central de indexing é importante para a robustez do Protocolo The Graph. Da mesma forma, um Graph Node pode ser construído do código fonte ou os indexadores podem usar uma das imagens disponíveis no Docker.
Banco de dados PostgreSQL
O armazenamento principal para o Graph Node. É aqui que são guardados dados de subgraph, assim como metadados sobre subgraphs e dados de rede agnósticos a subgraphs, como o cache de blocos e o cache eth_call.
Clientes de rede
Para indexar uma rede, o Graph Node precisa de acesso a um cliente de rede através de uma API JSON-RPC compatível com EVM. Esta RPC (chamada de processamento remoto) pode se conectar a um único cliente de Ethereum; ou o setup pode ser mais complexo, de modo a carregar saldos em múltiplos clientes.
Enquanto alguns subgraphs exigem apenas um node completo, alguns podem ter recursos de indexação que precisem de funções adicionais de RPC (chamadas de procedimento remoto). Especificamente, subgraphs que usam o eth_calls
como parte da indexação exigirão um node de arquivo que apoie o EIP-1898; e subgraphs com callHandlers
, ou blockHandlers
com um filtro call
, exigem apoio ao trace_filter
(veja a documentação sobre o trace module (módulo de rastreio) aqui).
Firehoses de Rede - um Firehose é um serviço de gRPC (chamadas de procedimento remoto - Google) que fornece uma transmissão ordenada — mas consciente de forks — de blocos, feito pelos programadores centrais do The Graph para permitir indexação em escala mais eficiente. Isto não é um requisito atual para Indexadores, mas é ideal que os mesmos experimentem a tecnologia, antes do apoio total à rede. Leia mais sobre o Firehose aqui.
Nodes IPFS
Os metadados de lançamento de subgraph são armazenados na rede IPFS. O Graph Node acessa primariamente o node IPFS durante o lançamento do subgraph, para retirar o manifest e todos os arquivos ligados. Os indexadores de rede não precisam hospedar seu próprio node IPFS. Um node IPFS para a rede é hospedado em https://ipfs.network.thegraph.com.
Servidor de métricas Prometheus
O Graph Node pode, opcionalmente, logar métricas a um servidor de métricas Prometheus para permitir funções de relatórios e monitorado.
Getting started from source
Install prerequisites
-
Rust
-
PostgreSQL
-
IPFS
-
Additional Requirements for Ubuntu users - To run a Graph Node on Ubuntu a few additional packages may be needed.
1sudo apt-get install -y clang libpq-dev libssl-dev pkg-config
Setup
- Start a PostgreSQL database server
1initdb -D .postgres2pg_ctl -D .postgres -l logfile start3createdb graph-node
-
Clone Graph Node repo and build the source by running
cargo build
-
Now that all the dependencies are setup, start the Graph Node:
1cargo run -p graph-node --release -- \2 --postgres-url postgresql://[USERNAME]:[PASSWORD]@localhost:5432/graph-node \3 --ethereum-rpc [NETWORK_NAME]:[URL] \4 --ipfs https://ipfs.network.thegraph.com
Como começar com Kubernetes
Veja um exemplo completo de configuração do Kubernetes no repositório do indexer.
Os básicos do Kubernetes
Durante a execução, o Graph Node expõe as seguintes portas:
Port | Purpose | Routes | CLI Argument | Environment Variable |
---|---|---|---|---|
8000 | GraphQL HTTP server (for subgraph queries) | /subgraphs/id/… /subgraphs/name/…/… | —http-port | - |
8001 | GraphQL WS (for subgraph subscriptions) | /subgraphs/id/… /subgraphs/name/…/… | —ws-port | - |
8020 | JSON-RPC (for managing deployments) | / | —admin-port | - |
8030 | Subgraph indexing status API | /graphql | —index-node-port | - |
8040 | Prometheus metrics | /metrics | —metrics-port | - |
Importante: Cuidado ao expor portas publicamente — portas de administração devem ser trancadas a sete chaves. Isto inclui o endpoint JSON-RPC do Graph Node.
Configurações avançadas do Graph Node
Basicamente, o Graph Node pode ser operado com uma única instância de Graph Node, um único banco de dados PostgreSQP, e os clientes de rede como exigidos pelos subgraphs a serem indexados.
Este setup pode ser escalado horizontalmente, com a adição de vários Graph Nodes e bancos de dados para apoiá-los. Utilizadores mais avançados podem tomar vantagem de algumas das capacidades de escala horizontal do Graph Node, assim como algumas das opções de configuração mais avançadas, através do arquivo config.toml
e as variáveis de ambiente do Graph Node.
config.toml
Um arquivo de configuração TOML pode ser usado para fazer configurações mais complexas do que as expostas na CLI. O local do arquivo é passado com a linha de comando —config.
Ao usar um arquivo de configuração, não é possível usar as opções —postgres-url, —postgres-secondary-hosts, e —postgres-host-weights.
É possível fornecer um arquivo mínimo config.toml
; o seguinte arquivo é o equivalente à opção de linha de comando —postgres-url:
1[store]2[store.primary]3connection="<.. argumento postgres-url ..>"4[deployment]5[[deployment.rule]]6indexers = [ "<.. lista de todos os nodes de indexação ..>" ]
A documentação completa do config.toml
pode ser encontrada nos documentos do Graph Node.
Múltiplos Graph Nodes
A indexação de Graph Nodes pode ser escalada horizontalmente, com a execução de várias instâncias de Graph Node para separar indexação de queries em nodes diferentes. Isto é possível só com a execução de Graph Nodes, configurados com um node_id
diferente na inicialização (por ex. no arquivo Docker Compose), que pode então ser usado no arquivo config.toml
para especificar nodes dedicados de query, ingestores de blocos e separar subgraphs entre nódulos com regras de lançamento.
Note que vários Graph Nodes podem ser configurados para usar o mesmo banco de dados — que, por conta própria, pode ser escalado horizontalmente através do sharding.
Regras de lançamento
Levando em conta vários Graph Nodes, é necessário gerir o lançamento de novos subgraphs para que o mesmo subgraph não seja indexado por dois nodes diferentes, o que levaria a colisões. Isto é possível regras de lançamento, que também podem especificar em qual shard
os dados de um subgraph devem ser armazenados, caso seja usado o sharding de bancos de dados. As regras de lançamento podem combinar com o nome do subgraph e com a rede que o lançamento indexa para fazer uma decisão.
Exemplo de configuração de regra de lançamento:
1[deployment]2[[deployment.rule]]3match = { name = "(vip|important)/.*" }4shard = "vip"5indexers = [ "index_node_vip_0", "index_node_vip_1" ]6[[deployment.rule]]7match = { network = "kovan" }8# No shard, so we use the default shard called 'primary'9indexers = [ "index_node_kovan_0" ]10[[deployment.rule]]11match = { network = [ "xdai", "poa-core" ] }12indexers = [ "index_node_other_0" ]13[[deployment.rule]]14# There's no 'match', so any subgraph matches15shards = [ "sharda", "shardb" ]16indexers = [17 "index_node_community_0",18 "index_node_community_1",19 "index_node_community_2",20 "index_node_community_3",21 "index_node_community_4",22 "index_node_community_5"23 ]
Saiba mais sobre regras de lançamento aqui.
Nodes dedicados de query
Os nodes podem ser configurados explicitamente para fins de query ao incluir o seguinte no arquivo de configuração:
1[general]2query = "<regular expression>"
Qualquer node cujo —node-id combina com a expressão regular será programado para responder apenas a queries.
Escalamento de bancos de dados via sharding
Para a maioria dos casos de uso, um único banco de dados Postgres é suficiente para apoiar uma instância de graph-node. Quando uma instância de graph-node cresce mais que um único banco Postgres, é possível dividir o armazenamento dos dados do graph-node entre múltiplos bancos Postgres. Todos os bancos de dados, juntos, formam o armazenamento da instância do graph-node. Cada banco de dados individual é chamado de shard.
Os shards servem para dividir lançamentos de subgraph em múltiplos bancos de dados, e podem também ser configurados para usar réplicas a fim de dividir a carga de query entre bancos de dados. Isto inclui a configuração do número de conexões disponíveis do banco que cada graph-node
deve manter em seu pool de conexão para cada banco, o que fica cada vez mais importante conforme são indexados mais subgraphs.
O sharding torna-se útil quando o seu banco de dados existente não aguenta o peso do Graph Node, e quando não é mais possível aumentar o tamanho do banco.
Geralmente, antes de começar com shards, é melhor maximizar o tamanho de um único banco de dados. Uma exceção é onde o tráfego de queries é dividido de maneira muito desigual entre subgraphs; nestes casos, pode ser de bom valor que os subgraphs de alto volume sejam mantidos em um shard e todo o resto em outro, porque aquele setup aumenta muito a chance dos dados para os subgraphs de alto volume permanecerem no cache db-internal e não serem substituídos por dados de menor prioridade nos subgraphs de baixo volume.
Em termos de configuração de conexões, comece com o max_connections no postgresql.conf configurado em 400 (ou talvez até 200) e preste atenção nas métricas do Prometheus store_connection_wait_time_ms e store_connection_checkout_count. Tempos de espera óbvios (acima de 5ms) indicam que há poucas conexões disponíveis; também podem ser causados por atividade excessiva no banco de dados (como uso alto de CPU). Mas caso o banco de dados pareça estável fora isto, os tempos de espera longos indicam uma necessidade de aumento no número de conexões. Na configuração, há um limite máximo de conexões que cada instância graph-node pode usar, e o Graph Node não manterá conexões abertas caso não sejam necessárias.
Veja mais sobre configurações de armazenamento aqui.
Ingestão dedicada de blocos
Caso hajam múltiplos nodes configurados, especifique um node a ser responsável pela ingestão de novos blocos, para que todos os nodes de indexação configurados não consultem o topo da cadeia. Isto é feito como parte do namespace chains
, a especificar o node_id
usado para ingestão de blocos:
1[chains]2ingestor = "block_ingestor_node"
Apoio a múltiplas redes
O Graph Protocol só aumenta o número de redes, com apoio a recompensas de indexação, e existem muitos subgraphs a indexarem redes não apoiadas que um indexador gostaria de processar. O arquivo config.toml permite a configuração expressiva e flexível de:
- Múltiplas redes
- Múltiplos provedores por rede (isto pode permitir a separação de peso entre eles, e pode permitir a configuração de nodes completos além de nodes de arquivo; o Graph Node prefere provedores mais baratos, caso permita uma carga de trabalho).
- Detalhes adicionais de provedor, como recursos, autenticação e tipo (para apoio experimental ao Firehose)
A seção [chains]
controla os provedores de ethereum a que o graph-node se conecta, e o local de armazenamento de blocos e outros metadados para cada chain. O seguinte exemplo configura duas chains, mainnet e kovan, onde blocos para a mainnet são armazenados no shard vip e blocos para kovan vão para o shard primário. A chain da mainnet pode usar dois provedores diferentes, sendo que o kovan só tem um provedor.
1[chains]2ingestor = "block_ingestor_node"3[chains.mainnet]4shard = "vip"5provider = [6 { label = "mainnet1", url = "http://..", features = [], headers = { Authorization = "Bearer foo" } },7 { label = "mainnet2", url = "http://..", features = [ "archive", "traces" ] }8]9[chains.kovan]10shard = "primary"11provider = [ { label = "kovan", url = "http://..", features = [] } ]
Veja mais sobre configurações de provedor aqui.
Variáveis de ambiente
O Graph Node apoia vários variáveis de ambiente que podem ativar funções ou mudar o seu comportamento. Mais informações aqui.
Lançamento contínuo
Os utilizadores a operar um setup de indexing escalado, com configurações avançadas, podem ganhar mais ao gerir os seus Graph Nodes com o Kubernetes.
- O repositório do indexer tem um exemplo de referência de Kubernetes
- O Launchpad é um conjunto kit de ferramentas para executar um Indexer do Graph Protocol no Kubernetes, mantido pelo GraphOps. Ele fornece um conjunto de charts Helm e uma CLI para administrar um lançamento de Graph Node.
Como gerir o Graph Node
Dado um Graph Node (ou Nodes!) em execução, o desafio torna-se gerir subgraphs lançados entre estes nodes. O Graph Node tem uma gama de ferramentas para ajudar a direção de subgraphs.
Logging
Os logs do Graph Node podem fornecer informações úteis, para debug e otimização — do Graph Node e de subgraphs específicos. O Graph Node apoia níveis diferentes de logs através da variável de ambiente GRAPH_LOG
, com os seguintes níveis: error
, warn
, info
, debug
ou trace
.
Além disto, configurar o GRAPH_LOG_QUERY_TIMING
para gql
fornece mais detalhes sobre o processo de queries no GraphQL (porém, isto criará um grande volume de logs).
Monitoração e alertas
Naturalmente, o Graph Node fornece as métricas através do endpoint Prometheus na porta 8040. Estas métricas podem ser visualizadas no Grafana.
O repositório do indexer fornece um exemplo de configuração do Grafana.
Graphman
O graphman
é uma ferramenta de manutenção para o Graph Node, que ajuda com o diagnóstico e a resolução de tarefas diferentes, sejam excecionais ou no dia a dia.
O comando graphman
é incluído nos containers oficiais, e pode ser executado com o docker exec
no seu container de graph-node
. Ele exige um arquivo config.toml
.
A documentação completa dos comandos do graphman
está no repositório do Graph Node. Veja o /docs/graphman.md no /docs
do Graph Node
Como trabalhar com subgraphs
API de estado de indexação
Inicialmente disponível na porta 8030/graphql, a API de estado de indexação expõe uma gama de métodos para conferir o estado da indexação para subgraphs diferentes, conferir provas de indexação, inspecionar características de subgraphs, e mais.
Veja o schema completo aqui.
Desempenho da indexação
Há três partes separadas no processo de indexação:
- Retirar eventos de interesse do provedor
- Processar eventos conforme os handlers apropriados (isto pode envolver chamar a chain para o estado, e retirar dados do armazenamento)
- Escrever os dados resultantes ao armazenamento
Estes estágios são segmentados (por ex., podem ser executados em paralelo), mas são dependentes um no outro. Quando há demora em indexar, a causa depende do subgraph específico.
Causas comuns de lentidão na indexação:
- Tempo para encontrar eventos relevantes na chain (handlers de chamada em particular podem demorar mais, dada a dependência no
trace_filter
) - Fazer muitos
eth_calls
como parte de handlers - Excesso de interações no armazenamento durante a execução
- Muitos dados para guardar no armazenamento
- Muitos eventos a serem processados
- Conexão lenta ao banco de dados, para nodes lotados
- Atraso do próprio provedor em relação ao topo da chain
- Atraso em retirar novos recibos do topo da chain do provedor
As métricas de indexação de subgraph podem ajudar a diagnosticar a causa raiz do atraso na indexação. Em alguns casos, o problema está no próprio subgraph, mas em outros, melhorar provedores de rede, reduzir a contenção no banco de dados, e outras melhorias na configuração podem aprimorar muito o desempenho da indexação.
Subgraphs falhos
É possível que subgraphs falhem durante a indexação, caso encontrem dados inesperados; algum componente não funcione como o esperado; ou se houver algum bug nos handlers de eventos ou na configuração. Geralmente, há dois tipos de falha:
- Falhas determinísticas: Falhas que não podem ser resolvidas com outras tentativas
- Falhas não determinísticas: podem ser resumidas em problemas com o provedor ou algum erro inesperado no Graph Node. Quando ocorrer uma falha não determinística, o Graph Node reiniciará os handlers falhos e recuará gradualmente.
Em alguns casos, uma falha pode ser resolvida pelo indexador (por ex. a indexação falhou por ter o tipo errado de provedor, e necessita do correto para continuar). Porém, em outros, é necessária uma alteração no código do subgraph.
Falhas determinísticas são consideradas “finais”, com uma Prova de Indexação (POI) gerada para o bloco falho; falhas não determinísticas não são finais, como há chances do subgraph superar a falha e continuar a indexar. Às vezes, o rótulo de “não determinístico” é incorreto e o subgraph não tem como melhorar do erro; estas falhas devem ser relatadas como problemas no repositório do Graph Node.
Cache de blocos e chamadas
O Graph Node cacheia certos dados no armazenamento para poupar um refetching do provedor. São cacheados os blocos e os resultados do eth_calls
(este último, cacheado a partir de um bloco específico). Este caching pode aumentar dramaticamente a velocidade de indexação durante a “ressincronização” de um subgraph levemente alterado.
Porém, em algumas instâncias, se um node Ethereum tiver fornecido dados incorretos em algum período, isto pode entrar no cache, o que causa dados incorretos ou subgraphs falhos. Neste caso, os indexadores podem usar o graphman
para limpar o cache envenenado e rebobinar os subgraphs afetados, que retirarão dados frescos do provedor (idealmente) saudável.
Caso haja uma suspeita de inconsistência no cache de blocos, como a falta de um evento tx receipt missing
:
- Digite
graphman chain list
para buscar o nome da chain. graphman chain check-blocks <CHAIN> by-number <NUMBER>
verificará se o bloco no cache corresponde ao provedor; se não for o caso, o bloco será apagado do cache.- Se houver uma diferença, pode ser mais seguro truncar o cache inteiro com
graphman chain truncate <CHAIN>
. - Caso o bloco corresponda ao provedor, então o problema pode ser debugado em frente ao provedor.
- Se houver uma diferença, pode ser mais seguro truncar o cache inteiro com
Erros e problemas de query
Quando um subgraph for indexado, os indexadores podem esperar servir consultas através do endpoint dedicado de consultas do subgraph. Se o indexador espera servir volumes significantes de consultas, é recomendado um node dedicado a queries; e para volumes muito altos, podem querer configurar réplicas de shard para que os queries não impactem o processo de indexação.
Porém, mesmo com um node dedicado a consultas e réplicas deste, certos queries podem demorar muito para executar; em alguns casos, aumentam o uso da memória e pioram o tempo de query para outros utilizadores.
Não há uma “bala de prata”, mas sim uma gama de ferramentas para prevenir, diagnosticar e lidar com queries lentos.
Caching de query
O Graph Node naturalmente cacheia queries no GraphQL, o que pode reduzir muito a carga no banco de dados. Isto pode ser configurado mais profundamente com GRAPH_QUERY_CACHE_BLOCKS
e GRAPH_QUERY_CACHE_MAX_MEM
— leia mais aqui.
Análise de queries
Queries problemáticos tendem a surgir em uma de duas maneiras. Em alguns casos, os próprios utilizadores relatam que um certo query está lento; neste caso, o desafio é diagnosticar a razão para a lentidão, seja um problema geral ou específico àquele subgraph ou query. E depois, claro, resolvê-lo se possível.
Em outros casos, o gatilho pode ser o excesso de uso de memória em um node de query; no caso, o primeiro desafio é identificar a consulta que causou este problema.
Os indexadores podem processar e resumir os logs de consulta do Graph Node com o comando qlog. Ativar GRAPH_LOG_QUERY_TIMING
também ajuda a identificar e debugar consultas lentas.
Considerando um query lento, os indexadores têm algumas opções. Claro que podem alterar o seu modelo de custo, para aumentar o custo de enviar o query problemático. A frequência dessa query pode diminuir, mas isto não costuma resolver a raiz do problema.
Otimização tipo-conta
Tábuas de base de dados que armazenam entidades geralmente vêm em duas variedades: ‘tipo-transação’, em que as entidades nunca são atualizadas depois da criação (por ex., armazenam algo como uma lista de transações financeiras); e ‘tipo-conta’, em que as entidades atualizam com frequência (ex.: armazenam algo como contas financeiras, que são modificadas sempre que uma transação é gravada). Tábuas tipo-contas são caracterizadas por ter muitas versões de entidade, mas poucas entidades distintas. O número de entidades distintas nessas tábuas costuma ser 1% do total de linhas (versões de entidade)
Para tábuas tipo-conta, o graph-node
pode gerar consultas que aproveitam detalhes de como o Postgres armazena dados tão frequentemente alterados — em particular, que todas as versões para blocos recentes estão numa pequena subsecção do armazenamento total para uma tábua do tipo.
O comando graphman stats show <sgdNNNN
> mostra, para cada tipo/tábua de entidade em um lançamento, quantas entidades distintas e versões de entidades estão em cada tábua. Esses dados são baseados em estimativas internas do Postgres e são necessariamente imprecisos, com uma grande margem de erro. Um -1
na coluna entities
significa que o Postgres acredita que todas as linhas contém uma entidade distinta.
Em geral, tábuas em que o número de entidades distintas é menos de 1% do total de linhas/versões de entidade são boas candidatas para a otimização tipo-conta. Quando o resultado do graphman stats show
indica que uma tábua pode se beneficiar desta otimização, ativar o graphman stats show <sgdNNN> <table>
fará uma contagem completa da tábua - que pode ser lenta, mas dá uma medida precisa da proporção entre entidades distintas e total de versões.
Quando uma tábua for determinada como “tipo-conta”, executar o graphman stats account-like <sgdNNN>.<table>
ativará a otimização tipo-conta para queries frente àquela tábua. A otimização pode ser desativada novamente com graphman stats account-like --clear <sgdNNN>.<table>
. Os nodes de consulta levam até 5 minutos para perceber que a otimização foi ligada ou desligada. Após ativar a otimização, verifique se a mudança não desacelera os queries para aquela tábua. Caso tenha configurado o Grafana para monitorar o Postgres, muitos queries lentos podem aparecer no pg_stat_activity
, com demora de vários segundos. Neste caso, a otimização precisa ser desativada novamente.
Para subgraphs parecidos com o Uniswap, as tábuas pair
e token
são ótimas para esta otimização, e podem ter efeitos surpreendentes na carga do banco de dados.
Como remover subgraphs
Esta é uma funcionalidade nova, que estará disponível no Graph Node 0.29.x
Em certo ponto, o indexador pode querer remover um subgraph. É só usar o graphman drop
, que apaga um lançamento e todos os seus dados indexados. O lançamento pode ser especificado como o nome de um subgraph, um hash IPFS Qm..
, ou o namespace de banco de dados sgdNNN
. Mais documentos sobre o processo aqui.