De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Am Hierscht 2019 ass e laang erwaarde Event am Mail.ru Cloud iOS Team geschitt. D'Haaptdatenbank fir persistent Späichere vum Applikatiounsstaat ass ganz exotesch fir déi mobil Welt ginn Lightning Memory-Mapped Datebank (LMDB). Ënnert dem Schnëtt proposéiere mir Iech eng detailléiert Iwwerpréiwung dovun a véier Deeler. Als éischt schwätze mer iwwer d'Grënn fir sou eng net-trivial a schwiereg Wiel. Da wäerte mir weidergoen fir déi dräi Piliere am Häerz vun der LMDB Architektur ze berücksichtegen: Memory-mapped Dateien, B +-Bam, Copy-on-Write Approche fir Transaktiounsfäegkeet a Multiversion ëmzesetzen. Endlech, fir Dessert - de prakteschen Deel. An et wäerte mir kucken wéi een en Datebankschema mat verschiddenen Dëscher designt an ëmsetzt, dorënner en Index, uewen op der Low-Level Key-Wäert API.

Inhalt

  1. Motivatioun fir Ëmsetzung
  2. LMDB Positionéierung
  3. Dräi Piliere vun LMDB
    3.1. Wal #1. Memory-mapéiert Dateien
    3.2. Wal #2. B+-Bam
    3.3. Wal #3. Copy-op-schreiwen
  4. Entwerfen en Dateschema uewen op der Schlësselwäert API
    4.1. Basis Abstraktiounen
    4.2. Dësch Modeller
    4.3. Modelléiere Relatiounen tëscht Dëscher

1. Motivatioun fir Ëmsetzung

Ee Joer am 2015 hu mir d'Muecht gemaach fir ze moossen wéi dacks d'Interface vun eiser Applikatioun lags. Mir hunn dat aus engem Grond gemaach. Mir hu méi dacks Reklamatioune kritt datt heiansdo d'Applikatioun ophält op Benotzeraktiounen ze reagéieren: Knäppercher kënnen net gedréckt ginn, Lëschte scrollen net, etc. Iwwer d'Mechanik vu Miessunge erzielt op AvitoTech, also hei ginn ech nëmmen d'Uerdnung vun den Zuelen.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

D'Miessresultater goufe fir eis eng kal Dusch. Et huet sech erausgestallt datt et vill méi Probleemer gëtt, déi duerch Afréiere verursaacht ginn wéi all aner. Wann ier Dir dës Tatsaach realiséiere war den Haapttechneschen Indicateur vun der Qualitéit Crash fräi, dann nom Fokus verréckelt op afréieren fräi.

Gebaut ze hunn Dashboard mat Afréiere an no Ausgaben quantitativ и Qualitéit D ' analys vun hire grënn, den hauptfeind gouf kloer - schwéier geschäftlech logik ausgefouert am haapt thread vun der Applikatioun. Déi natierlech Reaktioun op dës Schimmt war e brennende Wonsch et an d'Aarbechtsstroum ze drécken. Fir dëse Problem systematesch ze léisen, hu mir op eng multi-threaded Architektur baséiert op liichte Schauspiller. Ech hunn et fir seng Adaptatioun fir d'iOS Welt gewidmet zwee thread op kollektiv Twitter an Artikel iwwer Habré. Als Deel vun der aktueller narrativ wëll ech déi Aspekter vun der Entscheedung ënnersträichen, déi d'Wiel vun der Datebank beaflosst hunn.

De Schauspillermodell vun der Systemorganisatioun gëtt ugeholl datt Multithreading seng zweet Essenz gëtt. Modellobjekter dran gär iwwer Stroumgrenzen iwwerschratt. A si maachen dat net heiansdo an hei an do, mee bal stänneg an iwwerall

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

D'Datebank ass ee vun den Ecksteen Komponenten am presentéiert Diagramm. Seng Haaptaufgab ass d'Makromuster ëmzesetzen Shared Datebank. Wann et an der Enterprise Welt benotzt gëtt fir Datensynchroniséierung tëscht Servicer ze organiséieren, dann am Fall vun der Schauspillerarchitektur - Daten tëscht Threads. Also hu mir eng Datebank gebraucht, déi net emol minimal Schwieregkeete géif verursaachen wann se an engem Multi-threaded Ëmfeld schaffen. Besonnesch heescht dat, datt d'Objeten, déi dovunner kritt ginn, op d'mannst thread-sécher sinn, an am Idealfall komplett net-mutabel. Wéi Dir wësst, kann déi lescht gläichzäiteg aus verschiddene thread benotzt ginn ouni op eng Sperrung ze kommen, wat e positiven Effekt op d'Leeschtung huet.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS UwendungenDen zweete bedeitende Faktor deen d'Wiel vun der Datebank beaflosst war eis Cloud API. Et gouf inspiréiert vun der Synchroniséierungs Approche ugeholl vu git. Wéi hien, hu mir gezielt offline-éischt API, wat méi wéi passend fir Cloud Clienten ausgesäit. Et gouf ugeholl datt se nëmmen eemol de ganzen Zoustand vun der Wollek auspompelen, an dann d'Synchroniséierung an der iwwerwältegend Majoritéit vu Fäll duerch Ausrollen Ännerungen geschéien. Leider ass dës Geleeënheet nach ëmmer nëmmen an der theoretescher Zone, a Clienten hunn net geléiert wéi se an der Praxis mat Patches schaffen. Et ginn eng Rei objektiv Grënn dofir, déi mir, fir d'Aféierung net ze verzögeren, Klammeren hannerloossen. Elo, wat vill méi interesséiert ass, sinn déi léierräich Conclusiounen vun der Lektioun iwwer wat geschitt wann eng API "A" seet a säi Konsument net "B" seet.

Also, wann Dir Iech Git virstellt, wat, wann Dir e Pull Kommando ausféiert, anstatt Patches op e lokalen Snapshot z'applizéieren, säi vollen Zoustand mam vollen Serverzoustand vergläicht, da wäert Dir eng zimlech genee Iddi hunn wéi d'Synchroniséierung an der Wollek geschitt. Clienten. Et ass einfach ze roden datt fir et ëmzesetzen, musst Dir zwee DOM-Beem an der Erënnerung mat Meta-Informatioun iwwer all Server a lokal Dateien verdeelen. Et stellt sech eraus datt wann e Benotzer 500 Tausend Dateien an der Wollek späichert, da fir se ze synchroniséieren ass et néideg fir zwee Beem mat 1 Millioun Noden nei ze kreéieren an ze zerstéieren. Awer all Node ass en Aggregat mat enger Grafik vun Ënnerobjekter. An dësem Liicht goufen d'Profiling Resultater erwaart. Et huet sech erausgestallt, datt och ouni de Fusiouns-Algorithmus ze berücksichtegen, d'ganz Prozedur fir eng grouss Zuel vu klengen Objeten ze kreéieren an duerno ze zerstéieren e schéine Penny ass d'Situatioun verschlechtert duerch d'Tatsaach datt d'Basis Synchroniséierungsoperatioun an enger grousser Zuel abegraff ass vun Benotzer Scripten. Als Resultat fixe mir den zweete wichtege Critère bei der Auswiel vun enger Datebank - d'Fäegkeet fir CRUD Operatiounen ëmzesetzen ouni dynamesch Allocatioun vun Objeten.

Aner Ufuerderunge si méi traditionell an hir ganz Lëscht ass wéi follegt.

  1. Fuedem Sécherheet.
  2. Multiprocessing. Diktéiert vum Wonsch déiselwecht Datebankinstanz ze benotzen fir Staat ze synchroniséieren net nëmmen tëscht Threads, awer och tëscht der Haaptapplikatioun an iOS Extensiounen.
  3. D'Kapazitéit fir gespäichert Entitéiten als net verännerbar Objeten ze representéieren
  4. Keng dynamesch Allokatiounen bannent CRUD Operatiounen.
  5. Transaktioun Ënnerstëtzung fir Basis Eegeschafte sauerem: Atomitéit, Konsistenz, Isolatioun an Zouverlässegkeet.
  6. Geschwindegkeet op de populärste Fäll.

Mat dësem Set vun Ufuerderunge war a bleift SQLite eng gutt Wiel. Wéi och ëmmer, am Kader vun der Etude vun Alternativen sinn ech op e Buch komm "Ufänken mat LevelDB". Ënner hirer Leedung gouf e Benchmark geschriwwen, deen d'Geschwindegkeet vun der Aarbecht mat verschiddenen Datenbanken an echte Wollekenszenarien vergläicht. D'Resultat huet eis wildest Erwaardungen iwwerschratt. An de beléifste Fäll - e Cursor op eng zortéiert Lëscht vun all Dateien ze kréien an eng zortéiert Lëscht vun all Dateie fir e bestëmmten Verzeechnes - LMDB huet sech 10 Mol méi séier wéi SQLite gewisen. De Choix gouf evident.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

2. LMDB Positionéierung

LMDB ass eng ganz kleng Bibliothéik (nëmmen 10K Reihen) déi déi ënnescht fundamental Schicht vun Datenbanken implementéiert - Späicheren.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Déi uewe genannte Diagramm weist datt LMDB mat SQLite ze vergläichen, wat och méi héich Niveauen implementéiert, allgemeng net méi korrekt ass wéi SQLite mat Core Data. Et wier méi fair fir déiselwecht Späichermotoren als gläiche Konkurrenten ze zitéieren - BerkeleyDB, LevelDB, Sophia, RocksDB, etc. Et gi souguer Entwécklungen wou LMDB als Späichermotorkomponent fir SQLite handelt. Dat éischt esou Experiment war am Joer 2012 ausginn vum LMDB Howard Chu. Resultater huet sech esou spannend erausgestallt, datt seng Initiativ vun OSS-Begeeschterten opgeholl gouf, a seng Fortsetzung an der Persoun fonnt huet LumoSQL. Am Januar 2020 war den Auteur vun dësem Projet Den Shearer presentéiert et bei LinuxConfAu.

LMDB gëtt haaptsächlech als Motor fir Uwendungsdatenbanken benotzt. D'Bibliothéik verdankt säin Erscheinungsbild un den Entwéckler OpenLDAP, déi mat BerkeleyDB als Basis fir hire Projet héich onzefridden waren. Ugefaange vun enger bescheidener Bibliothéik btree, Den Howard Chu konnt eng vun de populäersten Alternativen vun eiser Zäit erstellen. Säi ganz coole Bericht huet hien der Geschicht, grad wéi der interner Struktur vun der LMDB gewidmet. "The Lightning Memory mapped Database". E gutt Beispill fir d'Eruewerung vun enger Lagerung gouf vum Leonid Yuryev (aka yljo) vu Positive Technologies a sengem Bericht bei Highload 2015 "De LMDB-Motor ass e spezielle Champion". Do schwätzt hien iwwer LMDB am Kader vun enger ähnlecher Aufgab fir ReOpenLDAP ëmzesetzen, an LevelDB gouf scho vergläichend Kritik ënnerworf. Als Resultat vun der Implementatioun haten Positive Technologies souguer eng aktiv Entwécklungsgabel MDBX mat ganz lecker Fonctiounen, optimizations an bugfixes.

LMDB gëtt dacks als As-is Späichere benotzt. Zum Beispill, Mozilla Firefox Browser gewielt et fir eng Rei vu Besoinen, an, ab Versioun 9, Xcode bevorzugt seng SQLite fir Indexen ze späicheren.

De Motor huet och seng Mark an der Welt vun der mobiler Entwécklung gemaach. Spure vu senger Notzung kënne sinn fannen am iOS Client fir Telegram. LinkedIn ass nach méi wäit gaang an huet LMDB als Standardlagerung fir säin hausgemaachte Date-Caching-Framework gewielt Rocket Data, iwwer déi gesot a sengem Artikel 2016.

LMDB kämpft erfollegräich fir eng Plaz an der Sonn an der Nisch, déi vum BerkeleyDB hannerlooss ass, nodeems se ënner der Kontroll vum Oracle koum. D'Bibliothéik ass gär fir seng Geschwindegkeet an Zouverlässegkeet, och am Verglach mat senge Kollegen. Wéi Dir wësst, ginn et keng gratis Mëttegiessen, an ech géif gären den Ofhandlung ënnersträichen, deen Dir maache musst wann Dir tëscht LMDB an SQLite wielt. D'Diagramm uewen weist kloer wéi erhéicht Geschwindegkeet erreecht gëtt. Als éischt bezuele mir net fir zousätzlech Schichten vun der Abstraktioun uewen op der Disklagerung. Et ass kloer datt eng gutt Architektur nach ëmmer net ouni si maache kann, a si wäerten zwangsleefeg am Applikatiounscode erschéngen, awer si wäerte vill méi subtiler sinn. Si enthalen keng Features déi net vun enger spezifescher Applikatioun erfuerderlech sinn, zum Beispill Ënnerstëtzung fir Ufroen an der SQL Sprooch. Zweetens gëtt et méiglech d'Mapping vun Applikatiounsoperatiounen op Ufroen op Disklagerung optimal ëmzesetzen. Wann SQLite a menger Aarbecht baséiert op den duerchschnëttleche statistesche Bedierfnesser vun enger duerchschnëttlecher Applikatioun, da sidd Dir als Applikatiounsentwéckler gutt bewosst iwwer d'Haaptaarbechtszenarie. Fir eng méi produktiv Léisung musst Dir e verstäerkte Präisstag bezuelen souwuel fir d'Entwécklung vun der initialer Léisung wéi och fir seng spéider Ënnerstëtzung.

3. Dräi Piliere vun LMDB

Nodeems Dir de LMDB aus engem Vugelperspektiv gekuckt huet, war et Zäit fir méi déif ze goen. Déi nächst dräi Sektioune gi fir eng Analyse vun den Haaptpiliere gewidmet, op deenen d'Späicherarchitektur riicht:

  1. Memory-mapéiert Dateien als Mechanismus fir mat Disk ze schaffen an intern Datestrukturen ze synchroniséieren.
  2. B + -Bam als Organisatioun vun der Struktur vun gespäichert Donnéeën.
  3. Copy-on-write als Approche fir ACID Transaktiounseigenschaften a Multiversion ze bidden.

3.1. Wal #1. Memory-mapéiert Dateien

Memory-mapped Dateien sinn esou e wichtegt architektonescht Element datt se souguer am Numm vum Repository erscheinen. Themen vum Caching a Synchroniséierung vum Zougang zu gespäichert Informatioun sinn ganz dem Betribssystem iwwerlooss. LMDB enthält keng Cache a sech selwer. Dëst ass eng bewosst Entscheedung vum Auteur, well d'Liesen vun Daten direkt vu mapéierten Dateien erlaabt Iech vill Ecker an der Motorimplementatioun ze schneiden. Drënner ass eng wäit net komplett Lëscht vun e puer vun hinnen.

  1. D'Konsistenz vun den Daten an der Späichere behalen wann Dir mat e puer Prozesser schafft, gëtt d'Verantwortung vum Betribssystem. An der nächster Rubrik gëtt dës Mechanik am Detail a mat Biller diskutéiert.
  2. D'Feele vu Cache eliminéiert LMDB komplett aus dem Overhead verbonne mat dynameschen Allokatiounen. Daten liesen an der Praxis heescht e Pointer op déi richteg Adress an der virtueller Erënnerung ze setzen an näischt méi. Et kléngt wéi Science Fiction, awer am Späicherquellcode sinn all Uriff op Calloc an der Späicherkonfiguratiounsfunktioun konzentréiert.
  3. D'Feele vu Cache bedeit och d'Feele vu Spären, déi mat der Synchroniséierung vun hiren Zougang verbonne sinn. Lieser, vun deenen et eng arbiträr Unzuel vu Lieser zur selwechter Zäit ka sinn, begéinen net een eenzegen Mutex um Wee an d'Daten. Dofir huet d'Liesgeschwindegkeet eng ideal linear Skalierbarkeet baséiert op der Unzuel vun den CPUs. An LMDB sinn nëmmen Ännerungsoperatioune synchroniséiert. Et kann nëmmen ee Schrëftsteller gläichzäiteg sinn.
  4. E Minimum vu Cache- a Synchroniséierungslogik eliminéiert déi extrem komplex Aart vu Feeler, déi mat der Aarbecht an engem Multi-threaded Ëmfeld verbonne sinn. Et waren zwou interessant Datebankstudien op der Usenix OSDI 2014 Konferenz: "All Dateiesystemer sinn net gläich erstallt: iwwer d'Komplexitéit vu Crash-konsistent Uwendungen ze kreéieren" и "Datebase gefoltert fir Spaass a Profit". Vun hinnen kënnt Dir Informatiounen iwwer déi eemoleg Zouverlässegkeet vun LMDB sammelen wéi och déi bal flawless Implementatioun vun ACID Transaktiounseigenschaften, déi besser ass wéi déi an SQLite.
  5. De Minimalismus vum LMDB erlaabt datt d'Maschinnvertriedung vu sengem Code komplett am L1 Cache vum Prozessor mat de folgende Geschwindegkeetseigenschaften lokaliséiert gëtt.

Leider, am iOS, mat Memory-mapped Dateien, ass alles net sou Wolleklos wéi mir wëllen. Fir méi bewosst iwwer d'Mängel ze schwätzen, déi mat hinnen verbonne sinn, ass et néideg fir d'allgemeng Prinzipien vun der Ëmsetzung vun dësem Mechanismus an de Betribssystemer ze erënneren.

Allgemeng Informatiounen iwwer Erënnerung-mapped Fichieren

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS UwendungenMat all Applikatioun déi leeft, ass de Betribssystem eng Entitéit associéiert genannt Prozess. All Prozess gëtt eng kontinuéierlech Palette vun Adressen zougewisen an deenen et alles plazéiert wat et brauch fir ze bedreiwen. Op den ënneschten Adressen ginn et Sektiounen mat Code an haart kodéierten Daten a Ressourcen. Als nächst kënnt e wuessende Block vun dynamesche Adressraum, eis bekannt ënner dem Numm Heap. Et enthält d'Adressen vun Entitéiten, déi während der Operatioun vum Programm erschéngen. Am Top ass d'Erënnerungsberäich benotzt vum Applikatiounsstack. Et wächst entweder oder vertragt, an anere Wierder, seng Gréisst huet och eng dynamesch Natur. Fir ze verhënneren, datt de Stack an de Koup sech géigesäiteg drécken an interferéieren, si si op verschiddenen Enden vum Adressraum lokaliséiert. Et gëtt e Lach tëscht den zwee dynamesche Sektiounen uewen an ënnen. De Betribssystem benotzt Adressen an dësem Mëttelsektioun fir eng Vielfalt vun Entitéite mam Prozess ze associéieren. Besonnesch kann et e bestëmmte kontinuéierleche Set vun Adressen mat enger Datei op der Disk verbannen. Sou eng Datei gëtt Memory-mapped genannt

Den Adressraum, deen dem Prozess zougewisen ass, ass enorm. Theoretesch ass d'Zuel vun den Adressen nëmme limitéiert duerch d'Gréisst vum Zeiger, déi duerch d'Bitskapazitéit vum System bestëmmt gëtt. Wann kierperlecht Gedächtnis dorop 1-zu-1 kartéiert wier, da géif deen alleréischte Prozess de ganze RAM opgoen, an et wier kee Gespréich vu Multitasking.

Wéi och ëmmer, aus eiser Erfahrung wësse mir datt modern Betribssystemer gläichzäiteg sou vill Prozesser wéi gewënscht kënne ausféieren. Dëst ass méiglech wéinst der Tatsaach datt se nëmme vill Erënnerung un d'Prozesser op Pabeier verdeelen, awer an der Realitéit lueden se an d'Haaptkierper Erënnerung nëmmen deen Deel deen hei an elo gefrot ass. Dofir gëtt d'Erënnerung verbonne mat engem Prozess virtuell genannt.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

De Betribssystem organiséiert virtuell a kierperlech Erënnerung a Säiten vun enger gewësser Gréisst. Soubal eng gewësse Säit vum virtuelle Gedächtnis gefuerdert ass, lued de Betribssystem et a kierperlecht Gedächtnis a passt hinnen an engem speziellen Dësch. Wann et keng gratis Plaze gëtt, da gëtt eng vun de virdru geluedene Säiten op d'Disk kopéiert, an deen, dee gefrot gëtt, hëlt seng Plaz. Dës Prozedur, op déi mer geschwënn zréckkommen, gëtt Tauschen genannt. D'Figur hei ënnen illustréiert de beschriwwene Prozess. Op et gouf Säit A mat Adress 0 gelueden an op der Haaptspeichersäit mat Adress 4 gesat.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

D'Geschicht ass genau d'selwecht mat Dateien, déi an d'Erënnerung gemappt sinn. Logischerweis si se vermeintlech kontinuéierlech a ganz am virtuelle Adressraum lokaliséiert. Wéi och ëmmer, si gitt physesch Erënnerung Säit fir Säit an nëmmen op Ufro. D'Ännerung vun esou Säiten ass mat der Datei op der Disk synchroniséiert. Op dës Manéier kënnt Dir Datei-I/O ausféieren andeems Dir einfach mat Bytes an der Erënnerung schafft - all Ännerungen ginn automatesch vum Betribssystemkär an d'Quelldatei transferéiert.​

D'Bild hei drënner weist wéi LMDB säin Zoustand synchroniséiert wann Dir mat der Datebank aus verschiddene Prozesser schafft. Andeems mir déi virtuell Erënnerung vu verschiddene Prozesser op déiselwecht Datei kartéieren, verflichte mir de Betribssystem de facto fir verschidde Blocke vun hiren Adressraim transitiv mateneen ze synchroniséieren, wou LMDB kuckt.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Eng wichteg Nuance ass datt LMDB, Par défaut, d'Datedatei duerch de Schreifsystem Uruffmechanismus ännert, a weist d'Datei selwer am Read-only Modus. Dës Approche huet zwou wichteg Konsequenzen.

Déi éischt Konsequenz ass gemeinsam fir all Betribssystemer. Seng Essenz ass de Schutz géint ongewollt Schued un der Datebank duerch falsche Code ze addéieren. Wéi Dir wësst, sinn déi ausführbar Instruktioune vun engem Prozess gratis fir Zougang zu Daten iwwerall a sengem Adressraum ze kréien. Zur selwechter Zäit, wéi mir eis just drun erënnert hunn, eng Datei am Lies-Schreifmodus ze weisen heescht datt all Instruktioun se och änneren kann. Wann hatt dëst duerch Feeler mécht, probéiert, zum Beispill, tatsächlech en Array-Element op engem net existente Index ze iwwerschreiwe, da kann se zoufälleg d'Datei, déi op dës Adress mapéiert ass, änneren, wat zu enger Korruptioun vun der Datebank féiert. Wann d'Datei am Read-only Modus ugewise gëtt, da féiert e Versuch de entspriechende Adressraum z'änneren zu engem Noutfall vum Programm mat engem Signal SIGSEGV, an d'Datei bleift intakt.

Déi zweet Konsequenz ass scho spezifesch fir iOS. Weder den Auteur nach keng aner Quellen ernimmen et explizit, awer ouni et wier LMDB net gëeegent fir op dësem mobilen Betribssystem ze lafen. Déi nächst Sektioun ass fir seng Iwwerleeung gewidmet.

Spezifizitéiten vun Erënnerung-mapped Fichieren am iOS

Et gouf e wonnerschéine Bericht um WWDC am Joer 2018 "iOS Memory Deep Dive". Et seet eis datt am iOS all Säiten an der kierperlecher Erënnerung eng vun 3 Aarte sinn: dreckeg, kompriméiert a propper.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Clean Memory ass eng Sammlung vu Säiten déi schmerzlos aus der kierperlecher Erënnerung entluede kënne ginn. D'Donnéeën, déi se enthalen, kënne wéi néideg aus hiren originelle Quellen nei geluede ginn. Read-only Memory-mapped Dateien falen an dës Kategorie. iOS fäert net d'Säiten, déi op eng Datei mapéiert sinn, zu all Moment aus der Erënnerung ze entluede, well se garantéiert sinn mat der Datei op der Disk synchroniséiert ze ginn.

All geännert Säiten kommen an dreckeg Erënnerung, egal wou se ursprénglech waren. Besonnesch Gedächtnismapéiert Dateien, déi geännert ginn duerch Schreiwen an d'virtuell Erënnerung, déi mat hinne verbonne sinn, ginn op dës Manéier klasséiert. Ouverture LMDB mat Fändel MDB_WRITEMAP, nodeems Dir Ännerunge gemaach hutt, kënnt Dir dëst perséinlech verifizéieren

Soubal eng Applikatioun ufänkt ze vill kierperlecht Gedächtnis opzehuelen, ënnersetzt iOS se dreckeg Säit Kompressioun. Dat Ganzt Gedächtnis, dat vu dreckeg a kompriméierte Säiten besat ass, mécht de sougenannte Memory Footprint vun der Applikatioun aus. Wann et e bestëmmte Schwellwäert erreecht huet, kënnt den OOM Killer System Daemon nom Prozess a schléisst en zwangsleefeg of. Dëst ass d'Besonderheet vum iOS am Verglach zu Desktop Betribssystemer. Am Géigesaz, d'Reduktioun vum Gedächtnisofdrock duerch d'Austausch vu Säiten vu kierperlecher Erënnerung op Disk gëtt net am iOS geliwwert. Vläicht ass d'Prozedur fir d'Säiten intensiv ze plënneren op Disk an zréck ze energieopwendeg fir mobilen Apparater, oder iOS spuert d'Ressource fir d'Zellen op SSD Drive ze schreiwen, oder vläicht waren d'Designer net zefridden mat der Gesamtleistung vum System, wou alles ass. konstant gewiesselt. Egal wéi et ass, d'Tatsaach bleift e Fakt.

Déi gutt Noriicht, scho virdru erwähnt, ass datt LMDB als Standard net de mmap Mechanismus benotzt fir Dateien ze aktualiséieren. Dëst bedeit datt déi ugewisen Donnéeën vum iOS als propper Gedächtnis klasséiert ginn an net zum Memory Footprint bäidroen. Dir kënnt dëst verifizéieren mat engem Xcode-Tool mam Numm VM Tracker. De Screenshot hei ënnen weist den Zoustand vun der iOS virtueller Erënnerung vun der Cloud Applikatioun wärend der Operatioun. Am Ufank goufen 2 LMDB Instanzen dran initialiséiert. Déi éischt war erlaabt seng Datei op 1GiB vu virtueller Erënnerung ze weisen, déi zweet - 512MiB. Trotz der Tatsaach, datt béid Stockage eng gewësse Quantitéit vun Awunner Erënnerung besetzen, weder vun hinnen dréit dreckeg Gréisst.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

An elo ass et Zäit fir schlecht Nouvellen. Dank dem Tauschmechanismus an 64-Bit Desktop Betribssystemer kann all Prozess esou vill virtuell Adressplaz besetzen wéi de fräie Festplackraum fir säi potenziellen Austausch erlaabt. Swap Ersatz mat Kompressioun am iOS reduzéiert radikal den theoreteschen Maximum. Elo mussen all lieweg Prozesser an den Haapt (liesen RAM) Erënnerung passen, an all déi, déi net passen, musse gezwongen sinn opzehalen. Dëst ass uginn wéi an der uewen ernimmt berichten, a offiziell Dokumentatioun. Als Konsequenz limitéiert iOS staark d'Quantitéit u Erënnerung verfügbar fir d'Allokatioun iwwer mmap. Hei hei Dir kënnt d'empiresch Limite vun der Quantitéit un Erënnerung kucken, déi op verschidden Apparater mat dësem Systemruff zougewisen ka ginn. Op de modernste Smartphone-Modeller ass iOS ëm 2 Gigabyte generéis ginn, an op Top-Versioune vum iPad - ëm 4. An der Praxis musst Dir natierlech op déi ënnescht ënnerstëtzt Apparatmodeller konzentréieren, wou alles ganz traureg ass. Nach méi schlëmm, andeems Dir den Erënnerungszoustand vun der Applikatioun am VM Tracker kuckt, fannt Dir datt LMDB wäit vun deen eenzegen ass, deen behaapt datt se Memory-mapped ass. Gutt Stécker gi vu Systemallokateuren, Ressourcedateien, Bildframeworks an aner méi kleng Feinde giess.

Baséierend op d'Resultater vun Experimenter an der Cloud, si mir op déi folgend Kompromësswäerter komm fir d'Erënnerung, déi vun LMDB zougewisen ass: 384 Megabytes fir 32-Bit Geräter an 768 fir 64-Bit Apparater. Nodeems dëse Volume benotzt ass, fänken all Ännerungsoperatioune mam Code op MDB_MAP_FULL. Mir beobachten esou Feeler an eiser Iwwerwaachung, awer si si kleng genuch datt se an dësem Stadium vernoléissegt kënne ginn.

En net offensichtleche Grond fir exzessiv Erënnerungsverbrauch duerch d'Späichere ka laanglieweg Transaktioune sinn. Fir ze verstoen, wéi dës zwee Phänomener matenee verbonne sinn, wäerte mir gehollef ginn andeems Dir déi aner zwee Piliere vun der LMDB berücksichtegt.

3.2. Wal #2. B+ Bam

Fir Dëscher uewen op enger Schlësselwäerterlagerung ze emuléieren, mussen déi folgend Operatiounen a senger API präsent sinn:

  1. En neit Element setzen.
  2. Sich no engem Element mat engem bestëmmte Schlëssel.
  3. Ewechzehuelen en Element.
  4. Iteréiert iwwer Intervalle vu Schlësselen an der Uerdnung déi se sortéiert sinn.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS UwendungenDéi einfachst Datestruktur, déi all véier Operatiounen einfach ëmsetzen kann ass e binäre Sichbaum. Jiddereng vun hiren Node stellt e Schlëssel duer, deen de ganzen Ënnerdeel vun de Kannerschlësselen an zwee Subtrees trennt. Déi lénks enthält déi méi kleng wéi d'Elteren, an déi riets enthält déi méi grouss. En bestallte Set vu Schlësselen ze kréien ass duerch ee vun de klassesche Bamtraversals erreecht

Binär Beem hunn zwee fundamental Mängel, déi verhënneren datt se effektiv als Disk-baséiert Datestruktur sinn. Als éischt ass de Grad vun hirem Gläichgewiicht onberechenbar. Et besteet e wesentleche Risiko fir Beem ze kréien, an deenen d'Héichte vu verschiddene Branchen immens ënnerscheeden kënnen, wat d'algorithmesch Komplexitéit vun der Sich wesentlech verschlechtert am Verglach zu deem wat erwaart gëtt. Zweetens, d'Heefegkeet vun Kräiz-Linken tëscht Wirbelen entzu binär Beem vun Uertschaft am Gedächtnis zougemaach Node (a punkto Verbindungen tëscht hinnen) kann op komplett verschiddene Säiten an virtuell Erënnerung lokaliséiert ginn. Als Konsequenz kann och eng einfach Traversal vu verschiddenen Nopeschnoden an engem Bam eng vergläichbar Zuel vu Säiten besichen. Dëst ass e Problem och wa mir iwwer d'Effizienz vu binäre Beem als In-Memory Datestruktur schwätzen, well dauernd rotéierend Säiten am Prozessor-Cache ass kee bëllege Genoss. Wann et drëm geet dacks Säiten ze recuperéieren verbonne mat Noden vun der Disk, gëtt d'Situatioun komplett bedauerlech.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS UwendungenB-Beem, déi eng Evolutioun vu binäre Beem sinn, léisen d'Problemer, déi am virege Paragraph identifizéiert goufen. Als éischt si se selbstverständlech. Zweetens, all eenzel vun hiren Noden trennt de Set vu Kannerschlësselen net an 2, mee an M bestallt Ënnersätz, an d'Zuel M kann zimlech grouss sinn, an der Uerdnung vun e puer honnert, oder souguer Dausende.

Doduerch:

  1. All Node enthält eng grouss Zuel vu scho bestallte Schlësselen an d'Beem si ganz kuerz.
  2. De Bam kritt d'Besëtz vun der Lokalitéit vun der Plaz an der Erënnerung, well Schlësselen, déi am Wäert no sinn, natierlech niewenteneen op de selwechten oder Nopeschnoden.
  3. D'Zuel vun den Transitknäppchen wann Dir e Bam erofgeet während enger Sichoperatioun gëtt reduzéiert.
  4. D'Zuel vun den Zilknäpper, déi während Range-Ufroen gelies ginn, gëtt reduzéiert, well jidderee vun hinnen schonn eng grouss Zuel vun bestallte Schlësselen enthält.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

LMDB benotzt eng Variatioun vum B-Bam genannt B + Bam fir Daten ze späicheren. D'Diagramm uewen weist déi dräi Aarte vu Wirbelen déi dran existéieren:

  1. Am Top ass d'Wurzel. Et materialiséiert näischt méi wéi d'Konzept vun enger Datebank an engem Lager. Bannent enger LMDB Instanz kënnt Dir e puer Datenbanken erstellen déi e kartéierte virtuelle Adressraum deelen. Jidderee vun hinnen fänkt vu senger eegener Root un.
  2. Um ënneschten Niveau sinn d'Blieder. Si an nëmmen si enthalen d'Schlësselwäertpaaren, déi an der Datebank gespäichert sinn. Iwwregens ass dëst d'Besonderheet vu B+-Beem. Wann e reegelméissege B-Bam Wäertdeeler an Noden vun allen Niveauen späichert, dann ass d'B+ Variatioun nëmmen um niddregsten. Nodeems dës Tatsaach fixéiert ass, nenne mir weider den Ënnertyp vum Bam deen an LMDB benotzt gëtt einfach e B-Bam.
  3. Tëscht der Wuerzel a Blieder ginn et 0 oder méi technesch Niveauen mat Navigatiouns- (Branche) Wirbelen. Hir Aufgab ass de zortéierte Set vu Schlësselen tëscht de Blieder ze trennen.

Kierperlech sinn Noden Erënnerungsblocken vun enger virbestëmmter Längt. Hir Gréisst ass e Multiple vun der Gréisst vun Erënnerung Säiten am Betribssystem, déi mir uewen diskutéiert. D'Node Struktur gëtt ënnendrënner gewisen. Den Header enthält Meta Informatioun, déi offensichtlechst vun deenen zum Beispill de Checksum ass. Als nächst kënnt Informatioun iwwer d'Offsets an deenen d'Zellen mat Daten sinn. D'Daten kënnen entweder Schlësselen sinn, wa mir iwwer Navigatiounsknäppchen schwätzen, oder ganz Schlësselwäertpaaren am Fall vu Blieder.​ Dir kënnt méi iwwer d'Struktur vun de Säiten an der Aarbecht liesen. "Evaluatioun vun High Performance Key-Value Stores".

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Nodeems mir den internen Inhalt vu Säitnoden beschäftegt hunn, wäerte mir de LMDB B-Bam weider op eng vereinfacht Manéier an der folgender Form vertrieden.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Säiten mat Node sinn sequenziell op der Disk lokaliséiert. Méi héich nummeréiert Säiten sinn um Enn vun der Datei. Déi sougenannte Meta-Säit enthält Informatiounen iwwer d'Offsets, duerch déi d'Wuerzelen vun alle Beem fonnt kënne ginn. Wann Dir eng Datei opmaacht, scannt LMDB d'Datei Säit fir Säit vun Enn bis Ufank op der Sich no enger gültiger Meta Säit an duerch se fënnt existent Datenbanken.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Elo, mat enger Iddi vun der logescher a kierperlecher Struktur vun der Dateorganisatioun, kënne mir weidergoen fir den drëtte Pilier vun der LMDB ze berücksichtegen. Et ass mat senger Hëllef datt all Späichermodifikatioune transaktiounsfäeg an isoléiert vuneneen geschéien, wat d'Datebank als Ganzt d'Eegeschafte vu Multiversion gëtt.

3.3. Wal #3. Copy-op-schreiwen

E puer B-Bam Operatiounen involvéieren eng Serie vu Ännerunge fir seng Wirbelen ze maachen. E Beispill ass en neie Schlëssel fir en Node derbäi ze ginn, dee scho seng maximal Kapazitéit erreecht huet. An dësem Fall ass et néideg, éischtens, den Node an zwee opzedeelen, an zweetens, e Link op den neie Budding Kand Node a sengem Elterendeel ze addéieren. Dës Prozedur ass potenziell ganz geféierlech. Wann aus irgendege Grënn (Crash, Stroumausbroch, etc.) nëmmen en Deel vun den Ännerungen aus der Serie optrieden, da bleift de Bam an engem inkonsistente Staat.

Eng traditionell Léisung fir eng Datebank Feeler-tolerant ze maachen ass eng zousätzlech On-Disk Datenstruktur nieft dem B-Bam ze addéieren - en Transaktiounslog, och bekannt als Write-ahead Log (WAL). Et ass e Fichier um Enn vun deem déi virgesinn Operatioun strikt geschriwwe gëtt ier de B-Bam selwer geännert gëtt. Also, wann d'Datekorruptioun während der Selbstdiagnos festgestallt gëtt, konsultéiert d'Datebank de Logbuch fir sech selwer an Uerdnung ze setzen.

LMDB huet eng aner Method als Feeler Toleranz Mechanismus gewielt, genannt Kopie-op-schreiwen. Seng Essenz ass datt amplaz Daten op enger existéierender Säit ze aktualiséieren, et fir d'éischt ganz kopéiert an all Ännerungen an der Kopie mécht.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Als nächst, fir datt déi aktualiséiert Donnéeën verfügbar sinn, ass et néideg de Link op den Node z'änneren, deen a sengem Elterenknuet aktuell ginn ass. Well et och dofir muss geännert ginn, gëtt se och virdru kopéiert. De Prozess geet rekursiv bis an d'Wurzel weider. Déi lescht Saach fir z'änneren ass d'Donnéeën op der Meta Säit

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Wann de Prozess op eemol während der Aktualiséierungsprozedur klappt, da gëtt entweder eng nei Meta Säit net erstallt, oder se gëtt net komplett op den Disk geschriwwe ginn, a seng Kontrollsumme wäert falsch sinn. An entweder vun dësen zwee Fäll sinn nei Säiten net erreechbar, awer al ginn net betraff. Dëst eliminéiert d'Noutwendegkeet fir LMDB fir viraus Log ze schreiwen fir Datenkonsistenz z'erhalen. De facto hëlt d'Struktur vun der Datelagerung op der uewe beschriwwener Disk gläichzäiteg seng Funktioun un. D'Feele vun engem expliziten Transaktiounsprotokoll ass eng vun de Feature vun LMDB déi héich Date Liesgeschwindegkeet ubitt

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Deen resultéierende Design, genannt append-nëmmen B-Bam, bitt natierlech Transaktiounsisolatioun a Multi-Versionéierung. An LMDB ass all oppen Transaktioun mat der aktueller relevanter Bamwurzel assoziéiert. Bis d'Transaktioun ofgeschloss ass, ginn d'Säiten vum Bam, déi domat verbonne sinn, ni geännert oder nei Versioune vun den Donnéeën benotzt d'Transaktioun gouf opgemaach, och wann d'Späichere weider aktiv zu dëser Zäit aktualiséiert gëtt. Dëst ass d'Essenz vun der Multiversion, déi LMDB eng ideal Datequell fir eis beléifte mécht UICollectionView. Nodeems Dir eng Transaktioun opgemaach hutt, ass et net néideg den Erënnerungsofdrock vun der Applikatioun ze erhéijen andeems se séier aktuell Daten an eng In-Memory Struktur auspompelen, aus Angscht ze bleiwen mat näischt. Dës Fonktioun ënnerscheet LMDB vun der selwechter SQLite, déi net mat sou enger totaler Isolatioun ka prägen. Nodeems Dir zwou Transaktiounen an der leschter opgemaach hutt an e bestëmmte Rekord an engem vun hinnen geläscht hutt, ass et net méi méiglech dee selwechte Rekord an der zweeter verbleiwen ze kréien.

D'Säit vun der Mënz ass de potenziell wesentlech méi héije Konsum vu virtueller Erënnerung. De Rutsch weist wéi d'Datebankstruktur ausgesäit wann se gläichzäiteg mat 3 oppene Liestransaktiounen geännert gëtt, déi verschidde Versioune vun der Datebank kucken. Zënter LMDB kann Noden net erreechbar vu Wuerzelen, déi mat aktuellen Transaktioune verbonne sinn, weiderbenotzen, huet de Buttek keng aner Wiel wéi eng aner véiert Root an d'Erënnerung ze verdeelen an nach eng Kéier déi geännert Säiten drënner ze klonen.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Hei wier et nëtzlech fir d'Sektioun iwwer Memory-mapped Dateien z'erënneren. Et schéngt, datt den zousätzleche Verbrauch vu virtueller Erënnerung eis net vill Suerge sollt maachen, well et net zum Erënnerungsofdrock vun der Applikatioun bäidréit. Wéi och ëmmer, gläichzäiteg gouf bemierkt datt iOS ganz knaschteg ass fir se ze verdeelen, a mir kënnen net, wéi op engem Server oder Desktop, eng LMDB Regioun vun 1 Terabyte ubidden a guer net iwwer dës Feature denken. Wa méiglech, sollt Dir probéieren d'Liewensdauer vun Transaktiounen esou kuerz wéi méiglech ze maachen.

4. Entwerfen vun engem Dateschema uewen op der Schlësselwäert API

Loosst eis eis API Analyse starten andeems Dir d'Basisabstraktioune vun LMDB kuckt: Ëmfeld an Datenbanken, Schlësselen a Wäerter, Transaktiounen a Cursoren.

Eng Note iwwer Code Opféierungen

All Funktiounen an der ëffentlecher LMDB API ginn d'Resultat vun hirer Aarbecht a Form vun engem Fehlercode zréck, awer an all spéider Oplëschtunge gëtt seng Verifizéierung ausgeschloss fir Kuerzegkeet. Forschett C++ wrappers lmbxx, an deem Feeler als C ++ Ausnahmen materialiséiert ginn.

Als de schnellste Wee fir LMDB mat engem Projet fir iOS oder MacOS ze verbannen, proposéiere ech meng CocoaPod POSLMDB.

4.1. Basis Abstraktiounen

Ëmwelt

Struktur MDB_env ass de Repository vum internen Zoustand vun der LMDB. Präfix Funktioun Famill mdb_env erlaabt Iech e puer vu senge Eegeschaften ze konfiguréieren. Am einfachste Fall gesäit d'Motorinitialiséierung esou aus.

mdb_env_create(env);​
mdb_env_set_map_size(*env, 1024 * 1024 * 512)​
mdb_env_open(*env, path.UTF8String, MDB_NOTLS, 0664);

An der Mail.ru Cloud Applikatioun hu mir d'Standardwäerter vun nëmmen zwee Parameteren geännert.

Déi éischt ass d'Gréisst vum virtuelle Adressraum, op deem d'Späicherdatei mapéiert ass. Leider, och op deemselwechten Apparat, kann de spezifesche Wäert wesentlech vu Laf zu Laf variéieren. Fir dës Feature vum iOS ze berücksichtegen, gëtt de maximale Späichervolumen dynamesch gewielt. Vun engem bestëmmte Wäert un, gëtt se sequentiell halbéiert bis d'Funktioun mdb_env_open wäert net e Resultat anescht aus zréck ENOMEM. An der Theorie gëtt et och de Géigendeel - fir d'éischt e Minimum un Erënnerung un de Motor ze verdeelen, an dann, wann d'Feeler opgeholl ginn, MDB_MAP_FULL, Erhéijung et. Allerdéngs ass et vill méi schwaach. De Grond ass datt d'Prozedur fir d'Erënnerung nei ze verdeelen (remap) mat der Funktioun mdb_env_set_map_size invalidéiert all Entitéiten (Cursoren, Transaktiounen, Schlësselen a Wäerter) déi virdru vum Motor kritt goufen. Dës Tour vun Eventer am Code berücksichtegen wäert zu senger bedeitender Komplikatioun féieren. Wann awer d'virtuell Erënnerung fir Iech ganz wichteg ass, da kann dëst e Grond sinn fir d'Gabel méi no ze kucken, déi wäit virgaang ass MDBX, Wou ënnert den ugekënnegten Fonctiounen et "automatesch on-the-fly Datebank Gréisst Upassung".

Den zweeten Parameter, de Standardwäert vun deem eis net gepasst huet, reguléiert d'Mechanik fir d'Gewënnsécherheet ze garantéieren. Leider huet op d'mannst iOS 10 Probleemer mat der Ënnerstëtzung fir thread lokal Späicheren. Aus dësem Grond, am Beispill hei uewen, gëtt de Repository mam Fändel opgemaach MDB_NOTLS. Zousätzlech zu dësem war et och néideg Forschett C++ wrapper lmbxxVariabelen mat dësem Attribut an dran auszeschneiden.

Datenbanken

D'Datebank ass eng separat B-Bam Instanz, déi mir hei uewen diskutéiert hunn. Seng Ouverture geschitt bannent enger Transaktioun, déi am Ufank e bësse komesch schéngen kann.

MDB_txn *txn;​
MDB_dbi dbi;​
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);​
mdb_dbi_open(txn, NULL, MDB_CREATE, &dbi);​
mdb_txn_abort(txn);

Tatsächlech ass eng Transaktioun an LMDB eng Späicherentitéit, net eng spezifesch Datebankentitéit. Dëst Konzept erlaabt Iech atomar Operatiounen op Entitéiten auszeféieren, déi a verschiddenen Datenbanken lokaliséiert sinn. An der Theorie mécht dëst d'Méiglechkeet op fir Dëscher a Form vu verschiddenen Datenbanken ze modelléieren, awer op eng Kéier hunn ech en anere Wee gemaach, am Detail hei ënnen beschriwwen.

Schlësselen a Wäerter

Struktur MDB_val Modeller d'Konzept vu Schlëssel a Wäert. De Repository huet keng Ahnung iwwer hir Semantik. Fir hatt ass eppes anescht just eng Rei vu Bytes vun enger bestëmmter Gréisst. Déi maximal Schlësselgréisst ass 512 Bytes.

typedef struct MDB_val {​
    size_t mv_size;​
    void *mv_data;​
} MDB_val;​​

Mat engem Comparator sortéiert de Buttek d'Schlësselen an opsteigend Uerdnung. Wann Dir et net mat Ärem eegenen ersetzt, da gëtt de Standard benotzt, wat se byte-by-byte a lexikographescher Uerdnung sortéiert.

Transaktiounen

D'Transaktiounsstruktur gëtt am Detail beschriwwen virdrun Kapitel, also hei wäert ech hir Haapteigenschaften kuerz widderhuelen:

  1. Ënnerstëtzt all Basiseigenschaften sauerem: Atomitéit, Konsistenz, Isolatioun an Zouverlässegkeet. Ech kann net hëllefen awer ze notéieren datt et e Feeler ass wat d'Haltbarkeet op macOS an iOS ugeet, deen am MDBX fixéiert gouf. Dir kënnt méi an hirem liesen README.
  2. D'Approche fir Multithreading gëtt vum Schema "Single Writer / Multiple Reader" beschriwwen. Schrëftsteller blockéieren all aner, awer blockéieren d'Lieser net. Lieser blockéieren net Schrëftsteller oder all aner.
  3. Ënnerstëtzung fir nestéiert Transaktiounen.
  4. Multiversion Ënnerstëtzung.

Multiversion an LMDB ass sou gutt datt ech et an Aktioun demonstréiere wëll. Vum Code hei drënner kënnt Dir gesinn datt all Transaktioun genau mat der Versioun vun der Datebank funktionnéiert déi aktuell war an der Zäit wou se opgemaach gouf, komplett isoléiert vun all spéider Ännerungen. D'Initialiséierung vun der Späichere an derbäi en Testrekord dozou stellt näischt interessant duer, sou datt dës Ritualen ënner dem Spoiler bleiwen.

Dobäizemaachen vun engem Test Entrée

MDB_env *env;
MDB_dbi dbi;
MDB_txn *txn;

mdb_env_create(&env);
mdb_env_open(env, "./testdb", MDB_NOTLS, 0664);

mdb_txn_begin(env, NULL, 0, &txn);
mdb_dbi_open(txn, NULL, 0, &dbi);
mdb_txn_abort(txn);

char k = 'k';
MDB_val key;
key.mv_size = sizeof(k);
key.mv_data = (void *)&k;

int v = 997;
MDB_val value;
value.mv_size = sizeof(v);
value.mv_data = (void *)&v;

mdb_txn_begin(env, NULL, 0, &txn);
mdb_put(txn, dbi, &key, &value, MDB_NOOVERWRITE);
mdb_txn_commit(txn);

MDB_txn *txn1, *txn2, *txn3;
MDB_val val;

// Открываем 2 транзакции, каждая из которых смотрит
// на версию базы данных с одной записью.
mdb_txn_begin(env, NULL, 0, &txn1); // read-write
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn2); // read-only

// В рамках первой транзакции удаляем из базы данных существующую в ней запись.
mdb_del(txn1, dbi, &key, NULL);
// Фиксируем удаление.
mdb_txn_commit(txn1);

// Открываем третью транзакцию, которая смотрит на
// актуальную версию базы данных, где записи уже нет.
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn3);
// Убеждаемся, что запись по искомому ключу уже не существует.
assert(mdb_get(txn3, dbi, &key, &val) == MDB_NOTFOUND);
// Завершаем транзакцию.
mdb_txn_abort(txn3);

// Убеждаемся, что в рамках второй транзакции, открытой на момент
// существования записи в базе данных, её всё ещё можно найти по ключу.
assert(mdb_get(txn2, dbi, &key, &val) == MDB_SUCCESS);
// Проверяем, что по ключу получен не абы какой мусор, а валидные данные.
assert(*(int *)val.mv_data == 997);
// Завершаем транзакцию, работающей хоть и с устаревшей, но консистентной базой данных.
mdb_txn_abort(txn2);

Ech recommandéieren dass Dir déi selwecht Trick mat SQLite probéieren a gesinn wat geschitt.

Multiversion bréngt ganz flott Virdeeler fir d'Liewen vun engem iOS Entwéckler. Mat dëser Propriétéit kënnt Dir einfach an natierlech den Update Taux vun der Datequell fir Écran Formen ajustéieren, baséiert op Benotzer Erfahrung Considératiounen. Zum Beispill, loosst eis eng Feature vun der Mail.ru Cloud Applikatioun huelen, sou wéi d'Autoloading vum Inhalt vun der Systemmediagalerie. Mat enger gudder Verbindung kann de Client e puer Fotoen pro Sekonn op de Server addéieren. Wann Dir no all Download aktualiséiert UICollectionView mat Medieninhalt an der Wollek vum Benotzer, kënnt Dir iwwer 60 fps vergiessen a glat Scrollen während dësem Prozess. Fir heefeg Écranupdates ze vermeiden, musst Dir iergendwéi den Taux limitéieren, mat deem d'Donnéeën am Basisdaten änneren UICollectionViewDataSource.

Wann d'Datebank keng Multiversion ënnerstëtzt an Iech erlaabt nëmme mam aktuellen aktuellen Zoustand ze schaffen, da fir en Zäitstabile Snapshot vun den Donnéeën ze kreéieren, musst Dir se entweder op eng In-Memory-Datenstruktur oder op eng temporär Tabell kopéieren. All vun dësen Approche ass ganz deier. Am Fall vun In-Memory Späichere kréie mir Käschten souwuel an der Erënnerung, verursaacht duerch d'Späichere vun konstruéierten Objeten, an an der Zäit, verbonne mat redundante ORM Transformatiounen. Wat den temporäre Dësch ugeet, ass dat nach méi deier Genoss, nëmme Sënn an net-triviale Fäll.

Dem LMDB seng Multiversion Léisung léist de Problem fir eng stabil Datenquell op eng ganz elegant Manéier z'erhalen. Et ass genuch just eng Transaktioun opzemaachen a voila - bis mir et fäerdeg bréngen, ass den Dateset garantéiert fixéiert. D'Logik fir seng Updategeschwindegkeet ass elo ganz an den Hänn vun der Presentatiounsschicht, ouni Overhead vu bedeitende Ressourcen.

Cursoren

Cursoren bidden e Mechanismus fir uerdentlech Iteratioun iwwer Schlësselwäertpaar duerch B-Bam Traversal. Ouni si wier et onméiglech d'Tabellen an der Datebank effektiv ze modelléieren, op déi mir elo wenden.

4.2. Dësch Modeller

D'Eegeschafte vu Schlësselbestellung erlaabt Iech eng Héichniveau Abstraktioun ze konstruéieren wéi en Dësch uewen op Basisabstraktiounen. Loosst eis dëse Prozess betruechten andeems Dir d'Beispill vun der Haapttabell vun engem Cloud Client benotzt, deen Informatioun iwwer all Dateien an Ordner vum Benotzer cache.

Dësch Schema

Ee vun de gemeinsame Szenarie fir déi eng Tabellstruktur mat engem Ordnerbaum soll ugepasst ginn ass d'Auswiel vun all Elementer an engem bestëmmte Verzeechnes E gudden Dateorganisatiounsmodell fir effizient Ufroen vun dëser Aart ass Adjacence Lëscht. Fir et uewen op der Schlësselwäertspäicherung ëmzesetzen, ass et néideg d'Schlëssel vun de Fichieren an Ordner esou ze sortéieren datt se op Basis vun hirer Memberschaft am Elterenverzeichnis gruppéiert ginn. Zousätzlech, fir den Inhalt vum Verzeechnes an der Form ze weisen, déi e Windows Benotzer vertraut ass (éischt Classeuren, dann Dateien, allebéid alphabetesch zortéiert), ass et néideg déi entspriechend zousätzlech Felder am Schlëssel ze enthalen.

D'Bild hei ënnen weist wéi, baséiert op der Aufgab, eng Duerstellung vu Schlësselen a Form vun engem Byte-Array kéint ausgesinn. D'Bytes mam Identifizéierer vum Elterenverzeechnes (rout) ginn als éischt plazéiert, dann mam Typ (gréng) an am Schwäif mam Numm (blo) Gëtt no der Default LMDB Comparator an der lexikographescher Uerdnung sortéiert néideg Manéier. Sequentiell duerchschnëttlech Schlësselen mam selwechte roude Präfix ginn eis hir assoziéiert Wäerter an der Uerdnung, déi se an der User-Interface (riets riets) ugewise solle ginn, ouni zousätzlech Postveraarbechtung ze erfuerderen.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Serialiséieren Schlësselen a Wäerter

Vill Methoden fir d'Serialiséierung vun Objeten sinn op der Welt erfonnt ginn. Well mir keng aner Ufuerderung wéi d'Geschwindegkeet haten, hu mir déi séierst méiglech fir eis gewielt - en Dump vun der Erënnerung besat vun enger Instanz vun der C Sproochstruktur Also kann de Schlëssel vun engem Verzeichniselement mat der folgender Struktur modelléiert ginn NodeKey.

typedef struct NodeKey {​
    EntityId parentId;​
    uint8_t type;​
    uint8_t nameBuffer[256];​
} NodeKey;

Späicheren NodeKey am Stockage néideg am Objet MDB_val positionéiert den Datepointer op d'Adress vum Ufank vun der Struktur, a berechent hir Gréisst mat der Funktioun sizeof.

MDB_val serialize(NodeKey * const key) {
    return MDB_val {
        .mv_size = sizeof(NodeKey),
        .mv_data = (void *)key
    };
}

Am éischten Kapitel iwwer Datebank Selektiounscritèren hunn ech d'Minimaliséierung vun dynamesche Allokatiounen bannent CRUD Operatiounen als e wichtege Selektiounsfaktor ernimmt. Funktioun Code serialize weist wéi am Fall vun LMDB se komplett vermeide kënne ginn wann Dir nei records an d'Datebank setzt. Déi erakommen Byte-Array vum Server gëtt als éischt an Stackstrukturen transforméiert, an dann gi se trivial an d'Späichere gedumpt. Bedenkt datt et och keng dynamesch Allocatioune bannent LMDB sinn, kënnt Dir eng fantastesch Situatioun duerch iOS Standards kréien - benotzt nëmmen Stack Memory fir mat Daten iwwer de ganze Wee vum Netz op d'Disk ze schaffen!

Bestellen Schlësselen mat engem binäre Comparator

D'Schlësseluerdnungsrelatioun gëtt vun enger spezieller Funktioun spezifizéiert, déi e Comparator genannt gëtt. Zënter datt de Motor näischt iwwer d'Semantik vun de Bytes kennt, déi se enthalen, huet de Standardvergläicher keng aner Wiel wéi d'Schlësselen an der lexikographescher Uerdnung ze arrangéieren, op e Byte-by-Byte Verglach. Et ze benotzen fir Strukturen ze organiséieren ass ähnlech ze raséieren mat enger Axt. Wéi och ëmmer, an einfache Fäll fannen ech dës Method akzeptabel. D'Alternativ gëtt hei ënnen beschriwwen, awer hei bemierken ech e puer Raken, déi laanscht dëse Wee verspreet sinn.

Déi éischt Saach fir ze erënneren ass d'Erënnerungsvertriedung vu primitive Datentypen. Also, op all Apple Apparater, sinn ganz Zuel Variablen am Format gespäichert Kleng Endian. Dëst bedeit datt de mannsten bedeitende Byte op der lénker Säit wäert sinn, an et wäert net méiglech sinn ganz Zuelen mat engem Byte-by-Byte Verglach ze sortéieren. Zum Beispill, probéiert dëst mat enger Rei vun Zuelen vun 0 bis 511 ze maachen, wäert dat folgend Resultat produzéieren.

// value (hex dump)
000 (0000)
256 (0001)
001 (0100)
257 (0101)
...
254 (fe00)
510 (fe01)
255 (ff00)
511 (ff01)

Fir dëse Problem ze léisen, mussen d'Integer am Schlëssel an engem Format gëeegent fir de Byte-Byte Comparator gespäichert ginn. Fonctiounen aus der Famill hëllefen Iech déi néideg Transformatioun auszeféieren hton* (besonnesch htons fir Duebelbyte Zuelen aus dem Beispill).

D'Format fir Strings an der Programméierung ze representéieren ass, wéi Dir wësst, e Ganzt Geschicht. Wann d'Semantik vu Strings, souwéi d'Kodéierung benotzt fir se an der Erënnerung ze representéieren, suggeréiert datt et méi wéi ee Byte pro Charakter ka sinn, dann ass et besser d'Iddi direkt opzeginn fir e Standardvergläicher ze benotzen.

Déi zweet Saach am Kapp ze halen ass Ausriichtungsprinzipien Struktur Feld Compiler. Wéinst hinnen kënnen Bytes mat Gerempelswäerter an Erënnerung tëscht Felder geformt ginn, wat natierlech d'Byte-Byte Sortéierung brécht. Fir Müll ze eliminéieren, musst Dir entweder Felder an enger strikt definéierter Uerdnung deklaréieren, Ausriichtungsregelen am Kapp behalen, oder d'Attribut an der Strukturerklärung benotzen packed.

Bestellen Schlësselen mat engem externen Comparator

D'Schlësselvergläichslogik kann ze komplex sinn fir e binäre Vergläicher. Ee vun de ville Grënn ass d'Präsenz vun technesche Felder bannent Strukturen. Ech illustréieren hir Optriede mam Beispill vun engem Schlëssel fir e Verzeichniselement dat eis scho vertraut ass.

typedef struct NodeKey {​
    EntityId parentId;​
    uint8_t type;​
    uint8_t nameBuffer[256];​
} NodeKey;

Trotz senger Einfachheet verbraucht et an der grousser Majoritéit vu Fäll ze vill Erënnerung. De Puffer fir den Numm hëlt 256 Bytes op, obwuel an der Moyenne Datei- an Dossiernimm selten 20-30 Zeechen iwwerschreiden.

Eng vun de Standardtechnike fir d'Gréisst vun engem Rekord ze optimiséieren ass et op déi aktuell Gréisst ze "triméieren". Seng Essenz ass datt d'Inhalter vun all Variabel-Längt Felder an engem Puffer um Enn vun der Struktur gespäichert sinn, an hir Längt sinn a getrennte Variabelen gespäichert.​ No dëser Approche ass de Schlëssel NodeKey gëtt wéi follegt transforméiert.

typedef struct NodeKey {​
    EntityId parentId;​
    uint8_t type;​
    uint8_t nameLength;​
    uint8_t nameBuffer[256];​
} NodeKey;

Weider, wann serialiséieren, gëtt d'Dategréisst net spezifizéiert sizeof déi ganz Struktur, an d'Gréisst vun alle Felder ass eng fix Längt plus d'Gréisst vum tatsächlech benotzten Deel vum Puffer.

MDB_val serialize(NodeKey * const key) {
    return MDB_val {
        .mv_size = offsetof(NodeKey, nameBuffer) + key->nameLength,
        .mv_data = (void *)key
    };
}

Als Resultat vun der Refactoring hu mir bedeitend Erspuernisser am Raum kritt, dee vu Schlësselen besat ass. Allerdéngs wéinst dem technesche Beräich nameLength, de Standard binäre Comparator ass net méi gëeegent fir Schlësselvergläicher. Wa mir et net mat eisem eegenen ersetzen, da wäert d'Längt vum Numm e méi héije Prioritéitsfaktor bei der Sortéierung sinn wéi den Numm selwer.

LMDB erlaabt all Datebank seng eege Schlësselvergleichfunktioun ze hunn. Dëst gëtt mat der Funktioun gemaach mdb_set_compare strikt virun der Ouverture. Aus offensichtleche Grënn kann et net am ganze Liewen vun der Datebank geännert ginn. De Comparator kritt zwee Schlësselen am binäre Format als Input, a beim Ausgang gëtt et d'Vergläichsresultat zréck: manner wéi (-1), méi wéi (1) oder gläich wéi (0). Pseudocode fir NodeKey gesäit esou aus.

int compare(MDB_val * const a, MDB_val * const b) {​
    NodeKey * const aKey = (NodeKey * const)a->mv_data;​
    NodeKey * const bKey = (NodeKey * const)b->mv_data;​
    return // ...
}​

Soulaang all d'Schlësselen an der Datebank vum selwechten Typ sinn, ass d'bedingungslos hir Byte Representatioun un d'Aart vun der Applikatiounsschlësselstruktur legal. Et gëtt eng Nuance hei, awer et gëtt ënnendrënner an der Ënnersektioun "Reading Records" diskutéiert.

Serialiséierung Wäerter

LMDB schafft extrem intensiv mat de Schlëssele vu gespäichert records. Hire Verglach mat all aner geschitt am Kader vun all applizéiert Operatioun, an der Leeschtung vun der ganzer Léisung hänkt op der Vitesse vun Comparator. An enger idealer Welt soll de Standard binäre Comparator genuch sinn fir Schlësselen ze vergläichen, awer wann Dir Är eege benotze musst, da sollt d'Prozedur fir d'Deserialiséierung vu Schlësselen an deem sou séier wéi méiglech sinn.

D'Datebank ass net besonnesch interesséiert am Wäert Deel vum Rekord (Wäert). Seng Konversioun vun enger Byte Representatioun an en Objet geschitt nëmme wann et scho vum Applikatiounscode erfuerderlech ass, zum Beispill, et um Bildschierm ze weisen. Well dat geschitt relativ selten, sinn d'Vitesse Viraussetzunge fir dës Prozedur net esou kritesch, a senger Ëmsetzung si mir vill méi fräi op d'Bequemlechkeet ze konzentréieren Zum Beispill, ze serialiséieren Metadaten iwwer Fichieren, déi nach net erofgeluede goufen NSKeyedArchiver.

NSData *data = serialize(object);​
MDB_val value = {​
    .mv_size = data.length,​
    .mv_data = (void *)data.bytes​
};

Wéi och ëmmer, et ginn Zäiten wou d'Leeschtung nach ëmmer wichteg ass. Zum Beispill, wann Dir Metainformatioun iwwer d'Dateistruktur vun enger Benotzerwolk späichert, benotze mir déiselwecht Erënnerungsdump vun Objeten. Den Highlight vun der Aufgab fir eng serialiséiert Representatioun vun hinnen ze generéieren ass d'Tatsaach datt d'Elementer vun engem Verzeechnes vun enger Hierarchie vu Klassen modelléiert ginn.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Fir et an der C Sprooch ëmzesetzen, sinn spezifesch Felder vun den Ierwen an getrennten Strukturen plazéiert, an hir Verbindung mat der Basis gëtt duerch e Feld vun der Typunioun uginn. Den aktuellen Inhalt vun der Gewerkschaft gëtt iwwer den techneschen Attributtyp uginn.

typedef struct NodeValue {​
    EntityId localId;​
    EntityType type;​
    union {​
        FileInfo file;​
        DirectoryInfo directory;​
    } info;​
    uint8_t nameLength;​
    uint8_t nameBuffer[256];​
} NodeValue;​

Dobäizemaachen an Aktualiséierung opgetrueden

De serialiséierte Schlëssel a Wäert kënnen an de Buttek bäigefüügt ginn. Fir dëst ze maachen, benotzt d'Funktioun mdb_put.

// key и value имеют тип MDB_val​
mdb_put(..., &key, &value, MDB_NOOVERWRITE);

Op der Konfiguratiounsstadium kann d'Späichere erlaabt oder verbueden ginn fir verschidde Rekorder mat deemselwechte Schlëssel ze späicheren. Wann Fraying nëmmen als Resultat vun engem Feeler am Code ka geschéien, da kënnt Dir Iech dovunner schützen andeems Dir de Fändel spezifizéiert NOOVERWRITE.

Liesen Entréen

Fir records an LMDB ze liesen, benotzt d'Funktioun mdb_get. Wann d'Schlësselwäertpaar duerch virdru dumpte Strukturen vertruede gëtt, da gesäit dës Prozedur esou aus.

NodeValue * const readNode(..., NodeKey * const key) {​
    MDB_val rawKey = serialize(key);​
    MDB_val rawValue;​
    mdb_get(..., &rawKey, &rawValue);​
    return (NodeValue * const)rawValue.mv_data;​
}

Déi präsentéiert Oplëschtung weist wéi d'Serialiséierung duerch Strukturdump erlaabt Iech dynamesch Allokatiounen net nëmme beim Schreiwen ze läschen, mee beim Liesen vun Daten. Ofgeleet vu Funktioun mdb_get de Pointer kuckt genau op déi virtuell Erënnerungsadress wou d'Datebank d'Byte Representatioun vum Objet späichert. Tatsächlech kréie mir eng Aart ORM déi ganz héich Date Liesgeschwindegkeet bal gratis gëtt. Trotz all der Schéinheet vun der Approche, ass et néideg e puer Fonctiounen ze erënneren, déi mat him assoziéiert.

  1. Fir eng liesbar Transaktioun ass de Pointer op d'Wäertstruktur garantéiert nëmme valabel ze bleiwen bis d'Transaktioun zou ass. Wéi virdru scho bemierkt, bleiwen d'B-Bam Säiten op deenen en Objet läit, dank dem Copy-on-Write Prinzip, onverännert soulaang se vun op d'mannst eng Transaktioun referenzéiert sinn. Zur selwechter Zäit, soubal déi lescht Transaktioun assoziéiert mat hinnen ofgeschloss ass, kënnen d'Säite fir nei Donnéeën erëmbenotzt ginn. Wann et néideg ass fir Objeten d'Transaktioun ze iwwerliewen déi se generéiert hunn, da musse se nach ëmmer kopéiert ginn.
  2. Fir eng Liesschreiwe Transaktioun ass de Pointer op déi resultéierend Wäertstruktur nëmme valabel bis déi éischt Ännerungsprozedur (Daten schreiwen oder läschen).
  3. Obwuel d'Struktur NodeValue net vollwäerteg, awer ofgeschnidden (kuckt Ënnersektioun "Schlëssel bestellen mat engem externe Comparator"), kënnt Dir sécher op seng Felder duerch den Zeiger zougräifen. Den Haapt Saach ass et net z'ënnerscheeden!
  4. Ënner kengen Ëmstänn däerf d'Struktur duerch den Empfangszeechen geännert ginn. All Ännerungen mussen nëmmen duerch d'Methode gemaach ginn mdb_put. Wéi och ëmmer, egal wéi schwéier Dir dëst maache wëllt, ass et net méiglech, well d'Erënnerungsgebitt, wou dës Struktur läit, am readonly-Modus kartéiert ass.
  5. Remap eng Datei op de Prozess Adress Raum fir den Zweck, zum Beispill, d'maximal Späichergréisst mat der Funktioun ze erhéijen mdb_env_set_map_size komplett ongëlteg all Transaktiounen an Zesummenhang Entitéite am Allgemengen an Hiweiser op bestëmmten Objete besonnesch.

Schlussendlech ass eng aner Feature sou lëschteg datt seng Essenz opzeweisen net an nëmmen en anere Paragraph passt. Am Kapitel iwwer de B-Bam hunn ech en Diagramm ginn wéi seng Säiten an der Erënnerung arrangéiert sinn. Et folgt aus dësem datt d'Adress vum Ufank vum Puffer mat serialiséierten Donnéeën absolut arbiträr ka sinn. Dofir krut de Pointer hinnen an der Struktur MDB_val a reduzéiert zu engem Zeigefanger zu enger Struktur, et stellt sech eraus am allgemenge Fall unalignéiert. Zur selwechter Zäit erfuerderen d'Architekturen vun e puer Chips (am iOS ass dat armv7) datt d'Adress vun all Daten e Multiple vun der Gréisst vum Maschinnwuert ass oder, an anere Wierder, d'Bitgréisst vum System ( fir armv7 ass et 32 ​​Bits). An anere Wierder, eng Operatioun wéi *(int *foo)0x800002 op hinnen entsprécht Flucht a féiert zu Hiriichtung mat engem Uerteel EXC_ARM_DA_ALIGN. Et ginn zwou Méiglechkeeten esou en traurege Schicksal ze vermeiden.

Déi éischt geet erof op eng virleefeg Kopie vun Daten an eng offensichtlech ausgeriicht Struktur. Zum Beispill, op engem personaliséierte Comparator gëtt dëst wéi follegt reflektéiert.

int compare(MDB_val * const a, MDB_val * const b) {
    NodeKey aKey, bKey;
    memcpy(&aKey, a->mv_data, a->mv_size);
    memcpy(&bKey, b->mv_data, b->mv_size);
    return // ...
}

Eng alternativ Manéier ass de Compiler am Viraus z'informéieren datt Schlësselwäertstrukture vläicht net Attributer ausgeriicht sinn aligned(1). Op ARM kënnt Dir deeselwechten Effekt hunn erreechen a benotzt de gepackten Attribut. Bedenkt datt et och hëlleft fir de Raum ze optimiséieren, deen vun der Struktur besat ass, schéngt dës Methode mir léiwer, obwuel z'erreechen zu enger Erhéijung vun de Käschte vun Daten Zougang Operatiounen.

typedef struct __attribute__((packed)) NodeKey {
    uint8_t parentId;
    uint8_t type;
    uint8_t nameLength;
    uint8_t nameBuffer[256];
} NodeKey;

Range Ufroen

Fir iwwer eng Grupp vu Rekorder ze iteréieren, bitt LMDB eng Cursorabstraktioun. Loosst eis kucken wéi een domat schafft mat dem Beispill vun engem Dësch mat User Cloud Metadaten, déi eis scho vertraut sinn.

Als Deel vun der Lëscht vun Dateien an engem Verzeechnes ze weisen, ass et néideg all d'Schlësselen ze fannen, mat deenen seng Kannerdateien an Ordner verbonne sinn. An de fréiere Sektiounen hu mir d'Schlëssel zortéiert NodeKey sou datt se haaptsächlech vun der ID vum Elterenverzeichnis bestallt ginn. Also, technesch ass d'Aufgab fir den Inhalt vun engem Dossier zréckzekommen, de Cursor op der ieweschter Grenz vun der Schlësselgrupp mat engem bestëmmte Präfix ze placéieren an dann op déi ënnescht Grenz ze iteréieren.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Déi iewescht Grenz kann direkt duerch sequenziell Sich fonnt ginn. Fir dëst ze maachen, gëtt de Cursor um Ufank vun der ganzer Lëscht vu Schlësselen an der Datebank plazéiert a weider eropgebaut bis e Schlëssel mam Identifizéierer vum Elterenverzeechnes drënner erschéngt. Dës Approche huet 2 offensichtlech Nodeeler:

  1. Linearer Sichkomplexitéit, obwuel, wéi bekannt, a Beem am allgemengen an an engem B-Bam besonnesch an der logarithmescher Zäit duerchgefouert ka ginn.
  2. Vergeblech ginn all Säiten, déi virun der gesicht ginn, vun der Datei an d'Haaptspeicher opgehuewen, wat extrem deier ass.

Glécklecherweis bitt d'LMDB API en effektive Wee fir de Cursor am Ufank ze positionéieren Fir dëst ze maachen, musst Dir e Schlëssel generéieren deem säi Wäert offensichtlech manner wéi oder gläich ass wéi de Schlëssel op der ieweschter Grenz vum Intervall. Zum Beispill, a Relatioun zu der Lëscht an der Figur uewendriwwer, kënne mir e Schlëssel maachen an deem d'Feld parentId wäert gläich sinn 2, an all de Rescht si mat Nullen gefëllt. Sou en deelweis gefëllte Schlëssel gëtt un d'Funktiounsinput geliwwert mdb_cursor_get d'Operatioun uginn MDB_SET_RANGE.

NodeKey upperBoundSearchKey = {​
    .parentId = 2,​
    .type = 0,​
    .nameLength = 0​
};​
MDB_val value, key = serialize(upperBoundSearchKey);​
MDB_cursor *cursor;​
mdb_cursor_open(..., &cursor);​
mdb_cursor_get(cursor, &key, &value, MDB_SET_RANGE);

Wann déi iewescht Grenz vun enger Grupp vu Schlësselen fonnt gëtt, da iteréiere mir doriwwer bis entweder mir treffen oder de Schlëssel en aneren entsprécht parentId, oder d'Schlësselen lafen guer net aus

do {​
    rc = mdb_cursor_get(cursor, &key, &value, MDB_NEXT);​
    // processing...​
} while (MDB_NOTFOUND != rc && // check end of table​
         IsTargetKey(key));    // check end of keys group​​

Wat gutt ass, ass datt als Deel vun der Iteratioun mat mdb_cursor_get mir net nëmmen de Schlëssel kréien, awer och de Wäert. Wann, fir d'Probebedéngungen ze erfëllen, musst Dir ënner anerem d'Felder aus dem Wäertdeel vum Rekord iwwerpréiwen, da si se ganz zougänglech ouni zousätzlech Gesten.

4.3. Modelléiere Relatiounen tëscht Dëscher

Bis elo hu mir et fäerdeg bruecht all Aspekter vum Design an Aarbecht mat enger Single-Table Datebank ze berücksichtegen. Mir kënne soen datt en Dësch eng Rei vun zortéierten Opzeechnungen ass, déi aus der selwechter Aart vu Schlësselwäertpaaren besteet. Wann Dir e Schlëssel als Rechteck an den assoziéierten Wäert als Parallepiped affichéiert, kritt Dir e visuellen Diagramm vun der Datebank.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Wéi och ëmmer, am richtege Liewen ass et selten méiglech mat sou wéineg Bluttvergießen duerchzekommen. Dacks an enger Datebank ass et erfuerderlech, éischtens, e puer Dëscher ze hunn, an zweetens, Selektiounen an hinnen an enger Uerdnung anescht wéi de primäre Schlëssel ze maachen. Dës lescht Sektioun ass gewidmet fir d'Problemer vun hirer Schafung an der Verbindung.

Index Dëscher

D'Wollekapplikatioun huet eng "Galerie" Sektioun. Et weist Medieninhalt aus der ganzer Wollek, no Datum zortéiert. Fir esou eng Auswiel optimal ëmzesetzen, nieft der Haapttabelle musst Dir eng aner mat enger neier Zort Schlëssel erstellen. Si enthalen e Feld mam Datum wou d'Datei erstallt gouf, wat als primär Sortéierungscritère handelt. Well déi nei Schlësselen déi selwecht Donnéeën wéi d'Schlësselen an der Haaptrei Dësch referenzéieren, si genannt Index Schlësselen. Op der Foto hei drënner si se an orange markéiert.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

Fir d'Schlëssel vu verschiddenen Dëscher vuneneen an der selwechter Datebank ze trennen, gouf en zousätzlech technescht Feld tableId un all vun hinnen bäigefüügt. Andeems Dir et déi héchst Prioritéit fir d'Sortéierung mécht, wäerte mir d'Gruppéierung vun de Schlësselen als éischt duerch Dëscher erreechen, a bannent Dëscher - no eisen eegene Reegelen.

Den Indexschlëssel bezitt sech op déiselwecht Daten wéi de primäre Schlëssel. Eng einfach Ëmsetzung vun dëser Immobilie duerch d'Associatioun mat der Kopie vum Wäertdeel vum primäre Schlëssel ass aus verschiddene Siicht net optimal:

  1. Wat de Raum ugeet, kënnen d'Metadaten zimlech räich sinn.
  2. Aus enger Leeschtungssiicht, well wann Dir d'Metadaten vun engem Node aktualiséiert, musst Dir se mat zwee Schlësselen iwwerschreiwe.
  3. Aus der Siicht vun der Code Ënnerstëtzung, wa mir vergiessen d'Donnéeën fir ee vun de Schlësselen ze aktualiséieren, kréie mir e elusive Käfer vun Dateninkonsistenz an der Späichere.

Als nächst wäerte mir kucken wéi dës Mängel eliminéiert ginn.

Organisatioun vun Relatiounen tëscht Dëscher

D'Muster ass gutt gëeegent fir den Index Dësch mat der Haaptrei Dësch ze verbannen "Schlëssel als Wäert". Wéi säin Numm et scho seet, ass de Wäertdeel vum Indexrekord eng Kopie vum primäre Schlësselwäert. Dës Approche eliminéiert all déi uewe genannte Nodeeler verbonne mat der Späichere vun enger Kopie vum Wäertdeel vum primäre Rekord. Déi eenzeg Käschte sinn datt fir e Wäert duerch Indexschlëssel ze kréien, musst Dir 2 Ufroen an d'Datebank maachen anstatt eng. Schematesch gesäit dat resultéierend Datebankschema esou aus.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

En anert Muster fir Bezéiungen tëscht Dëscher ze organiséieren ass "redundante Schlëssel". Seng Essenz ass zousätzlech Attributer un de Schlëssel ze addéieren, déi net fir d'Zortéierung gebraucht ginn, mee fir den assoziéierten Schlëssel ze kreéieren am Kontext vu spezifesche iOS Kaderen, ginn ech e fiktivt, awer awer e méi kloer Beispill

Cloud mobil Clienten hunn eng Säit déi all Dateien an Ordner weist, déi de Benotzer mat anere Leit gedeelt huet. Well et relativ wéineg esou Dateie gëtt, an et vill verschidden Aarte vu spezifeschen Informatioun iwwer d'Publizitéit mat hinnen assoziéiert (wien Zougang kritt, mat wéi enge Rechter, asw.), wäert et net rational sinn de Wäertdeel vun der Rekord an der Haaptrei Dësch mat et. Wéi och ëmmer, wann Dir esou Dateien offline wëllt weisen, musst Dir se ëmmer nach iergendwou späicheren. Eng natierlech Léisung ass en separaten Dësch dofir ze kreéieren. Am Diagramm hei drënner ass säi Schlëssel mat "P" virgeschriwwe ginn, an de Plazhalter "propname" kann duerch de méi spezifesche Wäert "ëffentlech Informatioun" ersat ginn.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

All eenzegaarteg Metadaten, fir ze späicheren, déi en neien Dësch erstallt gouf, ginn an de Wäertdeel vum Rekord gesat. Zur selwechter Zäit wëllt Dir d'Donnéeën iwwer Dateien an Ordner net duplizéieren, déi schonn an der Haapttabelle gespäichert sinn. Amplaz ginn redundante Donnéeën op de "P" Schlëssel a Form vun den "Node ID" an "Timestamp" Felder bäigefüügt. Dank hinnen kënnt Dir en Indexschlëssel konstruéieren, aus deem Dir e primäre Schlëssel kritt, aus deem Dir endlech Node Metadaten kritt.

Conclusioun

Mir bewäerten d'Resultater vun der Ëmsetzung vun LMDB positiv. Duerno ass d'Zuel vun den Uwendungsfräien ëm 30% erofgaang.

De Glanz an d'Aarmut vun der Schlësselwäerter Datebank LMDB an iOS Uwendungen

D'Resultater vun der Aarbecht hunn iwwer d'iOS Team resonéiert. De Moment ass eng vun den Haapt "Dateien" Sektiounen an der Android Applikatioun och op LMDB gewiesselt, an aner Deeler sinn um Wee. D'C Sprooch, an där de Schlësselwäertgeschäft implementéiert ass, war eng gutt Hëllef fir am Ufank en Applikatiounskader ronderëm et Cross-Plattform an C++ ze kreéieren. E Code Generator gouf benotzt fir déi resultéierend C++ Bibliothéik nahtlos mat Plattformcode an Objective-C a Kotlin ze verbannen Djinni vun Dropbox, awer dat ass eng ganz aner Geschicht.

Source: will.com

Setzt e Commentaire