Como Operar um Graph Node
Reading time: 17 min
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 providencia um resumo contextual do Graph Node, e algumas das opções mais avançadas disponíveis para indexers. Mais instruções, e documentação, podem no .
O é 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 do indexador) pode ser executado em bare metal ou num ambiente na nuvem. Esta flexibilidade do componente central de indexing é crucial para a robustez do Protocolo The Graph. Da mesma forma, um Graph Node pode ser ou os indexadores podem usar uma das .
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.
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 indexing que exijam funcionalidades adicionais de RPC. Especificamente, subgraphs que usam o eth_calls
como parte do indexing exigirão um node de arquivo que apoie o ; e subgraphs com callHandlers
, ou blockHandlers
com um filtro call
, exigem apoio ao trace_filter
().
Firehoses de Rede - um Firehose é um serviço gRPC que providencia uma transmissão ordenada, mas consciente de forks, de blocos, feito pelos programadores centrais do The Graph para melhorar o apoio a indexing eficiente em escala. Isto não é um requisito atual para Indexadores, mas vale os mesmos se familiarizarem com a tecnologia, antes do apoio total à rede. Leia mais sobre o Firehose .
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 .
O Graph Node pode, opcionalmente, logar métricas a um servidor de métricas Prometheus para permitir funções de relatórios e monitorado.
-
Rust
-
PostgreSQL
-
IPFS
-
Requisitos Adicionais para utilizadores de Ubuntu — A execução de um Graph Node no Ubuntu pode exigir pacotes adicionais.
sudo apt-get install -y clang libpq-dev libssl-dev pkg-config
- Comece um servidor de banco de dados PostgreSQL
initdb -D .postgrespg_ctl -D .postgres -l logfile startcreatedb graph-node
Clone o repositório do e construa a fonte executando o
cargo build
Agora que todas as dependências estão prontas, inicie o Graph Node:
cargo run -p graph-node --release -- \--postgres-url postgresql://[USERNAME]:[PASSWORD]@localhost:5432/graph-node \--ethereum-rpc [NETWORK_NAME]:[URL] \--ipfs https://ipfs.network.thegraph.com
Veja uma configuração de exemplo completa do Kubernetes no .
Durante a execução, o Graph Node expõe as seguintes portas:
Porta | Propósito | Rotas | Argumento CLI | Variável de Ambiente |
---|---|---|---|---|
8000 | Servidor HTTP GraphQL (para queries de subgraph) | /subgraphs/id/... /subgraphs/name/.../... | --http-port | - |
8001 | WS GraphQL (para inscrições a subgraphs) | /subgraphs/id/... /subgraphs/name/.../... | --ws-port | - |
8020 | JSON-RPC (para gerir lançamentos) | / | --admin-port | - |
8030 | API de status de indexamento do subgraph | /graphql | --index-node-port | - |
8040 | Métricas Prometheus | /metrics | --metrics-port | - |
Importante: Cuidado ao expor portas publicamente; as portas de administração devem ser trancadas a sete chaves. Isto inclui o endpoint JSON-RPC 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.
Um arquivo de configuração 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.
Um arquivo mínimo config.toml
pode ser fornecido; o seguinte arquivo é o equivalente à opção de linha de comando --postgres-url:
[store][store.primary]connection="<.. postgres-url argument ..>"[deployment][[deployment.rule]]indexers = [ "<.. list of all indexing nodes ..>" ]
Veja a documentação completa do config.toml
na documentação do .
A indexação de Graph Nodes pode escalar horizontalmente, com a execução de várias instâncias de Graph Node para separar indexação de queries em nodes diferentes. Isto é facilmente possível 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 , e separar subgraphs entre nódulos com .
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.
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:
[deployment][[deployment.rule]]match = { name = "(vip|important)/.*" }shard = "vip"indexers = [ "index_node_vip_0", "index_node_vip_1" ][[deployment.rule]]match = { network = "kovan" }# No shard, so we use the default shard called 'primary'indexers = [ "index_node_kovan_0" ][[deployment.rule]]match = { network = [ "xdai", "poa-core" ] }indexers = [ "index_node_other_0" ][[deployment.rule]]# There's no 'match', so any subgraph matchesshards = [ "sharda", "shardb" ]indexers = ["index_node_community_0","index_node_community_1","index_node_community_2","index_node_community_3","index_node_community_4","index_node_community_5"]
Leia mais sobre as regras de lançamento .
Os nodes podem ser configurados explicitamente para fins de query ao incluir o seguinte no arquivo de configuração:
[general]query = "<regular expression>"
Qualquer node cujo --node-id combina com a expressão regular será programado para responder apenas a queries.
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 são úteis para dividir lançamentos de subgraph em múltiplos bancos de dados, e podem também ser configurados a usarem réplicas para 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.
Leia mais sobre configuração de armazenamento .
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:
[chains]ingestor = "block_ingestor_node"
O Graph Protocol só aumenta o número de redes com apoio a recompensas de indexing, e existem muitos subgraphs a indexarem redes não apoiadas que um indexer 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.
[chains]ingestor = "block_ingestor_node"[chains.mainnet]shard = "vip"provider = [{ label = "mainnet1", url = "http://..", features = [], headers = { Authorization = "Bearer foo" } },{ label = "mainnet2", url = "http://..", features = [ "archive", "traces" ] }][chains.kovan]shard = "primary"provider = [ { label = "kovan", url = "http://..", features = [] } ]
Leia mais sobre configuração de provedores .
O Graph Node apoia uma gama de variáveis de ambiente, que podem permitir recursos ou mudar o comportamento do Graph Node. Leia mais sobre as variáveis .
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 de indexadores tem uma
- O é um conjunto de ferramentas para executar um Indexer do Graph Protocol no Kubernetes, mantido pelo GraphOps. Ele providencia um conjunto de charts Helm e uma CLI para gerir um lançamento de 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.
Os logs do Graph Node podem fornecer informações úteis, para o debug, e a 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
em gql
fornece mais detalhes sobre o processo de queries no GraphQL (porém, isto causará um grande volume de logs).
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 providencia um .
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.
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.
O schema completo está disponível .
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 ser lentos, dada a dependência no
trace_filter
) - Fazer uma grande quantidade de
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.
É 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.
O Graph Node cacheia certos dados no armazenamento para poupar um refetching do provedor. São cacheados não são blocos, como também 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 saudável (idealmente).
Caso haja uma suspeita de inconsistência no cache de blocos, como a falta de um evento tx receipt missing
:
graphman chain list
para achar o nome da chain.graphman chain check-blocks <CHAIN> by-number <NUMBER>
confere se o bloco no cache corresponde ao provedor, e apaga o bloco do cache se não for o caso.- Caso haja 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.
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.
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 .
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 . 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.
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 retarda consultas 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 candidatas para esta otimização, e podem ter efeitos surpreendentes na carga do banco de dados.
Esta é uma funcionalidade nova, que estará disponível no Graph Node 0.29.x
Em certo ponto, o indexador pode querer remover um subgraph dado. É 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 .