5 minutos
Sistemas Distribuidos
The Graph es un protocolo implementado como un sistema distribuido.
Las conexiones fallan. Las solicitudes llegan fuera de orden. Diferentes computadoras con relojes y estados desincronizados procesan solicitudes relacionadas. Los servidores se reinician. Las reorganizaciones se producen entre las solicitudes. Estos problemas son inherentes a todos los sistemas distribuidos, pero se agravan en los sistemas que funcionan a escala mundial.
Considera este ejemplo de lo que puede ocurrir si un cliente pregunta a un Indexador por los últimos datos durante una reorganización.
- El indexador ingiere el bloque 8
- Solicitud servida al cliente para el bloque 8
- El indexador ingiere el bloque 9
- El indexador ingiere el bloque 10A
- Solicitud servida al cliente para el bloque 10A
- El indexador detecta la reorganización a 10B y revierte 10A
- Solicitud servida al cliente para el bloque 9
- El indexador ingiere el bloque 10B
- El indexador ingiere el bloque 11
- Solicitud servida al cliente para el bloque 11
Desde el punto de vista del indexador, las cosas avanzan lógicamente. El tiempo avanza, aunque tuvimos que revertir un bloque uncle y jugar el bloque bajo consenso hacia adelante encima de él. En el camino, el Indexador sirve las peticiones utilizando el último estado que conoce en ese momento.
Sin embargo, desde el punto de vista del cliente, las cosas parecen caóticas. El cliente observa que las respuestas fueron para los bloques 8, 10, 9 y 11 en ese orden. Lo llamamos el problema del “block wobble” (bamboleo del bloque). Cuando un cliente experimenta un bamboleo de bloques, los datos pueden parecer contradecirse a lo largo del tiempo. La situación se agrava si tenemos en cuenta que no todos los indexadores ingieren los últimos bloques de forma simultánea, y tus peticiones pueden ser dirigidas a varios indexadores.
Es responsabilidad del cliente y del servidor trabajar juntos para proporcionar datos coherentes al usuario. Hay que utilizar diferentes enfoques en función de la consistencia deseada, ya que no existe un programa adecuado para todos los problemas.
Razonar las implicaciones de los sistemas distribuidos es difícil, pero la solución puede no serlo! Hemos establecido APIs y patrones para ayudarte a navegar por algunos casos de uso comunes. Los siguientes ejemplos ilustran estos patrones pero eluden los detalles requeridos por el código de producción (como el manejo de errores y la cancelación) para no ofuscar las ideas principales.
Sondeo de datos actualizados
The Graph provides the block: { number_gte: $minBlock }
API, which ensures that the response is for a single block equal or higher to $minBlock
. If the request is made to a graph-node
instance and the min block is not yet synced, graph-node
will return an error. If graph-node
has synced min block, it will run the response for the latest block. If the request is made to an Edge & Node Gateway, the Gateway will filter out any Indexers that have not yet synced min block and make the request for the latest block the Indexer has synced.
We can use number_gte
to ensure that time never travels backward when polling for data in a loop. Here is an example:
1/// Updates the protocol.paused variable to the latest2/// known value in a loop by fetching it using The Graph.3async function updateProtocolPaused() {4 // It's ok to start with minBlock at 0. The query will be served5 // using the latest block available. Setting minBlock to 0 is the6 // same as leaving out that argument.7 let minBlock = 089 for (;;) {10 // Schedule a promise that will be ready once11 // the next Ethereum block will likely be available.12 const nextBlock = new Promise((f) => {13 setTimeout(f, 14000)14 })1516 const query = `17 query GetProtocol($minBlock: Int!) {18 protocol(block: { number_gte: $minBlock } id: "0") {19 paused20 }21 _meta {22 block {23 number24 }25 }26 }`2728 const variables = { minBlock }29 const response = await graphql(query, variables)30 minBlock = response._meta.block.number3132 // TODO: Do something with the response data here instead of logging it.33 console.log(response.protocol.paused)3435 // Sleep to wait for the next block36 await nextBlock37 }38}
Obtención de un conjunto de elementos relacionados
Otro caso de uso es la recuperación de un conjunto grande o, más generalmente, la recuperación de elementos relacionados a través de múltiples solicitudes. A diferencia del caso del sondeo (en el que la consistencia deseada era avanzar en el tiempo), la consistencia deseada es para un único punto en el tiempo.
Here we will use the block: { hash: $blockHash }
argument to pin all of our results to the same block.
1/// Gets a list of domain names from a single block using pagination2async function getDomainNames() {3 // Set a cap on the maximum number of items to pull.4 let pages = 55 const perPage = 100067 // The first query will get the first page of results and also get the block8 // hash so that the remainder of the queries are consistent with the first.9 const listDomainsQuery = `10 query ListDomains($perPage: Int!) {11 domains(first: $perPage) {12 name13 id14 }15 _meta {16 block {17 hash18 }19 }20 }`2122 let data = await graphql(listDomainsQuery, { perPage })23 let result = data.domains.map((d) => d.name)24 let blockHash = data._meta.block.hash2526 let query27 // Continue fetching additional pages until either we run into the limit of28 // 5 pages total (specified above) or we know we have reached the last page29 // because the page has fewer entities than a full page.30 while (data.domains.length == perPage && --pages) {31 let lastID = data.domains[data.domains.length - 1].id32 query = `33 query ListDomains($perPage: Int!, $lastID: ID!, $blockHash: Bytes!) {34 domains(first: $perPage, where: { id_gt: $lastID }, block: { hash: $blockHash }) {35 name36 id37 }38 }`3940 data = await graphql(query, { perPage, lastID, blockHash })4142 // Accumulate domain names into the result43 for (domain of data.domains) {44 result.push(domain.name)45 }46 }47 return result48}
Ten en cuenta que en caso de reorganización, el cliente tendrá que reintentar desde la primera solicitud para actualizar el hash del bloque a un bloque no-uncle.