El tercer eje, el alcance: qué parte del universo baja al cliente
El tercer eje decide qué subconjunto de los datos llega al dispositivo, con tres granularidades habituales, y arrastra el problema más incómodo del nivel: qué ocurre con la copia local cuando cambian tus permisos.
Los dos ejes anteriores decidían quién manda y qué objeto viaja. El tercero decide cuánto viaja, y es el que más veces se subestima porque suena a problema de ancho de banda cuando en realidad es un problema de definición: sincronizar es siempre sincronizar un subconjunto, y elegir cómo se describe ese subconjunto determina el arranque en frío, la factura del servidor, qué consultas puede responder tu cliente sin red y, sobre todo, qué pasa el día en que alguien deja de tener permiso sobre algo que ya está grabado en su disco. Las tres granularidades que se han impuesto son sincronizarlo todo, sincronizar el resultado vivo de consultas declaradas y sincronizar colecciones definidas por reglas. Ninguna es una versión más refinada de las otras: son tres respuestas distintas a la pregunta de quién recalcula la pertenencia al conjunto y con qué frecuencia, y esa pregunta, que parece de implementación, es la que gobierna a la vez tu coste y tu capacidad de revocar.
- Distinguir las tres granularidades de alcance por su modelo de pertenencia y no por su tamaño.
- Anticipar los costes que impone toda réplica parcial, del agregado imposible al arranque en frío.
- Diseñar la revocación sabiendo qué restaura y qué no restaura jamás.
- Entender por qué la reincorporación de datos ya antiguos rompe los puntos de reanudación ingenuos.
Tres granularidades de alcance
La sincronización total hace bajar todo lo que el usuario puede ver, sin filtros posteriores. Su virtud es que elimina una categoría entera de problemas: no hay pertenencia que recalcular, no hay consulta que el cliente no pueda responder, no hay junturas rotas por ausencia y hay un único punto de reanudación para todo. Es la elección correcta con más frecuencia de lo que la gente admite, porque en herramientas internas, aplicaciones personales y espacios de equipo pequeños el conjunto entero cabe holgadamente. Sus tres límites son duros y previsibles: el tiempo del primer arranque, la cuota de almacenamiento del dispositivo y el hecho de que todo lo que baja queda expuesto a quien controle esa máquina.
La sincronización por consulta invierte el planteamiento: el cliente declara predicados —lo que ElectricSQL llama formas y Zero expresa como consultas— y el servidor mantiene vivo el conjunto de filas que los satisfacen. Lo que viaja no son solo altas y modificaciones, sino también entradas y salidas del conjunto, y esa segunda mitad es la que separa una implementación seria de un ejercicio: una fila que deja de cumplir el predicado tiene que abandonar la copia local, y el mensaje que la expulsa es tan importante como el que la trajo. La virtud es la precisión, y encaja de forma natural con una interfaz donde cada pantalla declara lo que necesita.
La sincronización por colección parte los datos en grupos nombrados según una regla, y el cliente se suscribe a un conjunto de grupos; es el modelo de reglas y cubos de PowerSync. Es más grueso que el anterior y a cambio tiene una propiedad de la que se habla poco y decide muchas arquitecturas: una colección es la misma para todos los clientes que la comparten, así que el servidor calcula su corriente de cambios una sola vez y la reparte, en lugar de mantener un conjunto vivo por cliente y por consulta. Además, cada colección lleva su propio punto de reanudación, lo que simplifica enormemente la reanudación y la reincorporación.
flowchart TD A[quien recalcula la pertenencia] --> T[total: nadie, se fija al entrar] A --> Q[por consulta: el servidor, por cliente y por predicado] A --> C[por coleccion: el servidor, una vez por cubo compartido] T --> TC[coste de arranque y de cuota] Q --> QC[coste que crece con clientes por consultas] C --> CC[coste que crece con el numero de cubos] style T fill:#f9e2af,color:#11111b style Q fill:#89b4fa,color:#11111b style C fill:#a6e3a1,color:#11111b
Lo que cuesta tener solo una parte
El primer coste es el de las preguntas que el cliente no puede responder. Un recuento global, una media sobre todo el histórico o un ranking entre elementos que no bajaron son consultas que exigen datos ausentes por definición, y ninguna cantidad de ingenio local las resuelve. La consecuencia no es técnica sino de producto: esos números tendrán que venir del servidor, con su indicador de carga y su posible fallo, justo dentro de una aplicación cuyo argumento principal era haber eliminado ambas cosas. Conviene decidir pronto y por escrito qué cifras son de este tipo, para que aparezcan en la interfaz como lo que son y no como una excepción inexplicable.
El segundo es la contabilidad de solapamientos. En el modelo por consulta, la copia local es la unión de varios conjuntos vivos que se pisan entre sí, así que cerrar una pantalla no autoriza a borrar sus filas: puede que la mitad pertenezca también a otra consulta todavía activa. Hace falta una cuenta de referencias por fila, y hace falta que sea correcta, porque un error por defecto borra datos que otra vista está mostrando y un error por exceso convierte la copia local en un vertedero que solo crece. En el modelo por colección este problema casi desaparece, porque la unidad de retención coincide con la unidad de suscripción.
El tercero es el arranque en frío, y es el que peor se prueba porque los equipos de desarrollo nunca lo sufren: sus dispositivos siempre tienen la copia caliente. El primer arranque de un usuario real es exactamente la situación que este tipo de arquitectura hace peor que la clásica, porque una aplicación convencional muestra la primera pantalla en cuanto llega su respuesta, mientras que una réplica local puede necesitar el conjunto entero antes de ser útil. Es obligatorio medirlo con almacenamiento vacío, con red lenta y con el volumen de datos del percentil noventa y nueve de tus usuarios, no con el tuyo.
Toda decisión de alcance se valida contra el usuario más cargado, no contra el promedio, y la distribución de estas cosas es siempre de cola larga y sorprendentemente pesada. El equipo que tiene diez veces más elementos que la mediana existe, es real, y suele ser además tu cliente más importante porque lleva más tiempo y usa más el producto. Una sincronización total que tarda cuatro segundos en la mediana puede tardar dos minutos en ese percentil, y esa persona no vivirá tu aplicación como rápida: la vivirá como rota. Haz la medida antes de elegir granularidad, no después de haberla implementado.
Cuando cambian los permisos
Aquí llega el problema que este eje arrastra y que ningún otro plantea. Un permiso revocado no puede quitar de un disco ajeno lo que ya está escrito allí; lo único que puede hacer el motor es enviar una instrucción de borrado y dejar de enviar actualizaciones. Conviene ser explícito con la consecuencia, porque casi siempre se pasa por alto en la fase de diseño y aparece en una auditoría: la revocación en un motor de sincronización es control de acceso continuado, no restauración de confidencialidad. Los datos estuvieron en ese dispositivo, pudieron copiarse, y ninguna instrucción posterior deshace eso. Si la revelación de un dato sería catastrófica en el supuesto de que una revocación futura tuviera que ser efectiva, ese dato no debe sincronizarse nunca.
Aceptado ese límite, la mecánica de la revocación depende por completo de la granularidad elegida, y la comparación es muy instructiva. En el modelo por consulta la revocación es casi gratis, porque el permiso forma parte del predicado: al dejar de cumplirse, la fila sale del conjunto por el mismo camino por el que saldría si alguien la hubiera archivado, y ese camino ya estaba construido y probado. En el modelo por colección es barata y gruesa: el cliente deja de estar suscrito y descarta el cubo entero, lo que es limpio, masivo y a veces excesivo. En la sincronización total es incómoda, porque no existe un mecanismo natural de expulsión y hay que fabricar uno, normalmente una lista de bajas explícitas que el motor no necesitaba para nada más.
El movimiento contrario, la ampliación de permisos, es menos comentado y técnicamente más traicionero. El cliente lleva un punto de reanudación que dice por dónde iba, y lo que acaba de autorizarse pudo cambiar hace meses, muy por detrás de esa marca. Pedir todo lo ocurrido desde el punto actual no traerá nada, porque no ha ocurrido nada nuevo: lo nuevo es el permiso, no el dato. Un motor que no haya previsto esto entrega al usuario una copia local con un agujero silencioso y perfectamente consistente en apariencia.
// Punto de reanudacion unico: la ampliacion de permisos deja un agujero
const cambios = await red.desde(cursorGlobal); // no incluye lo antiguo recien autorizado
// Punto por ambito: lo recien autorizado arranca desde el principio
cursores[nuevoAmbito] = 0;
for (const [ambito, c] of Object.entries(cursores)) {
const { cambios, siguiente } = await red.desde(ambito, c);
local.integrar(cambios);
cursores[ambito] = siguiente;
}
Ese fragmento explica una decisión de diseño que de otro modo parece burocracia: la razón por la que los sistemas basados en colecciones mantienen un punto de reanudación por colección en lugar de uno global no es la comodidad, es que sin él la reincorporación de datos previamente invisibles es incorrecta. Y hay un giro adicional que conviene mirar de frente: la regla que decide el alcance suele depender de datos que también se sincronizan, como la fila que dice a qué equipo perteneces. El alcance depende entonces de un dato que está dentro del alcance, y el motor tiene que tratar los cambios en esas filas como acontecimientos especiales que recalculan la pertenencia, no como una modificación cualquiera.
Cómo elegir el alcance
Total cuando el conjunto está acotado
Si el peor usuario cabe con holgura y todo lo que cabe está autorizado, elígelo sin complejos: elimina categorías enteras de error.
Consulta cuando el acceso es fino y dinámico
Precisión máxima y revocación gratuita, a cambio de un coste de servidor que crece con clientes multiplicados por consultas activas.
Colección cuando hay muchos clientes iguales
Los cubos compartidos se calculan una vez y se reparten, y cada uno lleva su reanudación. Es el modelo que mejor escala en número de clientes.
Nunca por comodidad de implementación
El alcance define tu superficie de exposición y tu latencia de revocación. Elegirlo por lo que es más fácil de programar se paga en auditoría.
La forma habitual de razonar sobre este eje es minimizar bytes, y por eso casi todo el mundo llega a la conclusión de que el modelo por consulta es el sofisticado y el resto son aproximaciones toscas que se irán abandonando. Es una lectura comprensible y lleva a decisiones caras. La reformulación correcta es que las tres granularidades responden a quién recalcula la pertenencia al conjunto y con qué frecuencia, y en cuanto se plantea así aparecen dos consecuencias que la lectura de los bytes no puede ver. La primera es de coste, y es aritmética elemental: en el modelo por consulta la pertenencia se recalcula por cliente y por predicado activo, de modo que el trabajo del servidor crece con el producto de ambos números; en el modelo por colección se recalcula una vez por cubo y el resultado se reparte entre todos los suscriptores, de modo que crece con el número de cubos, que es un número que tú controlas al diseñar las reglas y que no depende de cuánta gente se conecte. Esa es la razón real de que la granularidad más gruesa gane a escala pese a enviar de más, y la razón de que los sistemas por consulta inviertan tantísimo esfuerzo en mantenimiento incremental de vistas: no lo hacen por elegancia, lo hacen porque sin él ese producto de dos números los aplasta. La segunda consecuencia es de seguridad y va en dirección contraria, lo que impide que exista una respuesta única: la granularidad que recalcula la pertenencia continuamente obtiene la revocación gratis, porque perder un permiso es indistinguible de dejar de cumplir un predicado y el camino de salida ya existe y se ejercita mil veces al día; mientras que la granularidad que fija la pertenencia una sola vez al entrar necesita construir un mecanismo de expulsión que no usa para nada más, y todo mecanismo que solo se ejercita en el caso excepcional es un mecanismo que estará roto el día que haga falta. Puestas juntas, esas dos consecuencias explican el eje entero: estás eligiendo entre pagar el recálculo de pertenencia en tu factura o pagarlo en tu latencia de revocación, y no hay una tercera opción porque la pertenencia hay que calcularla en algún sitio. Del enunciado se sigue además el criterio práctico que este eje sí admite y que es más útil que cualquier comparativa: mira cuánto tarda de verdad, medido en producción y no en un diagrama, desde que alguien pierde acceso a algo hasta que ese algo desaparece de su dispositivo. Ese número, y no el volumen sincronizado, es la métrica que resume tu posición en el eje, la que te preguntarán en la primera auditoría seria y la que casi nadie ha medido nunca.
- Calcula el volumen sincronizado del usuario en el percentil noventa y nueve de tu producto, no el de la mediana ni el tuyo.
- Mide el arranque en frío con almacenamiento vacío y red limitada usando ese volumen, y anota el tiempo hasta la primera pantalla útil.
- Enumera las consultas de tu interfaz que no podrían responderse con una réplica parcial y decide cómo se van a presentar esos números.
- Diseña la prueba de revocación de principio a fin y mide cuántos segundos pasan hasta que el dato desaparece del dispositivo revocado.
- Simula una ampliación de permisos sobre datos antiguos y comprueba si tu punto de reanudación los trae o deja un agujero silencioso.
- Identifica qué filas de tu modelo determinan el alcance de otras y verifica que un cambio en ellas dispara un recálculo de pertenencia.