Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

In die herfs van 2019 het 'n langverwagte gebeurtenis in die Mail.ru Cloud iOS-span plaasgevind. Die hoofdatabasis vir aanhoudende berging van toepassingstatus het nogal eksoties geword vir die mobiele wêreld Weerlig-geheue-gekarteer databasis (LMDB). Onder die snit word u aandag uitgenooi na die gedetailleerde resensie in vier dele. Kom ons praat eers oor die redes vir so 'n nie-triviale en moeilike keuse. Kom ons gaan dan oor na die oorweging van drie walvisse in die hart van die LMDB-argitektuur: geheue-gekarteerde lêers, B + boom, kopieer-op-skryf-benadering vir die implementering van transaksionele en multiversiering. Ten slotte, vir nagereg - die praktiese deel. Daarin sal ons kyk hoe om 'n basisskema met verskeie tabelle te ontwerp en te implementeer, insluitend 'n indeks een, bo-op die laevlak sleutelwaarde API.

inhoud

  1. Implementering Motivering
  2. Posisionering van LMDB
  3. Drie walvisse LMDB
    3.1. Walvis #1. Geheue-gekarteer lêers
    3.2. Walvis #2. B+-boom
    3.3. Walvis #3. kopieer-op-skryf
  4. Ontwerp 'n dataskema bo-op die sleutelwaarde-API
    4.1. Basiese abstraksies
    4.2. Tafelmodellering
    4.3. Modellering van verhoudings tussen tabelle

1. Implementering motivering

Een keer per jaar, in 2015, het ons gesorg om 'n maatstaf te neem, hoe dikwels die koppelvlak van ons toepassing agterbly. Ons het dit nie net gedoen nie. Ons het meer en meer klagtes oor die feit dat die toepassing soms ophou om op gebruikershandelinge te reageer: knoppies word nie gedruk nie, lyste blaai nie, ens. Oor die meganika van metings vertel op AvitoTech, so hier gee ek slegs die volgorde van getalle.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die meetresultate het vir ons 'n koue stort geword. Dit het geblyk dat die probleme wat veroorsaak word deur vries veel meer is as enige ander. As, voor die besef van hierdie feit, die belangrikste tegniese aanwyser van kwaliteit ongelukvry was, dan na die fokus verskuif op vriesvry.

Het gebou paneelbord met vriespunte en spandeer het kwantitatief и gehalte ontleding van hul oorsake, die belangrikste vyand het duidelik geword - swaar besigheid logika uitvoering in die hoof draad van die aansoek. 'n Natuurlike reaksie op hierdie skande was 'n brandende begeerte om dit in werkstrome te stoot. Vir 'n sistematiese oplossing van hierdie probleem het ons gebruik gemaak van 'n multi-draad argitektuur gebaseer op liggewig akteurs. Ek het haar aanpassings vir die iOS-wêreld opgedra twee drade in die kollektiewe twitter en artikel oor Habré. As deel van die huidige storie wil ek daardie aspekte van die besluit beklemtoon wat die keuse van die databasis beïnvloed het

Die akteursmodel van stelselorganisasie neem aan dat multithreading sy tweede essensie word. Modelvoorwerpe daarin hou daarvan om draadgrense oor te steek. En hulle doen dit nie soms en op sommige plekke nie, maar amper voortdurend en oral.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die databasis is een van die hoeksteenkomponente in die voorgestelde diagram. Sy hooftaak is om 'n makropatroon te implementeer Gedeelde databasis. As dit in die ondernemingswêreld gebruik word om datasinchronisasie tussen dienste te organiseer, dan in die geval van 'n akteur-argitektuur, data tussen drade. Ons het dus so 'n databasis nodig gehad, waarmee werk in 'n multi-threaded omgewing nie eers minimale probleme veroorsaak nie. Dit beteken veral dat die voorwerpe wat daaruit afgelei word, ten minste draadveilig moet wees, en ideaal gesproke glad nie veranderbaar nie. Soos u weet, kan laasgenoemde gelyktydig van verskeie drade gebruik word sonder om enige soort slotte te gebruik, wat 'n voordelige uitwerking op prestasie het.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassingsDie tweede belangrike faktor wat die keuse van die databasis beïnvloed het, was ons wolk API. Dit is geïnspireer deur die git-benadering tot sinchronisasie. Soos hom het ons na gemik vanlyn eerste API, wat meer as geskik lyk vir wolkkliënte. Daar is aanvaar dat hulle net een keer die volle toestand van die wolk sou uitpomp, en dan sou sinchronisasie in die oorgrote meerderheid van gevalle deur rollende veranderinge plaasvind. Helaas, hierdie moontlikheid is nog net in die teoretiese sone, en in die praktyk het kliënte nie geleer hoe om met pleisters te werk nie. Daar is 'n aantal objektiewe redes hiervoor, wat ons, om nie die bekendstelling te vertraag nie, buite die hakies sal laat. Nou baie interessanter is die leersame resultate van die les oor wat gebeur wanneer die API "A" gesê het en sy verbruiker nie "B" gesê het nie.

Dus, as u git voorstel, wat, wanneer u 'n trek-opdrag uitvoer, in plaas daarvan om pleisters op 'n plaaslike momentopname toe te pas, sy volle toestand met die volle bediener een vergelyk, dan sal u 'n redelik akkurate idee hê van hoe sinchronisasie kom in wolkkliënte voor. Dit is maklik om te raai dat dit vir die implementering daarvan nodig is om twee DOM-bome in die geheue toe te ken met meta-inligting oor alle bediener- en plaaslike lêers. Dit blyk dat as 'n gebruiker 500 duisend lêers in die wolk stoor, om dit te sinchroniseer, dit nodig is om twee bome met 1 miljoen nodusse te herskep en te vernietig. Maar elke nodus is 'n aggregaat wat 'n grafiek van subvoorwerpe bevat. In hierdie lig is die profileringsresultate verwag. Dit het geblyk dat selfs sonder om die samesmeltingsalgoritme in ag te neem, die prosedure om 'n groot aantal klein voorwerpe te skep en dan te vernietig 'n mooi sent kos. Die situasie word vererger deur die feit dat die basiese sinchronisasie-operasie by 'n groot aantal ingesluit is van gebruikersskrifte. As gevolg hiervan stel ons die tweede belangrike kriterium vas in die keuse van 'n databasis - die vermoë om CRUD-operasies te implementeer sonder dinamiese toewysing van voorwerpe.

Ander vereistes is meer tradisioneel, en hul volledige lys is soos volg.

  1. Draadveiligheid.
  2. Multiverwerking. Gedikteer deur die begeerte om dieselfde databasis-instansie te gebruik om toestand te sinchroniseer nie net tussen drade nie, maar ook tussen die hooftoepassing en iOS-uitbreidings.
  3. Die vermoë om gestoorde entiteite as nie-veranderbare voorwerpe voor te stel
  4. Gebrek aan dinamiese toekennings binne CRUD-bedrywighede.
  5. Transaksie-ondersteuning vir basiese eiendomme ACIDSleutelwoorde: atomiteit, konsekwentheid, isolasie en betroubaarheid.
  6. Spoed op die gewildste gevalle.

Met hierdie stel vereistes was en is SQLite steeds 'n goeie keuse. As deel van die studie van alternatiewe het ek egter op 'n boek afgekom "Om te begin met LevelDB". Onder haar leiding is 'n maatstaf geskryf wat die spoed van werk met verskillende databasisse in werklike wolkscenario's vergelyk. Die resultaat het die stoutste verwagtinge oortref. In die gewildste gevalle - om 'n wyser op 'n gesorteerde lys van alle lêers en 'n gesorteerde lys van alle lêers vir 'n gegewe gids te kry - LMDB blyk 10 keer vinniger te wees as SQLite. Die keuse het duidelik geword.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

2. LMDB-posisionering

LMDB is 'n biblioteek, baie klein (slegs 10K lyne), wat die laagste fundamentele laag van databasisse implementeer - berging.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die bostaande diagram toon dat die vergelyking van LMDB met SQLite, wat selfs hoër vlakke implementeer, oor die algemeen nie meer korrek is as SQLite met Core Data nie. Dit sal meer regverdig wees om dieselfde bergingsenjins as gelyke mededingers aan te haal - BerkeleyDB, LevelDB, Sophia, RocksDB, ens. Daar is selfs ontwikkelings waar LMDB as 'n bergingsenjinkomponent vir SQLite optree. Die eerste sulke eksperiment in 2012 spandeer skrywer LMDB Howard Chu. Bevindinge was so interessant dat sy inisiatief deur OSS-entoesiaste opgeraap is, en die voortsetting daarvan gevind het in die lig van LumoSQL. In Januarie 2020 is die skrywer van hierdie projek Den Shearer aangebied dit op LinuxConfAu.

Die hoofgebruik van LMDB is as 'n enjin vir toepassingsdatabasisse. Die biblioteek het sy voorkoms aan die ontwikkelaars te danke OpenLDAP, wat sterk ontevrede was met BerkeleyDB as die basis van hul projek. Stoot weg van die nederige biblioteek btree, Howard Chu kon een van die gewildste alternatiewe van ons tyd skep. Hy het sy baie koel verslag aan hierdie storie gewy, sowel as aan die interne struktuur van LMDB "Die weerlig-geheue-gekarteer databasis". Leonid Yuriev (aka yleo) van Positive Technologies in sy praatjie by Highload 2015 "Die LMDB-enjin is 'n spesiale kampioen". Daarin praat hy oor LMDB in die konteks van 'n soortgelyke taak om ReOpenLDAP te implementeer, en LevelDB het reeds vergelykende kritiek ondergaan. As gevolg van die implementering het Positive Technologies selfs 'n aktief ontwikkelende vurk gekry MDBX met baie smaaklike kenmerke, optimalisering en foutoplossings.

LMDB word ook dikwels as 'n as is-berging gebruik. Byvoorbeeld, die Mozilla Firefox-blaaier gekies het dit vir 'n aantal behoeftes, en, vanaf weergawe 9, Xcode verkies sy SQLite om indekse te stoor.

Die enjin het ook vasgevang in die wêreld van mobiele ontwikkeling. Spore van die gebruik daarvan kan wees om te vind in die iOS-kliënt vir Telegram. LinkedIn het 'n stap verder gegaan en LMDB gekies as die verstekberging vir sy tuisgemaakte datakasraamwerk, Rocket Data, waaroor vertel in 'n artikel in 2016.

LMDB veg suksesvol vir 'n plek in die son in die nis wat BerkeleyDB gelaat het ná die oorgang onder die beheer van Oracle. Die biblioteek is geliefd vir sy spoed en betroubaarheid, selfs in vergelyking met sy eie soort. Soos u weet, is daar geen gratis middagetes nie, en ek wil graag die afweging wat u in die gesig staar wanneer u tussen LMDB en SQLite kies, beklemtoon. Die diagram hierbo demonstreer duidelik hoe die verhoogde spoed bereik word. Eerstens betaal ons nie vir bykomende lae abstraksie bo-op skyfberging nie. Natuurlik, in 'n goeie argitektuur kan jy steeds nie sonder hulle klaarkom nie, en hulle sal onvermydelik in die toepassingskode verskyn, maar hulle sal baie dunner wees. Hulle sal nie kenmerke hê wat nie deur 'n spesifieke toepassing vereis word nie, byvoorbeeld ondersteuning vir navrae in die SQL-taal. Tweedens word dit moontlik om die kartering van toepassingsbewerkings op versoeke na skyfberging optimaal te implementeer. As SQLite in my werk kom uit die gemiddelde behoeftes van 'n gemiddelde toepassing, dan is jy, as 'n toepassingsontwikkelaar, deeglik bewus van die hoofladingscenario's. Vir 'n meer produktiewe oplossing sal jy 'n verhoogde prysetiket moet betaal vir beide die ontwikkeling van die aanvanklike oplossing en die daaropvolgende ondersteuning.

3. Drie walvisse LMDB

Nadat jy na die LMDB vanuit 'n voëlvlug gekyk het, is dit tyd om dieper te gaan. Die volgende drie afdelings sal gewy word aan die ontleding van die belangrikste walvisse waarop die bergingsargitektuur berus:

  1. Geheue-gekarteer lêers as 'n meganisme om met skyf te werk en interne datastrukture te sinchroniseer.
  2. B+-boom as 'n organisasie van die gestoorde datastruktuur.
  3. Kopieer-op-skryf as 'n benadering om ACID transaksionele eienskappe en multiversiering te verskaf.

3.1. Walvis #1. Geheue-gekarteer lêers

Geheue-gekarteer lêers is so 'n belangrike argitektoniese element dat hulle selfs verskyn in die naam van die bewaarplek. Kwessies van kas en sinchronisering van toegang tot gestoorde inligting is geheel en al aan die bedryfstelsel se genade. LMDB bevat geen kas in homself nie. Dit is 'n bewuste besluit deur die skrywer, aangesien die lees van data direk vanaf gekarteerde lêers jou toelaat om baie hoeke te sny in die implementering van die enjin. Hieronder is 'n ver van volledige lys van sommige van hulle.

  1. Die handhawing van die konsekwentheid van data in die stoor wanneer daar vanaf verskeie prosesse daarmee gewerk word, word die verantwoordelikheid van die bedryfstelsel. In die volgende afdeling word hierdie werktuigkundige in detail en met prente bespreek.
  2. Die afwesigheid van kas verlig LMDB heeltemal van die bokoste wat verband hou met dinamiese toekennings. Om data in die praktyk te lees, stel die wyser na die korrekte adres in virtuele geheue en niks meer nie. Klink soos fantasie, maar in die bewaarplekbron is alle calloc-oproepe in die bewaarplekkonfigurasiefunksie gekonsentreer.
  3. Die afwesigheid van kas beteken ook die afwesigheid van slotte wat verband hou met sinchronisasie om toegang daartoe te verkry. Lesers, waarvan 'n arbitrêre aantal terselfdertyd kan bestaan, kom nie 'n enkele mutex teë op pad na die data nie. As gevolg hiervan het die leesspoed 'n ideale lineêre skaalbaarheid in terme van die aantal SVE's. In LMDB word slegs wysigingsbewerkings gesinchroniseer. Daar kan net een skrywer op 'n slag wees.
  4. 'n Minimum van kas en sinchronisasie logika red die kode van 'n uiters komplekse tipe foute wat verband hou met die werk in 'n multi-draad omgewing. Daar was twee interessante databasisstudies by die Usenix OSDI 2014-konferensie: "Alle lêerstelsels word nie gelyk geskep nie: oor die kompleksiteit van die vervaardiging van ongeval-konsekwente toepassings" и Martel databasisse vir pret en wins. Van hulle kan jy inligting kry oor beide die ongekende betroubaarheid van LMDB, en die byna foutlose implementering van die ACID-eienskappe van transaksies, wat dit in dieselfde SQLite oortref.
  5. Die minimalisme van LMDB laat toe dat die masjienvoorstelling van sy kode heeltemal in die L1-kas van die verwerker geplaas word met die gevolglike spoedeienskappe.

Ongelukkig, in iOS is geheue-gekarteer lêers nie so rooskleurig as wat ons sou wou hê nie. Om meer bewustelik te praat oor die nadele wat daarmee gepaard gaan, is dit nodig om die algemene beginsels vir die implementering van hierdie meganisme in bedryfstelsels te onthou.

Algemene inligting oor geheue-gekarteer lêers

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassingsMet elke uitvoerbare toepassing assosieer die bedryfstelsel 'n entiteit wat 'n proses genoem word. Aan elke proses word 'n aaneenlopende reeks adresse toegeken waarin dit alles plaas wat nodig is om te werk. Die laagste adresse bevat afdelings met kode en hardgekodeerde data en hulpbronne. Volgende kom die opwaarts-groeiende blok van dinamiese adresruimte, aan ons bekend as die hoop. Dit bevat die adresse van entiteite wat tydens die program se werking verskyn. Aan die bokant is die area van geheue wat deur die toepassingstapel gebruik word. Dit groei of krimp, met ander woorde, sy grootte het ook 'n dinamiese aard. Sodat die stapel en hoop nie stoot en met mekaar inmeng nie, word hulle aan verskillende punte van die adresspasie geskei.Daar is 'n gat tussen die twee dinamiese afdelings bo en onder. Die adresse in hierdie middelste afdeling word deur die bedryfstelsel gebruik om met 'n proses van verskeie entiteite te assosieer. Dit kan veral 'n sekere aaneenlopende stel adresse na 'n lêer op skyf karteer. So 'n lêer word 'n geheuekaartlêer genoem

Die adresspasie wat aan 'n proses toegeken word, is groot. Teoreties word die aantal adresse slegs beperk deur die grootte van die wyser, wat bepaal word deur die bitheid van die stelsel. As fisiese geheue 1-in-1 daaraan toegewys word, sal die eerste proses die hele RAM opslurp, en daar sal geen sprake wees van enige multitasking nie.

Ons weet egter uit ondervinding dat moderne bedryfstelsels soveel prosesse kan laat loop as wat jy wil op dieselfde tyd. Dit is moontlik as gevolg van die feit dat hulle baie geheue toewys aan prosesse slegs op papier, maar in werklikheid laai hulle in die hoof fisiese geheue net daardie deel wat hier en nou in aanvraag is. Daarom word die geheue wat met die proses geassosieer word, virtueel genoem.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die bedryfstelsel organiseer virtuele en fisiese geheue in bladsye van 'n sekere grootte. Sodra 'n sekere bladsy van virtuele geheue in aanvraag is, laai die bedryfstelsel dit in fisiese geheue en plaas 'n korrespondensie tussen hulle in 'n spesiale tabel. As daar geen vrye gleuwe is nie, word een van die voorheen gelaaide bladsye na skyf gekopieer, en die versoekte neem die plek in. Hierdie prosedure, waarna ons binnekort sal terugkeer, word omruil genoem. Die figuur hieronder illustreer die beskryfde proses. Daarop is bladsy A met adres 0 gelaai en op die hoofgeheuebladsy met adres 4 geplaas. Hierdie feit is in die korrespondensietabel in selnommer 0 weerspieël.​

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Met geheue-gekarteer lêers is die storie presies dieselfde. Logies word hulle kwansuis voortdurend en heeltemal in die virtuele adresruimte geplaas. Hulle kom egter bladsy vir bladsy in fisiese geheue in en slegs op aanvraag. Wysiging van sulke bladsye word gesinchroniseer met die lêer op skyf. U kan dus lêer I / O uitvoer deur eenvoudig met grepe in die geheue te werk - alle veranderinge sal outomaties deur die bedryfstelselkern na die oorspronklike lêer oorgedra word.
â € <
Die prent hieronder demonstreer hoe LMDB sy toestand sinchroniseer wanneer daar vanaf verskillende prosesse met die databasis gewerk word. Deur die virtuele geheue van verskillende prosesse op dieselfde lêer te karteer, verplig ons de facto die bedryfstelsel om sekere blokke van hul adresruimtes oorganklik met mekaar te sinchroniseer, dit is waar LMDB lyk.
â € <

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

'n Belangrike nuanse is dat LMDB die datalêer by verstek wysig deur die skryfstelseloproepmeganisme, en die lêer self vertoon in leesalleenmodus. Hierdie benadering het twee belangrike implikasies.

Die eerste gevolg is algemeen vir alle bedryfstelsels. Die essensie daarvan is om beskerming teen onopsetlike skade aan die databasis deur verkeerde kode by te voeg. Soos u weet, is die uitvoerbare instruksies van 'n proses vry om toegang tot data vanaf enige plek in sy adresruimte te verkry. Terselfdertyd, soos ons pas onthou het, beteken die vertoon van 'n lêer in lees-skryf-modus dat enige instruksie dit ook kan verander. As sy dit per ongeluk doen en byvoorbeeld probeer om 'n skikkingselement by 'n nie-bestaande indeks te oorskryf, kan sy op hierdie manier per ongeluk die lêer verander wat na hierdie adres gekarteer is, wat tot databasiskorrupsie sal lei. As die lêer in leesalleen-modus vertoon word, sal 'n poging om die adresspasie wat daarmee ooreenstem, lei tot die programongeluk met die sein SIGSEGV, en die lêer sal ongeskonde bly.

Die tweede gevolg is reeds spesifiek vir iOS. Nie die skrywer of enige ander bronne noem dit uitdruklik nie, maar daarsonder sal LMDB ongeskik wees om op hierdie mobiele bedryfstelsel te werk. Die volgende afdeling word aan die oorweging daarvan gewy.

Besonderhede van geheue-gekarteer lêers in iOS

In 2018 was daar 'n wonderlike verslag by WWDC iOS Memory Deep Dive. Dit vertel dat in iOS alle bladsye wat in fisiese geheue geleë is, aan een van 3 tipes behoort: vuil, saamgeperste en skoon.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Skoon geheue is 'n versameling bladsye wat veilig uit fisiese geheue geruil kan word. Die data wat hulle bevat kan herlaai word vanaf hul oorspronklike bronne soos nodig. Leesalleen-geheue-gekarteer lêers val in hierdie kategorie. iOS is nie bang om die bladsye wat na 'n lêer gekarteer is te eniger tyd uit die geheue af te laai nie, aangesien dit gewaarborg is om gesinchroniseer te word met die lêer op skyf.
â € <
Alle gewysigde bladsye kom in die vuil geheue, maak nie saak waar hulle oorspronklik geleë is nie. Veral geheue-gekarteer lêers wat gewysig is deur te skryf na die virtuele geheue wat daarmee geassosieer word, sal ook op hierdie manier geklassifiseer word. Opening LMDB met vlag MDB_WRITEMAP, nadat jy veranderinge daaraan gemaak het, kan jy self sien.​

​Sodra 'n toepassing te veel fisiese geheue begin opneem, komprimeer iOS sy vuil bladsye. Die versameling geheue wat deur vuil en saamgeperste bladsye beset word, is die sogenaamde geheue-voetspoor van die toepassing. Wanneer dit 'n sekere drempelwaarde bereik, kom die OOM-moordenaarstelsel daemon na die proses en beëindig dit met geweld. Dit is die eienaardigheid van iOS in vergelyking met rekenaarbedryfstelsels. Daarteenoor word nie in iOS voorsien om die geheuevoetspoor te verlaag deur bladsye van fisiese geheue na skyf te ruil nie. Mens kan net oor die redes raai. Miskien is die prosedure om bladsye intensief na skyf en terug te skuif te energieverbruik vir mobiele toestelle, of iOS spaar die hulpbron om selle op SSD-aandrywers te herskryf, of miskien was die ontwerpers nie tevrede met die algehele werkverrigting van die stelsel nie, waar alles is voortdurend omgeruil. Hoe dit ook al sy, die feit bly staan.

Die goeie nuus, wat reeds vroeër genoem is, is dat LMDB nie die mmap-meganisme by verstek gebruik om lêers op te dateer nie. Dit volg dat die gelewerde data deur iOS as skoon geheue geklassifiseer word en nie bydra tot die geheue-voetspoor nie. Dit kan geverifieer word met behulp van die Xcode-instrument genaamd VM Tracker. Die skermkiekie hieronder toon die virtuele geheue-toestand van die iOS Wolk-toepassing tydens werking. Aan die begin is 2 LMDB-gevalle daarin geïnisialiseer. Die eerste is toegelaat om sy lêer na 1GiB virtuele geheue te karteer, die tweede - 512MiB. Ten spyte van die feit dat beide bergings 'n sekere hoeveelheid inwonende geheue beslaan, dra nie een van hulle by tot die vuil grootte nie.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Nou is dit tyd vir die slegte nuus. Danksy die ruilmeganisme in 64-bis-werkskermbedryfstelsels kan elke proses soveel virtuele adresspasie in beslag neem as wat die vrye spasie op die hardeskyf moontlik maak vir die moontlike omruiling daarvan. Die vervanging van ruil met kompressie in iOS verminder die teoretiese maksimum drasties. Nou moet alle lewende prosesse in die hoof (lees RAM) geheue pas, en almal wat nie pas nie, is onderhewig aan gedwonge beëindiging. Dit word genoem soos in bogenoemde verslag, en in amptelike dokumentasie. As gevolg hiervan beperk iOS die hoeveelheid geheue wat beskikbaar is vir toewysing via mmap, ernstig. Hier hier jy kan kyk na die empiriese limiete op die hoeveelheid geheue wat aan verskillende toestelle toegeken kan word deur hierdie stelseloproep te gebruik. Op die mees moderne modelle van slimfone het iOS vrygewig geword met 2 gigagrepe, en op top weergawes van die iPad - met 4. In die praktyk moet jy natuurlik fokus op die jongste ondersteunde toestelmodelle, waar alles baie hartseer is. Nog erger, as u na die toepassing se geheuetoestand in VM Tracker kyk, sal u vind dat LMDB ver van die enigste een is wat aanspraak maak op geheue-gekarteerde geheue. Goeie stukke word weggevreet deur stelseltoewysers, hulpbronlêers, beeldraamwerke en ander kleiner roofdiere.

Gebaseer op die resultate van eksperimente in die Wolk, het ons by die volgende kompromiewaardes van geheue gekom wat deur LMDB toegeken is: 384 megagrepe vir 32-bis-toestelle en 768 vir 64-bis-toestelle. Nadat hierdie volume opgebruik is, begin enige wysigingsbewerkings met die kode voltooi word MDB_MAP_FULL. Ons neem sulke foute in ons monitering waar, maar hulle is klein genoeg om in hierdie stadium afgeskeep te word.

'n Nie-vanselfsprekende rede vir oormatige geheueverbruik deur berging kan langlewende transaksies wees. Om te verstaan ​​hoe hierdie twee verskynsels verband hou, sal dit ons help om die oorblywende twee LMDB-walvisse te oorweeg.

3.2. Walvis #2. B+-boom

Om tabelle bo-op 'n sleutelwaarde-stoor na te boots, moet die volgende bewerkings in sy API teenwoordig wees:

  1. Voeg 'n nuwe element in.
  2. Soek vir 'n element met 'n gegewe sleutel.
  3. Vee 'n element uit.
  4. Herhaal sleutelintervalle in hul sorteervolgorde.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassingsDie eenvoudigste datastruktuur wat al vier bewerkings maklik kan implementeer, is 'n binêre soekboom. Elkeen van sy nodusse is 'n sleutel wat die hele subset van kindsleutels in twee subbome verdeel. Aan die linkerkant is dié wat kleiner as die ouer is, en aan die regterkant - dié wat groter is. Die verkryging van 'n geordende stel sleutels word bereik deur een van die klassieke boomkruisings

Binêre bome het twee fundamentele nadele wat verhoed dat hulle effektief is as 'n skyfdatastruktuur. Eerstens is die mate van hul balans onvoorspelbaar. Daar is 'n aansienlike risiko om bome te verkry waarin die hoogte van verskillende takke baie kan verskil, wat die algoritmiese kompleksiteit van die soektog aansienlik vererger in vergelyking met wat verwag word. Tweedens, die oorvloed van kruisskakels tussen nodusse ontneem binêre bome van lokaliteit in die geheue.Naby nodusse (in terme van skakels tussen hulle) kan op heeltemal verskillende bladsye in virtuele geheue geleë wees. As gevolg hiervan kan selfs 'n eenvoudige deurkruising van verskeie naburige nodusse in 'n boom 'n besoek van 'n vergelykbare aantal bladsye vereis. Dit is 'n probleem, selfs wanneer ons praat oor die doeltreffendheid van binêre bome as 'n datastruktuur in die geheue, aangesien voortdurend roterende bladsye in die verwerkerkas nie goedkoop is nie. As dit kom by die gereelde verhoging van nodusverwante bladsye vanaf skyf, raak dinge regtig sleg. betreurenswaardig.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassingsB-bome, synde 'n evolusie van binêre bome, los die probleme op wat in die vorige paragraaf geïdentifiseer is. Eerstens is hulle selfbalanserend. Tweedens verdeel elkeen van hul nodusse die stel kindsleutels nie in 2 nie, maar in M-geordende subversamelings, en die getal M kan redelik groot wees, in die orde van 'n paar honderd of selfs duisende.

Daardeur:

  1. Elke nodus het 'n groot aantal reeds bestelde sleutels en die bome is baie laag.
  2. Die boom verkry die eiendom van ligging in die geheue, aangesien sleutels wat naby in waarde is, natuurlik langs mekaar op een of naburige nodusse geleë is.
  3. Verminder die aantal transito nodusse wanneer die boom afklim tydens 'n soekoperasie.
  4. Verminder die aantal teikennodusse wat vir reeksnavrae gelees word, aangesien elkeen reeds 'n groot aantal geordende sleutels bevat.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

LMDB gebruik 'n variant van die B-boom genaamd die B+ boom om data te stoor. Die diagram hierbo toon die drie tipes nodusse wat dit bevat:

  1. Aan die bokant is die wortel. Dit realiseer niks meer as die konsep van 'n databasis binne 'n bewaarplek nie. Binne 'n enkele LMDB-instansie kan u veelvuldige databasisse skep wat die gekarteerde virtuele adresruimte deel. Elkeen van hulle begin by sy eie wortel.
  2. Op die laagste vlak is die blare (blaar). Dit is hulle en slegs hulle wat die sleutel-waarde-pare bevat wat in die databasis gestoor is. Terloops, dit is die eienaardigheid van B+-bome. As 'n normale B-boom die waarde-dele by die nodusse van alle vlakke stoor, dan is die B+-variasie slegs op die laagste een. Nadat ons hierdie feit vasgestel het, sal ons in wat volg die subtipe van die boom wat in LMDB gebruik word bloot 'n B-boom noem.
  3. Tussen die wortel en die blare is daar 0 of meer tegniese vlakke met navigasie (tak) nodusse. Hulle taak is om die gesorteerde stel sleutels tussen die blare te verdeel.

Fisies is nodusse blokke geheue van 'n voorafbepaalde lengte. Hul grootte is 'n veelvoud van die grootte van die geheuebladsye in die bedryfstelsel, waaroor ons hierbo gepraat het. Die nodusstruktuur word hieronder getoon. Die kop bevat meta-inligting, waarvan die mees voor die hand liggend, byvoorbeeld, die kontrolesom is. Vervolgens kom inligting oor afwykings, waarlangs selle met data geleë is. Die rol van data kan óf sleutels wees, as ons van navigasienodusse praat, óf hele sleutel-waarde-pare in die geval van blare. Jy kan meer lees oor die struktuur van bladsye in die werk "Evaluering van hoëprestasie sleutelwaardewinkels".

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Nadat ons die interne inhoud van die bladsynodusse behandel het, sal ons die LMDB B-boom verder op 'n vereenvoudigde wyse in die volgende vorm voorstel.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Bladsye met nodusse word opeenvolgend op skyf gerangskik. Bladsye met 'n hoër nommer is aan die einde van die lêer geleë. Die sogenaamde metabladsy (metabladsy) bevat inligting oor afwykings, wat gebruik kan word om die wortels van alle bome te vind. Wanneer 'n lêer oopgemaak word, skandeer LMDB die lêer bladsy vir bladsy van die einde tot die begin op soek na 'n geldige metabladsy en vind bestaande databasisse daardeur.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Nou, met 'n idee van die logiese en fisiese struktuur van data-organisasie, kan ons voortgaan om die derde walvis van LMDB te oorweeg. Dit is met sy hulp dat alle stoorwysigings transaksioneel en in isolasie van mekaar plaasvind, wat die databasis as geheel ook die multiversie-eienskap gee.

3.3. Walvis #3. kopieer-op-skryf

Sommige B-boom-bewerkings behels die maak van 'n hele reeks veranderinge aan sy nodusse. Een voorbeeld is die toevoeging van 'n nuwe sleutel tot 'n nodus wat reeds sy maksimum kapasiteit bereik het. In hierdie geval is dit nodig, eerstens, om die nodus in twee te verdeel, en tweedens, om 'n skakel by die nuwe afgesonne kindernodus in sy ouer te voeg. Hierdie prosedure is potensieel baie gevaarlik. As om een ​​of ander rede (ongeluk, kragonderbreking, ens.) slegs 'n deel van die veranderinge van die reeks plaasvind, sal die boom in 'n inkonsekwente toestand bly.

Een tradisionele oplossing om 'n databasis foutverdraagsaam te maak, is om 'n bykomende skyf-gebaseerde datastruktuur, die transaksielog, ook bekend as die skryf-vooruit-log (WAL), langs die B-boom by te voeg. Dit is 'n lêer, aan die einde waarvan, streng voor die wysiging van die B-boom self, die beoogde bewerking geskryf word. Dus, as datakorrupsie tydens selfdiagnose opgespoor word, raadpleeg die databasis die logboek om homself skoon te maak.

LMDB het 'n ander metode gekies as sy fouttoleransiemeganisme, wat kopieer-op-skryf genoem word. Die essensie daarvan is dat in plaas daarvan om die data op 'n bestaande bladsy op te dateer, dit eers heeltemal kopieer en alle wysigings wat reeds in die kopie is, aanbring.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Verder, ten einde die opgedateerde data beskikbaar te wees, is dit nodig om die skakel na die nodus te verander wat op datum geraak het in die ouer nodus met betrekking tot dit. Aangesien dit ook hiervoor aangepas moet word, word dit ook vooraf gekopieer. Die proses gaan rekursief voort tot by die wortel. Die data op die metabladsy is die laaste om te verander.​

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

As die proses skielik ineenstort tydens die opdateringsprosedure, sal óf 'n nuwe metabladsy nie geskep word nie, óf dit sal nie tot aan die einde na skyf geskryf word nie, en die kontrolesom daarvan sal verkeerd wees. In een van hierdie twee gevalle sal die nuwe bladsye onbereikbaar wees en die oues sal nie geraak word nie. Dit elimineer die behoefte vir LMDB om vooruitlogboek te skryf om datakonsekwentheid te handhaaf. De facto neem die struktuur van databerging op die skyf, soos hierbo beskryf, terselfdertyd sy funksie aan. Die afwesigheid van 'n eksplisiete transaksielogboek is een van die kenmerke van LMDB, wat hoë data-leesspoed bied.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die gevolglike konstruk, genoem slegs-byvoeg-B-boom, bied natuurlik transaksie-isolasie en multiversiering. In LMDB het elke oop transaksie 'n bygewerkte boomwortel wat daarmee geassosieer word. Solank as wat die transaksie nie voltooi is nie, sal die bladsye van die boom wat daarmee geassosieer word nooit verander of hergebruik word vir nuwe weergawes van data nie. Jy kan dus so lank werk as wat jy wil presies met die datastel wat relevant was by die tyd wat die transaksie oopgemaak is, selfs al word die berging steeds aktief opgedateer op hierdie tydstip. Dit is die kern van multiversioning, wat LMDB 'n ideale databron vir ons geliefde maak UICollectionView. Nadat u 'n transaksie oopgemaak het, hoef u nie die geheue-voetspoor van die toepassing te vergroot nie, u moet die huidige data haastig in die een of ander in-geheue-struktuur uitpomp, omdat u bang is om met niks gelaat te word nie. Hierdie kenmerk onderskei LMDB van dieselfde SQLite, wat nie met so 'n totale isolasie kan spog nie. Nadat twee transaksies in laasgenoemde oopgemaak is en 'n sekere rekord binne een daarvan uitgevee is, kan dieselfde rekord nie meer binne die tweede oorblywende een verkry word nie.

Die keersy van die muntstuk is die potensieel aansienlik hoër verbruik van virtuele geheue. Die skyfie wys hoe die databasisstruktuur sal lyk as dit gelyktydig gewysig word met 3 oopgeleestransaksies wat na verskillende weergawes van die databasis kyk. Aangesien LMDB nie nodusse kan hergebruik wat bereikbaar is vanaf die wortels wat met die werklike transaksies geassosieer word nie, het die stoor geen ander keuse as om nog 'n vierde wortel in die geheue toe te ken en weer die gewysigde bladsye daaronder te kloon nie.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Hier sal dit nie oorbodig wees om die afdeling oor geheue-gekarteer lêers te herroep nie. Dit blyk dat die bykomende verbruik van virtuele geheue ons nie veel behoort te pla nie, aangesien dit nie bydra tot die toepassing se geheuevoetspoor nie. Daar is egter terselfdertyd opgemerk dat iOS baie suinig is om dit toe te ken, en ons kan nie 'n 1 teragreep LMDB-streek op 'n bediener of lessenaar vanaf die meester se skouer verskaf en glad nie aan hierdie kenmerk dink nie. Wanneer moontlik, moet jy probeer om die leeftyd van transaksies so kort as moontlik te hou.

4. Ontwerp 'n dataskema bo-op die sleutelwaarde-API

Kom ons begin die API ontleed deur te kyk na die basiese abstraksies wat deur LMDB verskaf word: omgewing en databasisse, sleutels en waardes, transaksies en wysers.

'n Nota oor kode-inskrywings

Alle funksies in die LMDB publieke API gee die resultaat van hul werk in die vorm van 'n foutkode terug, maar in alle daaropvolgende lyste word die kontrole daarvan weggelaat ter wille van bondigheid.In die praktyk het ons ons eie kode gebruik om met die bewaarplek te kommunikeer. vurk C++ omhulsels lmdbxx, waarin foute realiseer as C++-uitsonderings.

As die vinnigste manier om LMDB aan 'n iOS- of macOS-projek te koppel, bied ek my CocoaPod aan POSLMDB.

4.1. Basiese abstraksies

Omgewing

Struktuur MDB_env is die bewaarplek van die interne toestand van die LMDB. Familie van voorvoegselfunksies mdb_env laat jou toe om sommige van sy eienskappe op te stel. In die eenvoudigste geval lyk die inisialisering van die enjin so.

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

In die Mail.ru Cloud-toepassing het ons die verstekwaardes vir slegs twee parameters verander.

Die eerste een is die grootte van die virtuele adresspasie waarna die stoorlêer gekarteer is. Ongelukkig, selfs op dieselfde toestel, kan die spesifieke waarde aansienlik verskil van lopie tot lopie. Om hierdie kenmerk van iOS in ag te neem, kies ons die maksimum hoeveelheid berging dinamies. Begin van 'n sekere waarde, halveer dit agtereenvolgens tot die funksie mdb_env_open sal nie 'n resultaat anders as ENOMEM. In teorie is daar 'n teenoorgestelde manier - ken eers 'n minimum geheue aan die enjin toe, en dan, wanneer foute ontvang word MDB_MAP_FULL, verhoog dit. Dit is egter baie meer netelig. Die rede is dat die prosedure vir die herkartering van geheue met behulp van die funksie mdb_env_set_map_size maak alle entiteite (wysers, transaksies, sleutels en waardes) wat vroeër van die enjin ontvang is, ongeldig. Verantwoording van so 'n wending in die kode sal lei tot die beduidende komplikasie daarvan. As virtuele geheue nogtans baie dierbaar is vir jou, dan is dit dalk 'n rede om te kyk na die vurk wat ver vooruit gegaan het. MDBX, waar onder die verklaarde kenmerke daar "outomatiese on-the-fly databasis grootte aanpassing" is.

Die tweede parameter, waarvan die verstekwaarde ons nie gepas het nie, reguleer die meganika om draadveiligheid te verseker. Ongelukkig is daar ten minste in iOS 10 probleme met plaaslike stoorondersteuning. Om hierdie rede, in die voorbeeld hierbo, word die bewaarplek met die vlag oopgemaak MDB_NOTLS. Daarbenewens het dit ook vereis vurk C++ omhulsel lmdbxxveranderlikes met en in hierdie eienskap te sny.

databasisse

Die databasis is 'n aparte instansie van die B-boom waaroor ons hierbo gepraat het. Die opening daarvan vind plaas binne 'n transaksie, wat aanvanklik 'n bietjie vreemd mag lyk.

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);

Inderdaad, 'n transaksie in LMDB is 'n bergingsentiteit, nie 'n spesifieke databasis nie. Hierdie konsep laat jou toe om atoombewerkings uit te voer op entiteite wat in verskillende databasisse geleë is. In teorie maak dit die moontlikheid oop om tabelle in die vorm van verskillende databasisse te modelleer, maar ek het eenkeer anderpad gegaan, wat hieronder in detail beskryf word.

Sleutels en waardes

Struktuur MDB_val modelleer die konsep van beide 'n sleutel en 'n waarde. Die bewaarplek het geen idee oor hul semantiek nie. Vir haar is iets wat anders is, net 'n reeks grepe van 'n gegewe grootte. Die maksimum sleutelgrootte is 512 grepe.

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

Die winkel gebruik 'n vergelyker om die sleutels in stygende volgorde te sorteer. As jy dit nie met jou eie vervang nie, sal die verstek een gebruik word, wat hulle greep-vir-greep in leksikografiese volgorde sorteer.​

Transaksies

Die transaksie-toestel word in detail beskryf in vorige hoofstuk, so hier sal ek hul hoofeienskappe in 'n kort reël herhaal:

  1. Ondersteuning vir alle basiese eiendomme ACIDSleutelwoorde: atomiteit, konsekwentheid, isolasie en betroubaarheid. Ek kan nie anders as om daarop te let dat in terme van duursaamheid op macOS en iOS daar 'n fout in MDBX opgelos is nie. Jy kan meer lees in hulle README.
  2. Die benadering tot multithreading word beskryf deur die "enkelskrywer / veelvuldige lesers"-skema. Skrywers blokkeer mekaar, maar hulle blokkeer nie lesers nie. Lesers blokkeer nie skrywers of mekaar nie.
  3. Ondersteuning vir geneste transaksies.
  4. Multiversion ondersteuning.

Multiversioning in LMDB is so goed dat ek dit in aksie wil demonstreer. Die kode hieronder wys dat elke transaksie werk met presies die weergawe van die databasis wat relevant was ten tyde van die opening daarvan, en is heeltemal geïsoleer van alle daaropvolgende veranderinge. Om die bewaarplek te inisialiseer en 'n toetsrekord daarby te voeg, is van geen belang nie, so hierdie rituele word onder die bederf gelaat.

Voeg 'n toetsinskrywing by

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);

Opsioneel, ek beveel aan om dieselfde truuk met SQLite te probeer en kyk wat gebeur.

Multiversioning bring baie goeie voordele vir die lewe van 'n iOS-ontwikkelaar. Deur hierdie eiendom te gebruik, kan jy maklik en natuurlik die databronopdateringskoers vir skermvorms aanpas op grond van gebruikerservaring-oorwegings. Kom ons neem byvoorbeeld so 'n kenmerk van die Mail.ru Cloud-toepassing as die outolaai van inhoud vanaf die stelselmediagalery. Met 'n goeie verbinding kan die kliënt verskeie foto's per sekonde by die bediener voeg. As jy na elke aflaai opdateer UICollectionView met media-inhoud in die gebruiker se wolk, kan jy vergeet van 60 fps en gladde blaai tydens hierdie proses. Om gereelde skermopdaterings te voorkom, moet jy op een of ander manier die tempo van dataverandering in die basis beperk UICollectionViewDataSource.

As die databasis nie multiweergawe ondersteun nie en jou toelaat om slegs met die huidige huidige toestand te werk, moet jy dit óf na 'n datastruktuur in die geheue óf na 'n tydelike tabel kopieer om 'n tydstabiele data-momentopname te skep. Enige van hierdie benaderings is baie duur. In die geval van in-geheue berging, kry ons beide die geheue koste wat veroorsaak word deur die stoor van gekonstrueerde voorwerpe en die tyd koste wat verband hou met oortollige ORM transformasies. Wat die tydelike tafel betref, is dit 'n selfs duurder plesier, wat slegs in nie-onbeduidende gevalle sin maak.

Multiversioning LMDB los die probleem op om 'n stabiele databron op 'n baie elegante manier te handhaaf. Dit is genoeg om net 'n transaksie oop te maak en voila - totdat ons dit voltooi, is die datastel gewaarborg om reg te wees. Die logika van sy opdateringstempo is nou geheel en al in die hande van die aanbiedingslaag, met geen bokoste van beduidende hulpbronne nie.

Wysers

Wysers verskaf 'n meganisme vir ordelike iterasie oor sleutel-waarde-pare deur 'n B-boom te kruis. Sonder hulle sou dit onmoontlik wees om die tabelle in die databasis, waarna ons nou wend, effektief te modelleer.

4.2. Tafelmodellering

Die sleutelbestel-eienskap laat jou toe om so 'n hoë vlak soos 'n tabel bo-op basiese abstraksies te bou. Kom ons kyk na hierdie proses op die voorbeeld van die hooftabel van die wolkkliënt, waarin inligting oor alle lêers en gidse van die gebruiker in die kas geberg is.

Tabelskema

Een van die algemene scenario's waarvoor die struktuur van 'n tabel met 'n vouerboom opgeskerp moet word, is om alle elemente wat binne 'n gegewe gids geleë is te kies. 'n Goeie data-organisasiemodel vir doeltreffende navrae van hierdie soort is Aangrensende lys. Om dit bo-op die sleutelwaardeberging te implementeer, is dit nodig om die sleutels van lêers en vouers op so 'n manier te sorteer dat hulle gegroepeer word op grond van die feit dat dit aan die ouergids behoort. Daarbenewens, om die inhoud van die gids te vertoon in die vorm wat bekend is vir 'n Windows-gebruiker (vouers eerste, dan lêers, albei word alfabeties gesorteer), is dit nodig om die ooreenstemmende bykomende velde in die sleutel in te sluit.

​Die prent hieronder wys hoe, gebaseer op die taak, die voorstelling van sleutels as 'n verskeidenheid grepe kan lyk. Eerstens word die grepe met die ouergids-identifiseerder (rooi) geplaas, dan met die tipe (groen), en reeds in die stert met die naam (blou). Word gesorteer volgens die verstek LMDB-vergelyker in leksikografiese volgorde, word hulle georden in die vereiste manier. Opeenvolgende deurkruising van sleutels met dieselfde rooi voorvoegsel gee ons die waardes wat daarmee geassosieer word in die volgorde waarin hulle in die gebruikerskoppelvlak (regs) vertoon moet word, sonder om enige bykomende naverwerking te vereis.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Serialisering van sleutels en waardes

Daar is baie metodes om voorwerpe regoor die wêreld te serialiseer. Aangesien ons geen ander vereiste as spoed gehad het nie, het ons die vinnigste moontlike een vir onsself gekies - 'n geheuestorting wat deur 'n instansie van die C-taalstruktuur beset word. Dus, die sleutel van 'n gidselement kan deur die volgende struktuur gemodelleer word NodeKey.

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

Om te spaar NodeKey in stoorbehoefte in voorwerp MDB_val plaas die wyser na die data by die adres van die begin van die struktuur, en bereken hul grootte met die funksie sizeof.

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

In die eerste hoofstuk oor databasisseleksiekriteria het ek die minimalisering van dinamiese toekennings as deel van CRUD-bedrywighede as 'n belangrike seleksiefaktor genoem. Funksie kode serialize wys hoe, in die geval van LMDB, hulle heeltemal vermy kan word wanneer nuwe rekords in die databasis ingevoeg word. Die inkomende reeks grepe vanaf die bediener word eers in stapelstrukture omskep, en dan word hulle onbenullig in die berging gestort. Aangesien daar ook geen dinamiese toekennings binne LMDB is nie, kan jy 'n fantastiese situasie kry volgens die standaarde van iOS - gebruik slegs stapelgeheue om met data te werk van die netwerk tot op die skyf!

Bestel sleutels met 'n binêre vergelyker

Die sleutelordeverhouding word gegee deur 'n spesiale funksie wat 'n vergelyker genoem word. Aangesien die enjin niks weet van die semantiek van die grepe wat hulle bevat nie, het die verstekvergelyker geen ander keuse as om die sleutels in leksikografiese volgorde te rangskik, met hul greep-vir-greep-vergelyking. Om dit te gebruik om strukture te rangskik is soortgelyk aan skeer met 'n kerfbyl. In eenvoudige gevalle vind ek hierdie metode egter aanvaarbaar. Die alternatief word hieronder beskryf, maar hier sal ek let op 'n paar harke wat langs die pad versprei is.

Die eerste ding om in gedagte te hou is die geheuevoorstelling van primitiewe datatipes. Dus, op alle Apple-toestelle word heelgetalveranderlikes in die formaat gestoor Klein Endian. Dit beteken dat die minste betekenisvolle greep aan die linkerkant sal wees, en jy sal nie heelgetalle kan sorteer deur hul greep-vir-greep-vergelyking te gebruik nie. As u dit byvoorbeeld probeer doen met 'n stel getalle van 0 tot 511, sal die volgende resultaat tot gevolg hê.

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

Om hierdie probleem op te los, moet die heelgetalle in die sleutel gestoor word in 'n formaat wat geskik is vir die greepvergelyker. Funksies van die gesin sal help om die nodige transformasie uit te voer. hton* (in die besonder htons vir dubbelgreepnommers uit die voorbeeld).

Die formaat vir die voorstelling van snare in programmering is, soos jy weet, 'n geheel история. As die semantiek van snare, sowel as die enkodering wat gebruik word om dit in die geheue voor te stel, daarop dui dat daar meer as een greep per karakter kan wees, is dit beter om dadelik die idee van die gebruik van 'n standaardvergelyker te laat vaar.

Die tweede ding om in gedagte te hou is belyningsbeginsels struct veld samesteller. As gevolg van hulle kan grepe met vulliswaardes in die geheue tussen velde gevorm word, wat natuurlik grepe-sortering verbreek. Om vullis uit te skakel, moet jy óf die velde in 'n streng gedefinieerde volgorde verklaar, met die belyningsreëls in gedagte, óf die kenmerk in die struktuurverklaring gebruik packed.

Sleutelbestelling deur 'n eksterne vergelyker

Die sleutelvergelykingslogika kan te kompleks blyk te wees vir 'n binêre vergelyker. Een van die vele redes is die teenwoordigheid van tegniese velde binne die strukture. Ek sal hul voorkoms illustreer op die voorbeeld van 'n sleutel wat reeds aan ons bekend is vir 'n gidselement.

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

Ten spyte van al sy eenvoud, verbruik dit in die oorgrote meerderheid van gevalle te veel geheue. Die titelbuffer is 256 grepe, hoewel lêer- en vouername gemiddeld selde 20-30 karakters oorskry.

Een van die standaardtegnieke om die grootte van 'n rekord te optimaliseer, is om dit af te sny om by sy werklike grootte te pas. Die essensie daarvan is dat die inhoud van alle veranderlike-lengte-velde in 'n buffer aan die einde van die struktuur gestoor word, en hul lengtes word in aparte veranderlikes gestoor. In ooreenstemming met hierdie benadering is die sleutel NodeKey word op die volgende manier getransformeer.

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

Verder, tydens serialisering, nie gespesifiseer as die datagrootte nie sizeof die hele struktuur, en die grootte van alle velde is vaste lengte plus die grootte van die werklik gebruikte deel van die buffer.

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

As gevolg van die herfaktorering het ons 'n aansienlike besparing gekry in die spasie wat deur die sleutels beset word. Maar as gevolg van die tegniese veld nameLength, Die verstek binêre vergelyker is nie meer geskik vir sleutelvergelyking nie. As ons dit nie met ons eie vervang nie, sal die lengte van die naam 'n meer prioriteitsfaktor in sortering wees as die naam self.

LMDB laat elke databasis toe om sy eie sleutelvergelykingsfunksie te hê. Dit word gedoen met behulp van die funksie mdb_set_compare streng voor oopmaak. Om ooglopende redes kan 'n databasis nie gedurende sy leeftyd verander word nie. By die invoer ontvang die vergelyker twee sleutels in binêre formaat, en by die uitvoer gee dit die resultaat van die vergelyking terug: minder as (-1), groter as (1) of gelyk aan (0). Pseudokode vir NodeKey lyk so.

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 // ...
}​

Solank as wat alle sleutels in die databasis van dieselfde tipe is, is dit wettig om hul greepvoorstelling onvoorwaardelik na die tipe van die sleutel se toepassingstruktuur te gooi. Daar is een nuanse hier, maar dit sal 'n bietjie laer in die "Leesrekords"-onderafdeling bespreek word.

Waarde serialisering

Met die sleutels van gestoorde rekords werk LMDB uiters intensief. Hulle word met mekaar vergelyk binne die raamwerk van enige toepassingsoperasie, en die werkverrigting van die hele oplossing hang af van die spoed van die vergelyker. In 'n ideale wêreld behoort die verstek binêre vergelyker genoeg te wees om sleutels te vergelyk, maar as jy regtig jou eie moes gebruik, dan moet die prosedure vir die deserialisering van sleutels daarin so vinnig as moontlik wees.

Die databasis stel nie veral belang in die Waarde-deel van die rekord (waarde) nie. Die omskakeling daarvan van 'n greepvoorstelling na 'n voorwerp vind slegs plaas wanneer dit reeds deur die toepassingskode vereis word, byvoorbeeld om dit op die skerm te vertoon. Aangesien dit relatief selde gebeur, is die vereistes vir die spoed van hierdie prosedure nie so krities nie, en in die implementering daarvan is ons baie meer vry om op gerief te fokus. Om byvoorbeeld metadata oor lêers wat nog nie afgelaai is te serialiseer nie, gebruik ons NSKeyedArchiver.

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

Daar is egter tye wanneer prestasie wel saak maak. Byvoorbeeld, wanneer ons meta-inligting oor die lêerstruktuur van die gebruikerswolk stoor, gebruik ons ​​dieselfde objekgeheuestorting. Die hoogtepunt van die taak om hul reeksvoorstelling te genereer, is die feit dat die elemente van 'n gids deur 'n klashiërargie gemodelleer word.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Vir die implementering daarvan in die C-taal word die spesifieke velde van die erfgename in aparte strukture uitgeneem, en hul verband met die basis een word gespesifiseer deur 'n veld van die vakbondtipe. Die werklike inhoud van die unie word gespesifiseer via die tipe tegniese kenmerk.

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

Byvoeging en opdatering van rekords

Die reekssleutel en waarde kan by die winkel gevoeg word. Hiervoor word die funksie gebruik mdb_put.

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

In die konfigurasiestadium kan die bewaarplek toegelaat of verbied word om veelvuldige rekords met dieselfde sleutel te stoor.​As duplisering van sleutels verbied word, kan jy, wanneer jy 'n rekord invoeg, bepaal of die opdatering van 'n reeds bestaande rekord toegelaat word of nie. As uitrafeling slegs kan voorkom as gevolg van 'n fout in die kode, dan kan jy daarteen verseker deur die vlag te spesifiseer NOOVERWRITE.

Lees rekords

Die funksie vir die lees van rekords in LMDB is mdb_get. As die sleutel-waarde-paar deur voorheen gestort strukture verteenwoordig word, dan lyk hierdie prosedure so.

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

Die voorgestelde lys wys hoe serialisering deur 'n storting van strukture jou toelaat om ontslae te raak van dinamiese toekennings nie net wanneer jy skryf nie, maar wanneer jy data lees. Afgelei van funksie mdb_get die wyser kyk presies na die virtuele geheue-adres waar die databasis die byte-voorstelling van die voorwerp stoor. Trouens, ons kry 'n soort ORM, amper gratis wat 'n baie hoë spoed van lees van data verskaf. Met al die skoonheid van die benadering, is dit nodig om verskeie kenmerke wat daarmee verband hou, te onthou.

  1. Vir 'n leesalleen-transaksie word 'n wyser na 'n waardestruktuur gewaarborg om slegs geldig te bly totdat die transaksie gesluit is. Soos vroeër opgemerk, bly die bladsye van die B-boom waarop die voorwerp geleë is, danksy die kopieer-op-skryf-beginsel, onveranderd solank ten minste een transaksie daarna verwys. Terselfdertyd, sodra die laaste transaksie wat daarmee geassosieer word, voltooi is, kan die bladsye hergebruik word vir nuwe data. As dit nodig is dat voorwerpe die transaksie wat hulle geskep het, moet oorleef, moet dit steeds gekopieer word.
  2. Vir 'n lees-skryf-transaksie sal die wyser na die resulterende struktuurwaarde slegs geldig wees tot die eerste wysigingsprosedure (skryf of verwydering van data).
  3. Al is die struktuur NodeValue nie volwaardig nie, maar geknip (sien onderafdeling "Bestel sleutels deur 'n eksterne vergelyker"), deur die wyser, kan jy maklik toegang tot sy velde kry. Die belangrikste ding is om dit nie te verwaarloos nie!
  4. In geen geval kan jy die struktuur deur die ontvangde wyser verander nie. Alle veranderinge moet slegs deur die metode gemaak word mdb_put. Met al die begeerte om dit te doen, sal dit egter nie werk nie, aangesien die geheuearea waar hierdie struktuur geleë is, in leesalleenmodus gekarteer word.
  5. Hermap 'n lêer na die adresspasie van 'n proses om byvoorbeeld die maksimum stoorgrootte te vergroot deur die funksie te gebruik mdb_env_set_map_size maak alle transaksies en verwante entiteite in die algemeen heeltemal ongeldig en verwysings om voorwerpe in die besonder te lees.

Ten slotte, nog een kenmerk is so verraderlik dat die openbaarmaking van die essensie daarvan nie in net nog een punt inpas nie. In die hoofstuk oor die B-boom het ek 'n diagram gegee van die organisasie van sy bladsye in geheue. Dit volg daaruit dat die adres van die begin van die buffer met geserialiseerde data absoluut arbitrêr kan wees. As gevolg hiervan, die wyser na hulle, verkry in die struktuur MDB_val en gegiet na 'n wyser na 'n struktuur is oor die algemeen nie in lyn nie. Terselfdertyd vereis die argitekture van sommige skyfies (in die geval van iOS, dit is armv7) dat die adres van enige data 'n veelvoud van die grootte van 'n masjienwoord is, of, met ander woorde, die bitheid van die stelsel (vir armv7 is dit 32 bisse). Met ander woorde, 'n operasie soos *(int *foo)0x800002 op hulle word gelykgestel aan ontsnapping en lei tot teregstelling met 'n vonnis EXC_ARM_DA_ALIGN. Daar is twee maniere om so 'n hartseer lot te vermy.

Die eerste een is om die data vooraf na 'n bekende-belynde struktuur te kopieer. Byvoorbeeld, op 'n pasgemaakte vergelyker, sal dit soos volg weerspieël word.

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 // ...
}

'n Alternatiewe manier is om die samesteller vooraf in kennis te stel dat strukture met 'n sleutel en waarde dalk nie met behulp van 'n kenmerk in lyn gebring kan word nie aligned(1). Op ARM kan dieselfde effek wees bereik en die gebruik van die verpakte kenmerk. Aangesien dit ook bydra tot die optimalisering van die ruimte wat deur die struktuur ingeneem word, lyk hierdie metode vir my verkieslik, alhoewel приводит om die koste van datatoegangsbedrywighede te verhoog.

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

Reeksnavrae

Om oor 'n groep rekords te herhaal, verskaf LMDB 'n wyserabstraksie. Kom ons kyk hoe om daarmee te werk deur die voorbeeld van 'n tabel met gebruikerswolk-metadata wat reeds aan ons bekend is, te gebruik.

As deel van die vertoon van 'n lys lêers in 'n gids, moet jy al die sleutels vind waarmee sy kinderlêers en vouers geassosieer word. In die vorige onderafdelings het ons die sleutels gesorteer NodeKey sodat hulle eers volgens hul ouergids-ID georden word. Dus, tegnies, word die taak om die inhoud van 'n gids te bekom verminder tot die plasing van die wyser op die boonste grens van 'n groep sleutels met 'n gegewe voorvoegsel, gevolg deur iterasie na die onderste grens.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Jy kan die boonste grens "op die voorkop" vind deur opeenvolgende soektog. Om dit te doen, word die wyser aan die begin van die hele lys sleutels in die databasis geplaas en dan verhoog totdat die sleutel met die ouergids-identifiseerder daaronder verskyn. Hierdie benadering het 2 ooglopende nadele:

  1. Die lineêre kompleksiteit van die soektog, alhoewel, soos u weet, in bome in die algemeen en in 'n B-boom in die besonder, dit in logaritmiese tyd gedoen kan word.
  2. Tevergeefs word alle bladsye wat die verlangde een voorafgaan van die lêer na die hoofgeheue verhoog, wat uiters duur is.

Gelukkig bied die LMDB API 'n doeltreffende manier om die wyser aanvanklik te posisioneer. Om dit te doen, moet jy 'n sleutel vorm waarvan die waarde bekend is dat dit minder as of gelyk is aan die sleutel wat op die boonste grens van die interval geleë is. Byvoorbeeld, met betrekking tot die lys in die figuur hierbo, kan ons 'n sleutel maak waarin die veld parentId sal gelyk wees aan 2, en al die res is gevul met nulle. So 'n gedeeltelik gevulde sleutel word na die invoer van die funksie gevoer mdb_cursor_get werking aandui 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);

As die boonste grens van die groep sleutels gevind word, dan herhaal ons daaroor totdat óf ons ontmoet óf die sleutel met 'n ander parentId, of die sleutels sal glad nie opraak nie.​

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 lekker is, as deel van iterasie met behulp van mdb_cursor_get, kry ons nie net die sleutel nie, maar ook die waarde. As dit nodig is om, om aan die keuringsvoorwaardes te voldoen, onder andere die velde van die waarde-deel van die rekord na te gaan, dan is dit redelik toeganklik vir hulself sonder bykomende gebare.

4.3. Modellering van verhoudings tussen tabelle

Tot op hede het ons daarin geslaag om alle aspekte van die ontwerp en werk met 'n enkeltabeldatabasis te oorweeg. Ons kan sê dat 'n tabel 'n stel gesorteerde rekords is wat bestaan ​​uit sleutel-waarde-pare van dieselfde tipe. As jy 'n sleutel as 'n reghoek en sy gepaardgaande waarde as 'n boks vertoon, kry jy 'n visuele diagram van die databasis.

â € <

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

In die werklike lewe is dit egter selde moontlik om met so min bloed oor die weg te kom. Dikwels word dit in 'n databasis vereis, eerstens, om verskeie tabelle te hê, en tweedens, om seleksies daarin uit te voer in 'n ander volgorde as die primêre sleutel. Hierdie laaste afdeling word gewy aan die kwessies van hul skepping en onderlinge verbinding.

Indeks tabelle

Die wolk-toepassing het 'n "Galery"-afdeling. Dit vertoon media-inhoud van die hele wolk, gesorteer volgens datum. Vir die optimale implementering van so 'n keuse, langs die hooftabel, moet u 'n ander een met 'n nuwe tipe sleutels skep. Hulle sal 'n veld bevat met die datum waarop die lêer geskep is, wat as die primêre sorteerkriterium sal optree. Omdat die nuwe sleutels na dieselfde data verwys as die sleutels in die onderliggende tabel, word hulle indekssleutels genoem. Hulle is in die prentjie hieronder in oranje uitgelig.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Om die sleutels van verskillende tabelle van mekaar binne dieselfde databasis te skei, is 'n addisionele tegniese veld tableId by almal gevoeg. Deur dit die hoogste prioriteit vir sortering te maak, sal ons die sleutels eerstens volgens tabelle groepeer, en reeds binne die tabelle - volgens ons eie reëls.​

Die indekssleutel verwys na dieselfde data as die primêre sleutel. Die eenvoudige implementering van hierdie eiendom deur 'n kopie van die waardedeel van die primêre sleutel daarmee te assosieer is suboptimaal vanuit verskeie oogpunte gelyktydig:

  1. Vanuit 'n ruimte-besette oogpunt kan die metadata redelik ryk wees.
  2. Vanuit 'n prestasie-oogpunt, aangesien jy twee sleutels moet oorskryf wanneer jy die nodus-metadata opdateer.
  3. Uit die oogpunt van kode-ondersteuning, as ons vergeet om die data vir een van die sleutels op te dateer, sal ons 'n subtiele fout van data-inkonsekwentheid in die berging kry.

Vervolgens sal ons kyk hoe om hierdie tekortkominge uit te skakel.

Organisering van verwantskappe tussen tabelle

Die patroon is goed geskik om 'n indekstabel met die hoof een te koppel "sleutel as waarde". Soos die naam aandui, is die waardedeel van die indeksrekord 'n kopie van die primêre sleutelwaarde. Hierdie benadering elimineer al die nadele hierbo gelys wat verband hou met die stoor van 'n kopie van die waarde-deel van die primêre rekord. Die enigste fooi is dat om die waarde deur die indekssleutel te kry, jy 2 navrae na die databasis moet maak in plaas van een. Skematies is die resulterende databasisskema soos volg.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Nog 'n patroon vir die organisering van verhoudings tussen tabelle is "oortollige sleutel". Die essensie daarvan is om bykomende eienskappe by die sleutel by te voeg, wat nie nodig is vir sortering nie, maar om die geassosieerde sleutel te herskep. Daar is egter werklike voorbeelde van die gebruik daarvan in die Mail.ru Wolk-toepassing, om te verhoed dat diep duik in die konteks van spesifieke iOS-raamwerke, sal ek 'n fiktiewe, maar 'n meer verstaanbare voorbeeld gee.

Wolk-mobiele kliënte het 'n bladsy wat al die lêers en vouers vertoon wat die gebruiker met ander mense gedeel het. Aangesien daar relatief min sulke lêers is, en daar baie spesifieke inligting oor publisiteit daarmee gepaardgaan (aan wie toegang verleen word, met watter regte, ens.), sal dit nie rasioneel wees om dit te belas met die waarde-deel van die inskrywing in die hooftabel. As jy egter sulke lêers vanlyn wil vertoon, moet jy dit steeds iewers stoor. 'n Natuurlike oplossing is om 'n aparte tafel daarvoor te skep. In die diagram hieronder word die sleutel voorafgegaan met "P", en die "propname" plekhouer kan vervang word met die meer spesifieke waarde "public info".​

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Alle unieke metadata, ter wille waarvan die nuwe tabel geskep is, word na die waardedeel van die rekord geskuif. Terselfdertyd wil ek nie die data oor lêers en vouers wat reeds in die hooftabel gestoor is, dupliseer nie. In plaas daarvan word oortollige data by die "P"-sleutel gevoeg in die vorm van die "node-ID" en "tydstempel"-velde. Danksy hulle kan jy 'n indekssleutel bou, waardeur jy die primêre sleutel kan kry, waardeur jy uiteindelik die metadata van die nodus kan kry.

Afsluiting

Ons evalueer die resultate van LMDB-implementering positief. Daarna het die aantal toedieningsbevriesings met 30% afgeneem.

Die briljantheid en armoede van die sleutelwaarde-databasis LMDB in iOS-toepassings

Die resultate van die werk wat gedoen is, het 'n reaksie buite die iOS-span gevind. Tans het een van die hoof "Lêers"-afdelings in die Android-toepassing ook oorgeskakel na die gebruik van LMDB, en ander dele is op pad. Die C-taal, waarin die sleutelwaarde-berging geïmplementeer word, was 'n goeie hulp om die toepassing aanvanklik bindend te maak rondom dit kruisplatform in die C ++-taal. Vir 'n naatlose verbinding van die resulterende C++-biblioteek met platformkode in Objective-C en Kotlin, is 'n kodegenerator gebruik Djinni van Dropbox, maar dit is 'n ander storie.

Bron: will.com

Voeg 'n opmerking