inro

Arquitectura

Escrit a partir de l'especificació de disseny del motor. El motor s'està implementant; això descriu el disseny, no una API publicada.

Un nucli clau-valor ordenat

El motor exposa claus i valors com a seqüències de bytes opaques, ordenades lexicogràficament. Les taules, els documents i els espais de noms es construeixen a sobre amb prefixos de clau, sense cap cost per al motor.

Els índexs secundaris, en canvi, sí que viuen al motor. En disc costen el mateix que mantenir-los a mà al codi que els crida — la diferència és el catàleg, una pàgina per base de dades. El que això compra és la garantia que un índex i les seves dades s'actualitzen dins de la mateixa transacció i no es poden desviar l'un de l'altre.

B+tree copy-on-write amb una llista lliure

Les pàgines modificades s'escriuen en slots lliures, mai sobre dades vives. Un commit és una única escriptura de pàgina meta. L'ocupació és estable, entre 1,2× i 2× les dades vives en el pitjor cas. Sense compactació, sense fils en segon pla, sense fitxer auxiliar, RAM constant.

Els lectors sense bloqueig surten d'això de franc: un lector fixa una pàgina meta, i l'arbre que en penja és immutable mentre la reté. No hi ha res més a sincronitzar.

file1. read page2. write copy into a free slot3. commit = new metapagecopymeta
Un commit allibera la pàgina antiga en lloc d'esborrar-la; torna a la llista lliure per reutilitzar-se.

Un escriptor, lectors sense bloqueig, fsync configurable

Un escriptor per base de dades, retingut per un mutex dins del procés. Els lectors mai bloquegen ni són bloquejats.

Sync::Commit és el valor per defecte: un fsync de les pàgines i un de la pàgina meta per commit. Un mode relaxat, Sync::Interval(d), agrupa la sincronització per a IoT, on sincronitzar cada escriptura desgasta la memòria flash. El mode relaxat es manté segur precisament gràcies al copy-on-write: un tall de corrent descarta els últims commits sense sincronitzar, però mai corromp, perquè les dades vives mai se sobreescriuen.

Un procés, multifil

Només un procés pot obrir un fitxer per escriure o llegir-hi. Això elimina la taula de lectors en memòria compartida que necessita LMDB, juntament amb el seu fitxer de bloqueig i la neteja de lectors morts després d'una aturada no neta — la taula de lectors es converteix en un comptador dins del procés. flock sobre el mateix fitxer es manté només com a xarxa de seguretat contra obrir la mateixa base de dades dues vegades per accident.

La llista lliure

Les pàgines que allibera una transacció no es poden reutilitzar mentre existeixi un lector d'una instantània anterior — per a aquest lector, encara són dades vives. Van a una llista pendent etiquetada amb el txn_id que les va alliberar, i passen a la llista lliure quan el lector actiu més antic supera aquest id. Com que l'accés és d'un sol procés, el registre de lectors actius és només una estructura en RAM: un comptador per txn_id amb lectors vius.

Les pàgines de la llista lliure emmagatzemen lots de números de pàgina més un punter al node següent, així que alliberar mil pàgines no costa mil escriptures. La llista en si és una cua, no una estructura en memòria: una llista enllaçada de pàgines amb el cap a l'extrem més antic — d'on es prenen les pàgines per reutilitzar-les — i la cua a l'extrem més recent, on s'hi afegeixen. Només aquestes dues pàgines viuen en RAM durant una transacció, i cadascuna s'escriu un cop, en el commit.

L'alternativa — mantenir la llista completa en RAM i serialitzar-la sencera en cada commit — seria més senzilla, però costaria 4 bytes de RAM per pàgina lliure: desenes de kilobytes en un node pro, cosa que menja del pressupost de RAM que és l'objectiu del projecte. La complexitat es paga per aquesta raó.

Un lector que manté la seva instantània oberta indefinidament bloqueja el reciclatge de cada pàgina alliberada des de llavors, i el fitxer creix — la mateixa limitació que té LMDB. El motor exposa una mètrica del txn_id retingut més antic perquè això es pugui diagnosticar.