Saltearse al contenido

Snapshots y epoch fencing

Snapshots

Cada lectura en NamiDB ocurre contra un snapshot — una vista inmutable del namespace en una versión específica del manifest. El snapshot fija:

  • El conjunto exacto de SSTs que estaban vivos en esa versión
  • El schema en esa versión
  • Un LSN piso

Se obtiene un snapshot desde un WriterSession:

let snap = writer.snapshot();
let rows = execute(&plan, &snap, &Params::new()).await?;

O implícitamente por cada llamada a Cypher desde el cliente de Python:

result = client.cypher("MATCH (p:Person) RETURN p.name")
# read against an internally-captured snapshot

Múltiples snapshots pueden coexistir. Una query analítica de larga duración y una escritura caliente pueden correr concurrentemente — la query lee desde su snapshot, el writer muta la memtable y produce una nueva versión del manifest. Cuando la query termina, el GC puede reclamar los SSTs que ya no están referenciados.

El manifest

Un objeto JSON pequeño en {namespace}/manifest.json que nombra todo lo que está actualmente vivo para el namespace. El schema (simplificado):

{
"version": 42,
"epoch": 7,
"lsn_watermark": 18374,
"schema_id": "...",
"ssts": {
"node": { "L0": [...], "L1": [...] },
"edge": { "L0": [...], "L1": [...] }
}
}

Cada commit de escritura produce una nueva versión del manifest. La versión previa sigue siendo direccionable (está referenciada por snapshots en vuelo) hasta que el GC la remueva.

CAS del manifest

Los mutadores hacen:

1. Read manifest.json, capture its ETag.
2. Build the new version locally (apply WAL segments, list new SSTs).
3. PUT manifest.json with If-Match: <captured ETag>.
4. If 412 Precondition Failed → re-read, rebuild, retry.
5. If 200 OK → broadcast new version, increment local epoch.

Esta es la misma receta que funciona para vectores y analytics sobre object storage. El conditional write de S3 reemplaza al tier de consenso.

Epoch fencing

Cada manifest lleva un contador de epoch que se incrementa en cada commit. Un writer que estuvo idle mientras otro writer avanzó el epoch va a fallar en su próximo intento de commit y debe re-bootstrapear desde el manifest más reciente.

Esto fencea a los writers obsoletos — por ejemplo, un proceso que perdió conectividad de red y después se reconectó ya no puede commitear contra el epoch viejo. Tiene que volver a leer y re-planear sus escrituras.

Qué todavía no se obtiene

  • Transacciones cross-namespace. Cada namespace es su propia unidad de CAS. No hay two-phase commit entre namespaces. Usar un solo namespace si se necesita consistencia transaccional sobre los datos.
  • Escala de readers más allá de un proceso. Hoy, namidb-server serializa los requests detrás de un Mutex de tokio (single-writer-per-namespace llevado a la capa de request). Remover el mutex del read path para que un único daemon pueda distribuir las lecturas entre todos los cores está en el roadmap.

Ver también