No veo mi transferencia
«Transferencia realizada»… ¿dónde?
Ana le transfiere 100 € a Luis. La app dice «Transferencia realizada» y vuelve a la lista de movimientos. La transferencia no está. Ana baja el dedo para refrescar y sigue sin estar. Vuelve a refrescar y ahí aparece, como si nada.
En el episodio 5 de la serie de consistencia viste qué ve Luis cuando la app lee de una réplica: un saldo que ya no existe, y los modelos de consistencia que prometen algo mejor. Hoy vemos cómo se arregla. Porque nada de lo de hoy es un fallo: la transferencia está hecha, confirmada y copiada en todas partes. Solo que la app le preguntó a una réplica que todavía no se había enterado. La réplica 2 del episodio 1, la que iba 2,5 s por detrás, hoy va 4: es día de cobro y el retraso crece.
Para quien mira un saldo cualquiera, unos segundos no importan. Para quien acaba de mover dinero, sí: piensa que la transferencia ha fallado y la repite. Y el soporte de Cuentasver en el mapa recibe llamadas por transferencias duplicadas que la base de datos hizo perfectamente bien.
Leer lo que acabas de escribir
Ciento cincuenta clientes tienen la app abierta: cada uno lee sus movimientos a menudo y, de vez en cuando, hace una transferenciaver en el mapa. La réplica 1 va medio segundo por detrás; la 2, cuatro. A la izquierda, cada lectura va a una réplica cualquiera, por turnos, como en el episodio 1. A la derecha, con la solución que elijas. Los dos contadores del marcador son los dos problemas de hoy: lecturas que no ven lo que ese cliente acababa de escribir, y lecturas que ven algo más viejo que lo que ese cliente ya había visto.
Arranca, pulsa el botón de Ana y, en cuanto tenga su resultado, el de Luis. Después, prueba cada solución y tira una réplica.
Qué ha pasado
Read-your-writes: no ver lo propio
La escritura de Ana va al primario, que le dice «hecho».probar en el laboratorio En ese mismo instante, su app pide los movimientos, y la lectura va a la réplica 1. La réplica 1 va medio segundo por detrás: todavía no tiene la transferencia. No hay nada roto; simplemente, la lectura ha salido antes de que el cambio llegara.
La garantía que falta se llama read-your-writes (leer lo que has escrito): cada uno ve siempre sus propias escrituras, aunque las de los demás le lleguen más tarde. No pide que todo esté al día; pide que lo tuyo sí. Y el contador de la izquierda enseña que no es un caso raro: con 150 clientes, cada pocos segundos alguien lee justo después de escribir.
Lecturas monótonas: el abono que va y viene
Luis refresca tres veces seguidas.probar en el laboratorio El primer refresco cae en la réplica 1, que ya tiene la transferencia de Ana: ve el abono. El segundo cae en la réplica 2, que va cuatro segundos atrás: el abono desaparece. El tercero, otra vez en la 1: vuelve. Cada réplica, por separado, es coherente; es saltar de una a otra lo que hace que el tiempo vaya hacia atrás.
La garantía se llama lecturas monótonas (monotonic reads): una vez que has visto algo, no vuelves a ver un estado anterior. Tampoco pide estar al día: pide no retroceder. Son dos garantías distintas, y lo interesante es que cada solución arregla una, la otra o las dos.
Tres soluciones, cada una con su precio
Leer del primario tras escribir. Durante unos segundos después de escribir (aquí, 5), las lecturas de ese cliente van al primario. Ana ve su transferencia siempre. Pero es un parche para lo propio: Luis no ha escrito nada, así que sigue saltando entre réplicas y el abono sigue yendo y viniendo. Y tiene un coste que se ve en el marcador: el día de cobro todo el mundo escribe y mira, y cada una de esas lecturas vuelve al primario del que el episodio 1 las había sacado. La carga del primario pasa de una cuarta parte a dos tercios de su capacidad. Si alguna vez una réplica va más de 5 s por detrás, tampoco basta.
Réplica fija por sesión. Cada cliente lee siempre de la misma réplica.probar en el laboratorio Nadie vuelve atrás: su réplica solo avanza. Pero no garantiza ver lo propio: a Luis le toca la réplica 2 y no ve el abono en ninguno de los tres refrescos; el precio de no retroceder es no avanzar deprisa. Y se rompe cuando su réplica se cae: todas sus sesiones pasan a la otra, que puede ir por detrás, y el contador de lecturas que vuelven atrás salta de golpe.
Esperar a tu posición del WAL.probar en el laboratorio Cada escritura confirmada tiene una posición en el WAL, y cada réplica sabe hasta qué posición ha aplicado. La app guarda la posición de lo último que ese cliente escribió o leyó, y la manda con cada lectura: se lee de una réplica que ya haya llegado ahí, y si ninguna ha llegado, se espera a la más adelantada. Arregla las dos garantías a la vez: los dos contadores bajan a cero. El precio es la espera.probar en el laboratorio Casi siempre es de décimas de segundo, pero con la réplica 1 caída y la 2 a quince segundos, quien acaba de escribir espera hasta que se rinde y lee del primario.
Criterio
| Solución | Ver lo propio | No volver atrás | Cuándo hace daño |
|---|---|---|---|
| Réplica cualquiera | ✗ | ✗ | Siempre que alguien lea justo después de escribir |
| Primario tras escribir | ✓ (si el retraso es menor que la ventana) | ✗ | Mucha gente que escribe y lee: devuelve carga al primario |
| Réplica fija por sesión | ✗ | ✓ (mientras no se caiga) | Cuando la réplica fija va atrás o se cae |
| Esperar a tu posición del WAL | ✓ | ✓ | Cuando todas las réplicas van muy por detrás: latencia |
-
¿Puedes guardar una posición del WAL en la sesión del cliente (una cookie, un token)?La app de BCPS Bank sí: cada respuesta de Cuentas trae la posición de lo que ha escrito.esperar a tu posición
-
¿Se escribe poco y el primario va holgado?Un panel interno que usan cuatro personas.primario tras escribir
- Réplica fija por sesión, y enseñar lo recién escrito desde el propio cliente (la app añade la transferencia a la lista sin esperar a leerla).
Esa última idea merece más de una línea: la app ya sabe que ha hecho la transferencia, porque Cuentas le ha dicho «hecho». Puede pintarla en la lista sin volver a preguntar, marcada como reciente. Es lo que hacen casi todas las apps con los mensajes que envías. No arregla las lecturas monótonas, pero resuelve la mitad del problema sin tocar la base de datos.
En el mundo real
Rails implementa «primario tras escribir» tal cual: con el cambio automático de conexión, después de una escritura, las lecturas de esa sesión van al primario durante un tiempo configurable, dos segundos por defecto, guardado en una cookie. MediaWiki, el software de Wikipedia, guarda en la sesión la posición del primario tras cada escritura y, en la siguiente petición, espera a que la réplica elegida la alcance antes de leer. PostgreSQL da las piezas para hacerlo a mano: pg_current_wal_lsn() en el primario, pg_last_wal_replay_lsn() en la réplica; MySQL tiene WAIT_FOR_EXECUTED_GTID_SET.
Las bases de datos que se diseñaron distribuidas lo ofrecen de serie. MongoDB tiene causal consistency por sesión: el cliente lleva una marca de tiempo de lo último que ha visto y cada lectura espera a un nodo que la haya alcanzado. En Amazon DynamoDB, cada lectura elige: eventually consistent, más barata, o strongly consistent, que va al nodo líder y cuesta el doble.
La réplica fija aparece sin querer en muchos sitios: un pool de conexiones que siempre da la misma conexión a un mismo servidor, o un balanceador con sticky sessions. Funciona hasta el día en que ese servidor se cae, que es justo cuando nadie está mirando.
Siguiente episodio
Hasta aquí, todas las copias eran idénticas: el mismo esquema, el mismo motor, las mismas consultas. Pero Cuentas quiere un buscador en la app («¿cuándo pagué el gimnasio?»), y eso no se resuelve con una réplica: necesita otro motor, alimentado con los mismos cambios. ¿Cómo se le mandan sin escribir dos veces?
Episodio 4 Copiar sin escribir dos veces Change data capture frente a dual write: leer el WAL para alimentar un buscador y un warehouse sin tocar el código de Cuentas.