17 minutes
Graph Node
Graph Node è il componente che indica i subgraph e rende i dati risultanti disponibili per l’interrogazione tramite API GraphQL. È quindi centrale per lo stack degli indexer, ed inoltre il corretto funzionamento di Graph Node è cruciale per il buon funzionamento di un indexer di successo.
This provides a contextual overview of Graph Node, and some of the more advanced options available to indexers. Detailed documentation and instructions can be found in the Graph Node repository.
Graph Node
Graph Node is the reference implementation for indexing Subgraphs on The Graph Network, connecting to blockchain clients, indexing subgraphs and making indexed data available to query.
Graph Node (and the whole indexer stack) can be run on bare metal, or in a cloud environment. This flexibility of the central indexing component is crucial to the robustness of The Graph Protocol. Similarly, Graph Node can be built from source, or indexers can use one of the provided Docker Images.
Database PostgreSQL
È l’archivio principale del Graph Node, in cui vengono memorizzati i dati dei subgraph, i metadati sui subgraph e i dati di rete che non dipendono dal subgraph, come la cache dei blocchi e la cache eth_call.
Clienti della rete
Per indicizzare una rete, Graph Node deve accedere a un cliente di rete tramite un’API JSON-RPC compatibile con EVM. Questo RPC può connettersi a un singolo cliente o può essere una configurazione più complessa che bilancia il carico su più clienti.
While some subgraphs may just require a full node, some may have indexing features which require additional RPC functionality. Specifically subgraphs which make eth_calls
as part of indexing will require an archive node which supports EIP-1898, and subgraphs with callHandlers
, or blockHandlers
with a call
filter, require trace_filter
support (see trace module documentation here).
Network Firehoses - a Firehose is a gRPC service providing an ordered, yet fork-aware, stream of blocks, developed by The Graph’s core developers to better support performant indexing at scale. This is not currently an Indexer requirement, but Indexers are encouraged to familiarise themselves with the technology, ahead of full network support. Learn more about the Firehose here.
Nodi IPFS
I metadati di distribuzione del subgraph sono memorizzati sulla rete IPFS. The Graph Node accede principalmente al nodo IPFS durante la distribuzione del subgraph per recuperare il manifest del subgraph e tutti i file collegati. Gli indexer di rete non devono ospitare un proprio nodo IPFS. Un nodo IPFS per la rete è ospitato su https://ipfs.network.thegraph.com.
Server di metriche Prometheus
Per consentire il monitoraggio e la creazione di report, Graph Node può opzionalmente registrare le metriche su un server di metriche Prometheus.
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
Come iniziare con Kubernetes
A complete Kubernetes example configuration can be found in the indexer repository.
Porti
Quando è in funzione, Graph Node espone le seguenti porte:
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 | - |
Important: Be careful about exposing ports publicly - administration ports should be kept locked down. This includes the the Graph Node JSON-RPC endpoint.
Configurazione avanzata del Graph Node
Nella sua forma più semplice, Graph Node può essere utilizzato con una singola istanza di Graph Node, un singolo database PostgreSQL, un nodo IPFS e i client di rete richiesti dai subgraph da indicizzare.
This setup can be scaled horizontally, by adding multiple Graph Nodes, and multiple databases to support those Graph Nodes. Advanced users may want to take advantage of some of the horizontal scaling capabilities of Graph Node, as well as some of the more advanced configuration options, via the config.toml
file and Graph Node’s environment variables.
config.toml
A TOML configuration file can be used to set more complex configurations than those exposed in the CLI. The location of the file is passed with the —config command line switch.
Quando si usa un file di configurazione, non è possibile usare le opzioni —postgres-url, —postgres-secondary-hosts e —postgres-host-weights.
A minimal config.toml
file can be provided; the following file is equivalent to using the —postgres-url command line option:
1[store]2[store.primary]3connection="<.. postgres-url argument ..>"4[deployment]5[[deployment.rule]]6indexers = [ "<.. list of all indexing nodes ..>" ]
Full documentation of config.toml
can be found in the Graph Node docs.
Graph Node multipli
Graph Node indexing can scale horizontally, running multiple instances of Graph Node to split indexing and querying across different nodes. This can be done simply by running Graph Nodes configured with a different node_id
on startup (e.g. in the Docker Compose file), which can then be used in the config.toml
file to specify dedicated query nodes, block ingestors, and splitting subgraphs across nodes with deployment rules.
Si noti che più Graph Node possono essere configurati per utilizzare lo stesso database, che può essere scalato orizzontalmente tramite sharding.
Regole di distribuzione
Given multiple Graph Nodes, it is necessary to manage deployment of new subgraphs so that the same subgraph isn’t being indexed by two different nodes, which would lead to collisions. This can be done by using deployment rules, which can also specify which shard
a subgraph’s data should be stored in, if database sharding is being used. Deployment rules can match on the subgraph name and the network that the deployment is indexing in order to make a decision.
Esempio di configurazione della regola di distribuzione:
1[deployment]2[[deployment.rule]]3match = { name = "(vip|importante)/.*" }4shard = "vip"5indexers = [ "index_node_vip_0", "index_node_vip_1" ]6[[deployment.rule]]7match = { network = "kovan" }8# Nessun shard, quindi usiamo lo shard predefinito chiamato "primario".9indicizzatori = [ "index_node_kovan_0" ]10[[deployment.rule]]11match = { network = [ "xdai", "poa-core" ] }12indexers = [ "index_node_other_0" ]13[[deployment.rule]]14# Non c'è nessun "match", quindi qualsiasi sottografo corrisponde15shard = [ "sharda", "shardb" ]16indicizzatori = [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 "indice_nodo_comunità_5"23 ]
Read more about deployment rules here.
Nodi di query dedicati
I nodi possono essere configurati per essere esplicitamente nodi di query includendo quanto segue nel file di configurazione:
1[general]2query = "<regular expression>"
Ogni nodo il cui —node-id corrisponde all’espressione regolare sarà impostato per rispondere solo alle query.
Scalabilità del database tramite sharding
Per la maggior parte dei casi d’uso, un singolo database Postgres è sufficiente per supportare un’istanza del graph-node. Quando un’istanza del graph-node supera un singolo database Postgres, è possibile suddividere l’archiviazione dei dati del graph-node su più database Postgres. Tutti i database insieme formano lo store dell’istanza del graph-node. Ogni singolo database è chiamato shard.
Shards can be used to split subgraph deployments across multiple databases, and can also be used to use replicas to spread query load across databases. This includes configuring the number of available database connections each graph-node
should keep in its connection pool for each database, which becomes increasingly important as more subgraphs are being indexed.
Lo sharding diventa utile quando il database esistente non riesce a reggere il carico che Graph Node gli impone e quando non è più possibile aumentare le dimensioni del database.
In genere è meglio creare un singolo database il più grande possibile, prima di iniziare con gli shard. Un’eccezione è rappresentata dai casi in cui il traffico di query è suddiviso in modo molto disomogeneo tra i subgraph; in queste situazioni può essere di grande aiuto tenere i subgraph ad alto volume in uno shard e tutto il resto in un altro, perché questa configurazione rende più probabile che i dati per i subgraph ad alto volume rimangano nella cache interna del database e non vengano sostituiti da dati non necessari per i subgraph a basso volume.
Per quanto riguarda la configurazione delle connessioni, iniziare con max_connections in postgresql.conf impostato a 400 (o forse anche a 200) e osservare le metriche di Prometheus store_connection_wait_time_ms e store_connection_checkout_count. Tempi di attesa notevoli (qualsiasi cosa superiore a 5 ms) indicano che le connessioni disponibili sono troppo poche; tempi di attesa elevati possono anche essere causati da un database molto occupato (come un elevato carico della CPU). Tuttavia, se il database sembra altrimenti stabile, tempi di attesa elevati indicano la necessità di aumentare il numero di connessioni. Nella configurazione, il numero di connessioni che ogni istanza del graph-node può utilizzare è un limite massimo e Graph Node non manterrà aperte le connessioni se non ne ha bisogno.
Read more about store configuration here.
Ingestione di blocchi dedicati
If there are multiple nodes configured, it will be necessary to specify one node which is responsible for ingestion of new blocks, so that all configured index nodes aren’t polling the chain head. This is done as part of the chains
namespace, specifying the node_id
to be used for block ingestion:
1[chains]2ingestor = "block_ingestor_node"
Supporto di più reti
The Graph Protocol is increasing the number of networks supported for indexing rewards, and there exist many subgraphs indexing unsupported networks which an indexer would like to process. The config.toml
file allows for expressive and flexible configuration of:
- Reti multiple
- Fornitori multipli per rete (questo può consentire di suddividere il carico tra i fornitori e di configurare nodi completi e nodi di archivio, con Graph Node che preferisce i fornitori più economici se un determinato carico di lavoro lo consente).
- Ulteriori dettagli sul provider, come le caratteristiche, l’autenticazione e il tipo di provider (per il supporto sperimentale di Firehose)
The [chains]
section controls the ethereum providers that graph-node connects to, and where blocks and other metadata for each chain are stored. The following example configures two chains, mainnet and kovan, where blocks for mainnet are stored in the vip shard and blocks for kovan are stored in the primary shard. The mainnet chain can use two different providers, whereas kovan only has one provider.
1[catene]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 = [ "archivio", "tracce" ] } }8]9[catene.kovan]10shard = "primary"11provider = [ { label = "kovan", url = "http://..", features = [] } ]
Read more about provider configuration here.
Variabili d’ambiente
Graph Node supports a range of environment variables which can enable features, or change Graph Node behaviour. These are documented here.
Distribuzione continua
Gli utenti che gestiscono una configurazione di indicizzazione scalare con una configurazione avanzata possono trarre vantaggio dalla gestione dei Graph Node con Kubernetes.
- The indexer repository has an example Kubernetes reference
- Launchpad is a toolkit for running a Graph Protocol Indexer on Kubernetes maintained by GraphOps. It provides a set of Helm charts and a CLI to manage a Graph Node deployment.
Gestione del Graph Node
Dato un Graph Node (o più Graph Nodes!) in funzione, la sfida consiste nel gestire i subgraph distribuiti tra i nodi. Graph Node offre una serie di strumenti che aiutano a gestire i subgraph.
Logging
Graph Node’s logs can provide useful information for debugging and optimisation of Graph Node and specific subgraphs. Graph Node supports different log levels via the GRAPH_LOG
environment variable, with the following levels: error, warn, info, debug or trace.
In addition setting GRAPH_LOG_QUERY_TIMING
to gql
provides more details about how GraphQL queries are running (though this will generate a large volume of logs).
Monitoring & alerting
Graph Node fornisce le metriche tramite l’endpoint Prometheus sulla porta 8040. È possibile utilizzare Grafana per visualizzare queste metriche.
The indexer repository provides an example Grafana configuration.
Graphman
graphman
is a maintenance tool for Graph Node, helping with diagnosis and resolution of different day-to-day and exceptional tasks.
The graphman command is included in the official containers, and you can docker exec into your graph-node container to run it. It requires a config.toml
file.
Full documentation of graphman
commands is available in the Graph Node repository. See [/docs/graphman.md] (https://github.com/graphprotocol/graph-node/blob/master/docs/graphman.md) in the Graph Node /docs
Lavorare con i subgraph
Stato dell’indicizzazione API
Disponibile sulla porta 8030/graphql per impostazione predefinita, l’API dello stato di indicizzazione espone una serie di metodi per verificare lo stato di indicizzazione di diversi subgraph, controllare le prove di indicizzazione, ispezionare le caratteristiche dei subgraph e altro ancora.
The full schema is available here.
Prestazioni di indicizzazione
Il processo di indicizzazione si articola in tre parti distinte:
- Recuperare gli eventi di interesse dal provider
- Elaborare gli eventi in ordine con i gestori appropriati (questo può comportare la chiamata alla chain per lo stato e il recupero dei dati dall’archivio)
- Scrivere i dati risultanti nell’archivio
Questi stadi sono collegati tra loro (cioè possono essere eseguiti in parallelo), ma dipendono l’uno dall’altro. Se i subgraph sono lenti da indicizzare, la causa dipende dal subgraph specifico.
Cause comuni di lentezza dell’indicizzazione:
- Time taken to find relevant events from the chain (call handlers in particular can be slow, given the reliance on
trace_filter
) - Making large numbers of
eth_calls
as part of handlers - Una grande quantità di interazioni con l’archivio durante l’esecuzione
- Una grande quantità di dati da salvare nell’archivio
- Un numero elevato di eventi da elaborare
- Tempo di connessione al database lento, per i nodi affollati
- Il fornitore stesso è in ritardo rispetto alla testa della chain
- Lentezza nell’acquisizione di nuove ricevute dal fornitore alla testa della chain
Le metriche di indicizzazione dei subgraph possono aiutare a diagnosticare la causa principale della lentezza dell’indicizzazione. In alcuni casi, il problema risiede nel subgraph stesso, ma in altri, il miglioramento dei provider di rete, la riduzione della contesa del database e altri miglioramenti della configurazione possono migliorare notevolmente le prestazioni dell’indicizzazione.
I subgraph falliti
Durante l’indicizzazione, i subgraph possono fallire se incontrano dati inaspettati, se qualche componente non funziona come previsto o se c’è un bug nei gestori di eventi o nella configurazione. Esistono due tipi generali di errore:
- Guasti deterministici: si tratta di guasti che non possono essere risolti con tentativi di risposta
- Fallimenti non deterministici: potrebbero essere dovuti a problemi con il provider o a qualche errore imprevisto di Graph Node. Quando si verifica un errore non deterministico, Graph Node riprova i gestori che non hanno funzionato, riducendo il tempo a disposizione.
In alcuni casi, un errore può essere risolto dall’indexer (ad esempio, se l’errore è dovuto alla mancanza del tipo di provider giusto, l’aggiunta del provider richiesto consentirà di continuare l’indicizzazione). In altri casi, invece, è necessario modificare il codice del subgraph.
Deterministic failures are considered “final”, with a Proof of Indexing generated for the failing block, while non-deterministic failures are not, as the subgraph may manage to “unfail” and continue indexing. In some cases, the non-deterministic label is incorrect, and the subgraph will never overcome the error; such failures should be reported as issues on the Graph Node repository.
Cache dei blocchi e delle chiamate
Graph Node caches certain data in the store in order to save refetching from the provider. Blocks are cached, as are the results of eth_calls
(the latter being cached as of a specific block). This caching can dramatically increase indexing speed during “resyncing” of a slightly altered subgraph.
However, in some instances, if an Ethereum node has provided incorrect data for some period, that can make its way into the cache, leading to incorrect data or failed subgraphs. In this case indexers can use graphman
to clear the poisoned cache, and then rewind the affected subgraphs, which will then fetch fresh data from the (hopefully) healthy provider.
Se si sospetta un’incongruenza nella cache a blocchi, come ad esempio un evento di ricezione tx mancante:
graphman chain list
to find the chain name.graphman chain check-blocks <CHAIN> by-number <NUMBER>
will check if the cached block matches the provider, and deletes the block from the cache if it doesn’t.- If there is a difference, it may be safer to truncate the whole cache with
graphman chain truncate <CHAIN>
. - Se il blocco corrisponde al provider, è possibile eseguire il debug del problema direttamente sul provider.
- If there is a difference, it may be safer to truncate the whole cache with
Problemi ed errori di query
Una volta che un subgraph è stato indicizzato, gli indexer possono aspettarsi di servire le query attraverso l’endpoint di query dedicato al subgraph. Se l’indexer spera di servire un volume significativo di query, è consigliabile un nodo di query dedicato; in caso di volumi di query molto elevati, gli indexer potrebbero voler configurare shard di replica in modo che le query non abbiano un impatto sul processo di indicizzazione.
Tuttavia, anche con un nodo di query dedicato e le repliche, alcune query possono richiedere molto tempo per essere eseguite e, in alcuni casi, aumentare l’utilizzo della memoria e avere un impatto negativo sul tempo di query per gli altri utenti.
Non esiste una “pallottola d’argento”, ma una serie di strumenti per prevenire, diagnosticare e gestire le query lente.
Caching delle query
Graph Node caches GraphQL queries by default, which can significantly reduce database load. This can be further configured with the GRAPH_QUERY_CACHE_BLOCKS
and GRAPH_QUERY_CACHE_MAX_MEM
settings - read more here.
Analisi delle query
Le query problematiche emergono spesso in due modi. In alcuni casi, sono gli stessi utenti a segnalare la lentezza di una determinata query. In questo caso, la sfida consiste nel diagnosticare la ragione della lentezza, sia che si tratti di un problema generale, sia che si tratti di un problema specifico di quel subgraph o di quella query. E poi, naturalmente, risolverlo, se possibile.
In altri casi, il fattore scatenante potrebbe essere l’elevato utilizzo della memoria su un nodo di query, nel qual caso la sfida consiste nell’identificare la query che causa il problema.
Indexers can use qlog to process and summarize Graph Node’s query logs. GRAPH_LOG_QUERY_TIMING
can also be enabled to help identify and debug slow queries.
Con una query lenta, gli indexer hanno alcune opzioni. Naturalmente possono modificare il loro modello di costo, aumentando in modo significativo il costo di invio della query problematica. Questo può portare a una riduzione della frequenza della query. Tuttavia, questo spesso non risolve la causa principale del problema.
Ottimizzazione di tipo account
Le tabelle di database che memorizzano le entità sembrano essere generalmente di due tipi: “tipo transazioni”, in cui le entità, una volta create, non vengono mai aggiornate, cioè memorizzano qualcosa di simile a un elenco di transazioni finanziarie, e “tipo account”, in cui le entità vengono aggiornate molto spesso, cioè memorizzano qualcosa di simile a conti finanziari che vengono modificati ogni volta che viene registrata una transazione. Le tabelle di tipo account sono caratterizzate dal fatto di contenere un gran numero di versioni di entità, ma relativamente poche entità distinte. Spesso, in queste tabelle il numero di entità distinte è pari all’1% del numero totale di righe (versioni di entità)
For account-like tables, graph-node
can generate queries that take advantage of details of how Postgres ends up storing data with such a high rate of change, namely that all of the versions for recent blocks are in a small subsection of the overall storage for such a table.
The command graphman stats show <sgdNNNN
> shows, for each entity type/table in a deployment, how many distinct entities, and how many entity versions each table contains. That data is based on Postgres-internal estimates, and is therefore necessarily imprecise, and can be off by an order of magnitude. A -1
in the entities
column means that Postgres believes that all rows contain a distinct entity.
In general, tables where the number of distinct entities are less than 1% of the total number of rows/entity versions are good candidates for the account-like optimization. When the output of graphman stats show
indicates that a table might benefit from this optimization, running graphman stats show <sgdNNN> <table>
will perform a full count of the table - that can be slow, but gives a precise measure of the ratio of distinct entities to overall entity versions.
Once a table has been determined to be account-like, running graphman stats account-like <sgdNNN>.<table>
will turn on the account-like optimization for queries against that table. The optimization can be turned off again with graphman stats account-like --clear <sgdNNN>.<table>
It takes up to 5 minutes for query nodes to notice that the optimization has been turned on or off. After turning the optimization on, it is necessary to verify that the change does not in fact make queries slower for that table. If you have configured Grafana to monitor Postgres, slow queries would show up in pg_stat_activity
in large numbers, taking several seconds. In that case, the optimization needs to be turned off again.
For Uniswap-like subgraphs, the pair
and token
tables are prime candidates for this optimization, and can have a dramatic effect on database load.
Rimozione dei subgraph
Si tratta di una nuova funzionalità, che sarà disponibile in Graph Node 0.29.x
At some point an indexer might want to remove a given subgraph. This can be easily done via graphman drop
, which deletes a deployment and all it’s indexed data. The deployment can be specified as either a subgraph name, an IPFS hash Qm..
, or the database namespace sgdNNN
. Further documentation is available here.