wandres.dev
D1 AVANZADO · migraciones y réplicas

Read replication global

Toda base en un solo lugar es rápida para quien está cerca y lenta para quien está lejos, y en una red global casi todos están lejos. La replicación de lectura de D1 coloca copias de solo lectura de tu base en regiones repartidas por el mundo para acercar las lecturas al usuario. Cómo funciona la replicación asíncrona, por qué el replica lag es un coste estructural y no un fallo, cómo se activa por base y se observa con served_by_region, y cuál es el compromiso de fondo: menor latencia y más rendimiento de lectura a cambio de una frescura que solo la Sessions API vuelve a domesticar con consistencia secuencial.

⏱ 16 min

Toda base de datos en un solo lugar es rápida para quien está cerca y lenta para quien está lejos, y en una red global casi todos están lejos. La replicación de lectura de D1 ataca el problema de raíz: coloca copias de solo lectura de tu base en regiones repartidas por el mundo, de modo que la lectura de un usuario en Sídney no tenga que cruzar el planeta hasta la primaria en Fráncfort. El precio de esa cercanía es una verdad incómoda: esas copias van, por diseño, un instante atrasadas.

🎯 Al terminar esta lección sabrás
  • Distinguir la instancia primaria de las réplicas de solo lectura.
  • Entender la replicación asíncrona y el replica lag como causa de inconsistencia.
  • Activar la replicación y observar dónde se atienden las consultas.
  • Sopesar el compromiso entre latencia y rendimiento contra frescura del dato.

Una primaria, muchas réplicas

Sin replicación, D1 enruta cada consulta —lea o escriba— a una única instancia en una sola región del mundo, la primaria. La latencia de cada petición depende de la distancia física a ese punto: un usuario al otro lado del planeta paga el round-trip completo en cada consulta, y ninguna optimización de tu código lo evita, porque el coste es la velocidad de la luz recorriendo la fibra.

Con replicación, Cloudflare crea copias asíncronas de solo lectura, las réplicas, en varias regiones de su red. El usuario lejano de la primaria puede estar, en cambio, cerca de una réplica; cuando su lectura se atiende ahí, la respuesta llega en una fracción del tiempo. Las réplicas se crean de forma automática, se activan o duermen según el tráfico, y el enrutamiento es transparente: tú no eliges la réplica, la red te acerca a la más próxima.

Que el enrutamiento sea transparente tiene una consecuencia liberadora: no escribes lógica de geolocalización, no eliges centros de datos, no mantienes una tabla de qué usuario va a qué réplica. Declaras, mediante la sesión, cuánta frescura necesita cada consulta, y la plataforma resuelve el dónde. Es infraestructura que desaparece, que es la mejor clase de infraestructura.

Y que las réplicas se activen y duerman según el tráfico significa que no pagas por capacidad ociosa ni gestionas un parque de servidores: una base con lectores en Asia despierta la réplica de Asia; una sin tráfico allí no la mantiene encendida. La topología sigue a la demanda, sola.

ℹ️
Una réplica por región soportada

Hoy D1 mantiene una réplica en cada una de sus regiones —las dos de Norteamérica, las dos de Europa, Asia-Pacífico y Oceanía—, incluida la de la propia primaria. El conjunto exacto puede cambiar con el tiempo, pero la idea es estable: cobertura global sin que tú elijas ni administres ubicaciones.

🗽

Primaria

La copia original. Única autoridad de escritura y también capaz de leer. Vive en una sola región del mundo.

🌍

Réplica de lectura

Copia de solo lectura que recibe cambios de la primaria de forma asíncrona. Reenvía cualquier escritura a la primaria.

ℹ️
Toda escritura sigue yendo a la primaria

La replicación solo acelera lecturas. Cada INSERT, UPDATE o DELETE se reenvía a la primaria, que es la única autoridad de escritura. Bajo el capó, cada réplica es un Durable Object distinto que recibe los cambios de la primaria; por eso una réplica sirve su propia copia para leer, pero delega en la primaria para escribir. Y no cuesta nada extra: pagas los mismos rows_read y rows_written con o sin réplicas.

Replicación asíncrona y replica lag

La palabra que lo explica todo es asíncrona. La primaria confirma una escritura sin esperar a que las réplicas la reciban, y propaga el cambio después. El intervalo entre que la primaria confirma un dato y una réplica lo tiene se llama replica lag, y durante esa ventana la réplica sirve, con total normalidad, una versión anterior de la base.

Esto no es un defecto a corregir, es el mecanismo mismo que compra la velocidad. Si cada escritura tuviera que esperar la confirmación de todas las réplicas del planeta, la latencia de escritura sería intercontinental y habríamos perdido justo lo que vinimos a ganar. El replica lag es el coste estructural de tener copias cercanas; la pregunta de ingeniería no es cómo eliminarlo, sino cómo tolerarlo sin servir datos que contradigan lo que el usuario ya vio.

El tamaño típico de ese atraso es pequeño —la propagación es continua, no por lotes espaciados—, pero su valor exacto es, por naturaleza, no garantizado: puede crecer bajo carga o si una región queda momentáneamente rezagada. Diseñar suponiendo un número concreto de milisegundos sería frágil; lo robusto es diseñar suponiendo solo que el atraso existe y puede variar, y dejar que el bookmark imponga la frescura cuando importa.

Conviene situar esto en el mapa de garantías: la primaria ofrece siempre la última verdad porque toda escritura pasa por ella; las réplicas ofrecen frescura acotada. No hay una tercera opción mágica de rápido y siempre lo último desde cualquier parte, porque esa opción contradice la física de una red planetaria. Elegir es, aquí, inevitable, y la Sessions API es lo que vuelve esa elección barata y explícita.

💡
No supongas un número; supón la propiedad

El error de diseño clásico es medir el replica lag una tarde tranquila, verlo en pocos milisegundos y codificar esa cifra como si fuera una garantía. No lo es. Trata el atraso como una propiedad cualitativa —existe, es variable, queda acotado por el bookmark— y tu sistema seguirá siendo correcto el día de más tráfico del año.

flowchart TD
P[primaria unica escrituras] -->|replicacion asincrona| R1[replica wnam]
P -->|replicacion asincrona| R2[replica weur]
P -->|replicacion asincrona| R3[replica apac]
U1[usuario en europa] -->|lectura cercana| R2
U2[usuario en asia] -->|lectura cercana| R3
style P fill:#f38ba8,color:#11111b
style R1 fill:#a6e3a1,color:#11111b
style R2 fill:#a6e3a1,color:#11111b
style R3 fill:#a6e3a1,color:#11111b

Activar y observar

La replicación se activa por base, no por consulta. Desde el panel de D1, en los ajustes de tu base, la habilitas; o por la REST API poniendo el modo en automático:

curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ID/d1/database/$DB" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"read_replication": {"mode": "auto"}}'

Pero activarla no basta: si sigues consultando env.DB directamente, todo va a la primaria como antes. Las réplicas solo entran en juego cuando usas la Sessions API. Para ver qué ocurre, cada resultado trae en su objeto meta los campos served_by_region y served_by_primary, que te dicen dónde se atendió la consulta.

const r = await env.DB.withSession().prepare("SELECT 1").run();
console.log(r.meta.served_by_region, r.meta.served_by_primary);

Estos campos son tu ventana a la topología: si una lectura que esperabas cercana sigue marcando served_by_primary, sabrás que algo —un bookmark demasiado exigente, una escritura previa en la sesión— la está forzando a la primaria. Observar antes de optimizar es la regla, y aquí la observación viene incluida en cada respuesta.

Comprobar si la replicación está activa es igual de directo por la REST API: pides la base y miras el modo.

curl -X GET "https://api.cloudflare.com/client/v4/accounts/$ID/d1/database/$DB" \
  -H "Authorization: Bearer $TOKEN"
# el campo read_replication.mode sera auto o disabled

Con el modo en auto, D1 mantiene una réplica en cada región soportada y la despierta según el tráfico; tú no gestionas ninguna instancia. Administras solo la decisión de consistencia por consulta, que ya vive en tu código a través de la sesión. Esa división del trabajo —Cloudflare opera la topología, tú declaras la frescura— es lo que hace que la replicación se sienta gratis de operar.

📝
El precio de replicar es cero; el de no medir, no

D1 no cobra almacenamiento ni cómputo extra por las réplicas: facturas los mismos rows_read y rows_written los sirva quien los sirva. El coste real de la replicación no está en la factura sino en el razonamiento: cada lectura servida desde una réplica es una lectura que aceptas ver, como mucho, un replica lag de antigüedad. Ese coste se paga pensando, no pagando.

⚠️
Ni en local ni al instante

La replicación no opera en desarrollo local: wrangler dev siempre habla con una sola base, y esos campos de meta llegan indefinidos. Y desactivarla no es inmediato: las réplicas pueden tardar hasta veinticuatro horas en dejar de atender. Como la Sessions API funciona igual con o sin réplicas, el código con sesiones es seguro en ambos estados.

El compromiso de consistencia

Lo que ganas es nítido: menor latencia de lectura para usuarios lejanos y más rendimiento agregado, porque muchas instancias reparten la carga de lectura. Lo que arriesgas es la frescura: sin cuidado, una lectura puede caer en una réplica atrasada y contradecir una escritura reciente.

El modelo que D1 ofrece para gobernar ese riesgo es la consistencia secuencial, y se obtiene con la Sessions API de la lección anterior. No es la consistencia más fuerte posible —no es serializabilidad estricta ni linealizabilidad global—, pero sí la más fuerte que un sistema global puede dar sin sacrificar la latencia: dentro de una sesión, las lecturas son monótonas, las escrituras son monótonas, y siempre lees tus propias escrituras.

La decisión de diseño, entonces, no es si replicar o no, sino qué consultas toleran un dato de hace un segundo y cuáles exigen la última verdad. Esa clasificación —hecha endpoint a endpoint, no de una vez para toda la base— es el verdadero trabajo de ingeniería que la replicación te pide a cambio de su velocidad.

🚀

Menor latencia

Las lecturas se atienden cerca del usuario, no al otro lado del planeta. La distancia física deja de dominar el tiempo de respuesta.

📈

Más rendimiento

Varias instancias reparten la carga de lectura. El caudal agregado de lectura crece sin recargar la primaria.

⚖️

Frescura acotada

A cambio, una lectura puede ir atrasada un replica lag; la Sessions API acota ese atraso a lo que cada consulta tolere.

Un modo útil de internalizarlo: la replicación no cambia qué datos tiene tu base, cambia dónde y cuándo puede leerlos cada usuario. Es una decisión sobre el espacio y el tiempo del acceso, no sobre el contenido. Por eso se activa sin migrar nada y se razona consulta a consulta, no tabla a tabla.

Un patrón mental ayuda a decidir rápido: separa tus rutas en las que leen para mostrar y las que leen para decidir. Un listado de artículos, un perfil público o un panel de métricas leen para mostrar, y toleran de sobra un dato de hace un instante; una comprobación de saldo antes de cobrar o una validación de stock antes de vender leen para decidir, y ahí exiges la primaria o esperas al bookmark. Y como la observación viene incluida, la clasificación no es teórica: despliega, mira served_by_region en tus rutas reales y deja que los datos confirmen si acertaste al etiquetar cada consulta.

⚠️
La Sessions API vive en el binding, no en la REST

La consistencia secuencial de la Sessions API solo está disponible a través del Worker Binding de D1, no de la REST API. Si accedes a tu base por HTTP desde fuera de un Worker, no tienes withSession ni bookmark, y por tanto no obtienes las garantías de esta lección. En la práctica, esto empuja la lógica de datos sensible a la consistencia hacia dentro del Worker, que es donde debe estar.

La geografía es el verdadero esquema de una base global

Durante medio siglo la teoría de bases de datos se escribió como si la distancia no existiera: una base era un punto lógico, las consultas la tocaban al instante, y la única física relevante era el disco. La red global rompe ese supuesto y lo sustituye por otro que la ingeniería clásica trataba como detalle sucio de operaciones: dónde está el dato respecto a quién lo pide. La replicación de lectura de D1 es, en el fondo, la admisión de que la geografía es parte del esquema. No puedes razonar sobre el rendimiento de tu aplicación mirando solo las tablas y los índices; tienes que saber dónde vive la primaria, dónde están los usuarios y qué fracción de tus consultas son lecturas que una copia cercana y ligeramente atrasada puede satisfacer. El teorema de fondo —que no puedes tener a la vez consistencia fuerte, disponibilidad y tolerancia a particiones— deja de ser un póster de aula y se vuelve la decisión que tomas en cada endpoint. Y la respuesta de D1 es astuta precisamente porque se niega a ser dogmática: en lugar de imponer un punto único del espectro consistencia-latencia para toda la base, te da la réplica cercana por defecto y el bookmark como perilla para exigir frescura solo donde importa. El catálogo de productos, que cambia una vez al día, se lee de la réplica y vuela; el saldo de la cuenta, que el usuario acaba de modificar, arranca en la primaria o espera al bookmark. La misma base, dos regímenes de consistencia, elegidos consulta a consulta según lo que el dato significa para el negocio. El ingeniero que viene del mundo de un solo servidor busca el interruptor de consistencia total y se frustra al no hallarlo; el que entiende los sistemas globales sabe que ese interruptor tendría un precio —la latencia intercontinental en cada lectura— que ningún usuario aceptaría, y que la madurez consiste en dejar de buscar un absoluto para empezar a asignar, con criterio, cuánta obsolescencia tolera cada pregunta que le haces a los datos.

⚔️ Mide la cercanía y su precio
  1. Activa la replicación de lectura en una base de prueba desde el panel o la REST API y confirma su estado.
  2. Da a tu base una pista de ubicación lo más lejos posible de ti y compara la latencia de una lectura con sesión y sin ella.
  3. Ejecuta una consulta con withSession e imprime served_by_region y served_by_primary; interpreta qué instancia la atendió.
  4. Clasifica tres consultas de una app real por su tolerancia a la obsolescencia y decide, para cada una, entre réplica y primaria.
  5. Argumenta en tres frases por qué el replica lag es un coste estructural y no un fallo a eliminar.