LMDB and its pros and cons in iOS applications

LMDB and its pros and cons in iOS applications

In autumn 2019, a long-anticipated event occurred in the iOS team of Mail.ru Cloud. The primary database for persistently storing app state became a rather exotic choice for the mobile world. Lightning Memory-Mapped Database (LMDB). Below, you'll find a detailed overview presented in four parts. First, we'll discuss the reasons for such a non-trivial and challenging choice. Then, we'll explore three pillars of LMDB architecture: memory-mapped files, B+-trees, and the copy-on-write approach for implementing transactionality and multi-versioning. Finally, we'll dive into a practical section where we'll design and implement a scheme with multiple tables on top of a low-level key-value API, including indexing.

Sisu

  1. Motivation for adoption
  2. Positioning of LMDB
  3. Three pillars of LMDB
    3.1. Pillar #1. Memory-mapped files
    3.2. Pillar #2. B+-tree
    3.3. Pillar #3. Copy-on-write
  4. Designing a data schema on top of key-value API
    4.1. Basic abstractions
    4.2. Modeling tables
    4.3. Modeling relationships between tables

1. Motivation for adoption

Kordagi 2015. aastal muretsesime selle ĂŒle, kui sageli meie rakenduse liides laguneb. Meie tegevus ei olnud juhuslik, kuna saime jĂ€rjest rohkem kaebusi selle kohta, et rakendus ei reageeri mĂ”nikord kasutaja toimingutele: nupud ei tööta, loendid ei kerida jne. MÔÔtmise mehhanikast olen ma rÀÀkinud AvitoTech'is, seetĂ”ttu toon siin vĂ€lja ainult number.

LMDB and its pros and cons in iOS applications

MÔÔtmise tulemused olid meile jahmatavad. Selgus, et probleeme, mis tekivad kĂŒlmumiste tĂ”ttu, on palju rohkem kui muude probleemidega. Kui enne selle fakti mĂ”istmist oli peamine tehniline kvaliteedi nĂ€itaja crash free, siis pĂ€rast keskendus fookus freeze free'ile.

Ehitasime dashboarid kĂŒlmumiste jaoks ja viisime lĂ€bi kvantitatiivse ja kvalitatiivse analĂŒĂŒsi nende pĂ”hjuste osas, mis aitasid mĂ”ista peamist vaenlast — keeruline Ă€riloogika, mis tĂ€idetakse rakenduse pĂ”hijoone jooksul. Loomulik reaktsioon sellisele olukorrale oli tungiv soov jaotada see tööliste voogude vahel. Selle probleemi sĂŒsteemseks lahendamiseks kasutasime kergkaaluliste aktorite pĂ”hjal mitme tahkuse arhitektuuri. Selle kohandust iOSi maailmas pĂŒhendasin kaks teemat kollektiivses Twitteris ja artikkelist Habr's. KĂ€esoleva jutustuse raames soovin rĂ”hutada neid lahendusi, mis mĂ”jutasid andmebaasi valikut.

Actorite mudel sĂŒsteemi korraldamiseks eeldab, et mitme sĂŒsteemi samaaegne töötamine muutub selle teiseks olemuseks. Mudeli objektid armastavad ĂŒletada voogude piire. Ja nad teevad seda mitte mĂ”nikord ja kusagil, vaid praktiliselt pidevalt ja igal pool.

LMDB and its pros and cons in iOS applications

Andmebaas on ĂŒks esmatĂ€htsaid komponente esitatud skeemis. Selle peamine ĂŒlesanne on makromustri rakendamine. Shared Database. Kui ettevĂ”tlusmaailmas organiseeritakse andmete sĂŒnkroniseerimist teenuste vahel, siis nĂ€itlejarakendusarkitektuuris – andmed voogude vahel. SeetĂ”ttu vajasime andmebaasi, mille kasutamine mitme lĂ”ime keskkonnas ei pĂ”hjusta isegi minimaalset raskust. Üksnes see tĂ€hendab, et sellest saadud objektid peavad olema vĂ€hemalt lĂ”imekindlad ja ideaaljuhul tĂ€iesti muutumatud. Nagu on teada, saab viimaseid kasutada korraga mitmest lĂ”imest, vĂ€ltides igasuguseid lukustusi, mis toob kaasa parema jĂ”udluse.

LMDB and its pros and cons in iOS applicationsTeiseks oluliseks teguriks, mis mĂ”jutas andmebaasi valikut, oli meie pilve API. See sai inspiratsiooni sĂŒnkroniseerimise lĂ€henemisest, mida kasutatakse git'is. Nagu see, sihitasime offline-first API, mis cloud'i kliendi jaoks nĂ€eb vĂ€lja rohkem kui asjakohane. Eeldati, et nad laadivad pilvekeskkonna tĂ€ieliku oleku alla ainult ĂŒks kord, pĂ€rast mida toimub sĂŒnkroniseerimine enamikus juhtudel muudatuste rakendamise kaudu. Kahjuks on see vĂ”imalus endiselt vaid teoreetilises sfÀÀris ning praktikas ei ole kliendid patchesidega töötama Ă”ppinud. Sellel on mitmeid objektiivseid pĂ”hjuseid, millele me ei keskendu selgitustes. Hetkel on palju suurem huvi selle Ă”petliku jĂ€relduse ĂŒle, mis juhtub, kui API ĂŒtleb "A", aga selle kasutaja ei ĂŒtle "B".

Kujutage nĂŒĂŒd ette git'i, mis kĂ€skluse pull tĂ€itmisel mitte ei rakenda plekke kohaliku snappimisele, vaid vĂ”rdleb selle tĂ€ielikku seisundit serveri tĂ€ieliku seisundiga. See annab piisava ettekujutuse, kuidas sĂŒnkroniseerimine toimub pilveklientides. Pole raske arvata, et selle teostamiseks tuleb mĂ€lus eraldada kaks DOM-puulehte, mis sisaldavad metaandmeid kĂ”igi serveri ja kohalike failide kohta. Seega, kui kasutaja salvestab pilves 500 tuhat faili, tuleb tema sĂŒnkroniseerimiseks luua ja hĂ€vitada kaks puu lehte, mis sisaldavad 1 miljon sĂ”lme. Iga sĂ”lm on aggregaat, mis sisaldab endas graafi objektide alamgruppidest. Sellest vaatenurgast osutusid profiliseerimise tulemused ootuspĂ€rasteks. Selgus, et isegi ilma sulandumisalgoritmita maksab juba iseenesest tohutu hulga pisikeste objektide loomise ja hĂ€vitamise protseduur. Olukorda sĂŒvendab see, et pĂ”hitegevus sĂŒnkroniseerimine on kaasatud paljude kasutajate stsenaariumidesse. Seega fikseerime teise olulise kriteeriumi andmebaasi valikul — CRUD-operatsioonide teostamise vĂ”imalus ilma dĂŒnaamiliste objektide eraldamiseta.

Teised nÔuded on traditsioonilisemad ja nende tÀielik nimekiri on jÀrgmine.

  1. Voogude turvalisus.
  2. Mitme protsessori kasutamine. See tuleneb soovist kasutada sama andmebaasi eksemplari, et sĂŒnkroniseerida olekut mitte ainult voogude vahel, vaid ka peamise rakenduse ja iOSi laienduste vahel.
  3. VÔime esitada salvestatud entiteedid kui muutumatuid objekte.
  4. DĂŒnaamiliste mÀÀramiste puudumine CRUD-operatsioonide kĂ€igus.
  5. Toe toimetamine pÔhivÀÀrtustele ACID: aatomilisus, jÀrjepidevus, isoleeritus ja usaldusvÀÀrsus.
  6. Kiirus kÔige populaarsemates kasutusjuhtumites.

Hea valik sellise nĂ”uete kogumi jaoks oli ja jÀÀb SQLite. Kuid alternatiivide uurimise raames sai minu kĂ€tte raamat „Getting Started with LevelDB“. Tema juhtimisel koostati benchmark, mis vĂ”rdles erinevate andmebaaside töökiirusest reaalses pilvescenaaris. Tulemused ĂŒletasid kĂ”ige julgemad ootused. KĂ”ige populaarsemates juhtumites – kursori saamine kĂ”igi failide jĂ€rjestatud loendisse ja mÀÀratud katalooge kĂ”igi failide jĂ€rjestatud loendisse – osutus LMDB 10 korda kiiremaks kui SQLite. Valik on ilmne.

LMDB and its pros and cons in iOS applications

2. LMDB positsioon

LMDB on vĂ€ga vĂ€ike teek (ainult 10K rida), millel on rakendatud andmebaaside kĂ”ige madalam pĂ”hikiht – sĂ€ilitamine.

LMDB and its pros and cons in iOS applications

Esitatud skeem nĂ€itab, et LMDB-d ei ole korrektne vĂ”rrelda SQLite-ga, mis rakendab ka kĂ”rgemaid tasemeid, sama nagu ei ole korrektne vĂ”rrelda SQLite-d Core Data-ga. VĂ”rdsete konkurentidena oleks Ă”iglasem tuua vĂ€lja sarnased sĂ€ilitustehnikad – BerkeleyDB, LevelDB, Sophia, RocksDB jne. On isegi arendusi, kus LMDB toimib SQLite sĂ€ilituse komponendina. Esimese sellise eksperimendi viidi lĂ€bi 2012. aastal. tegi LMDB autor Howard Chu. Tulemused olid nii intrigeerivad, et tema algatus vĂ”eti ĂŒle OSS entusiastide poolt ja leidis jĂ€tku LumoSQL. Jaanuaris 2020 esitas projekti autor Den Shearer esitluse LinuxConfAu-l.

LMDB peamine rakendus on rakenduslike andmebaaside mootorina. Raamatukogu sai alguse arendajatelt OpenLDAP, kes olid vĂ€ga rahulolematud BerkeleyDB-ga oma projekti aluseks. Alustades tagasihoidlikust raamatukogust btree, suutis Howard Chu luua ĂŒhe kĂ”ige populaarsema alternatiivi tĂ€napĂ€eval. Sellele loole ja LMDB sisemisele seadistusele pĂŒhendas ta oma laheda ettekande «The Lightning Memory-mapped Database». Hea nĂ€ite andmete talletamise valdamise kohta jagas Leonid Jurjev (aka yleo) Positive Technologies'il oma ettekandes Highload 2015 konverentsil «DĂŒnaamiline LMDB — eriline meister». Selles rÀÀgib ta LMDB-st sarnase ĂŒlesande kontekstis, rakendades ReOpenLDAP, samal ajal kui LevelDB sai kriitilise hinnangu. Tulemuseks oli, et Positive Technologies'il tekkis aktiivselt arenev forkk MDBX , millel on palju hĂ€id funktsioone, optimeerimisi ja veakorrigeerimisi..

LMDB-d kasutatakse sageli andmete talletuseks. NĂ€iteks valis brauser Mozilla Firefox selle mitmete vajaduste jaoks, ja alates versioonist 9 on Xcode eelistanud seda SQLite-le indeksite talletamiseks.

Mootor on saanud tÀhelepanu mobiilse arenduse maailmas. Selle kasutamise jÀlgi vÔib leidma Telegrami iOS-Àpis. LinkedIn lÀks veelgi kaugemale ja valis LMDB vaikimisi andmete salvestusruumiks oma kohandatud andmevahemiku raamistikus Rocket Data, millest rÀÀkis oma artiklis 2016. aastal.

LMDB vĂ”itleb edukalt oma kohta pĂ€ikese all niĆĄĆĄis, mille jĂ€ttis BerkeleyDB pĂ€rast ĂŒleminekut Oracle'i kontrolli alla. Raamatukogu on saanud armastust oma kiirusest ja usaldusvÀÀrsusest isegi vĂ”rreldes sarnaste lahendustega. Nagu teada, ei ole tasuta lĂ”unaid ning tahetakse rĂ”hutada kompromissi, millega tuleb silmitsi seista valides LMDB ja SQLite vahel. Ülalolev skeem nĂ€itab selgelt, kuidas saavutatakse suurenenud kiirus. Esiteks, me ei maksa lisatasu diskihaldurite peal olevate abstraktsioonikihtide eest. Selge on see, et hea arhitektuur ei saa ilma nendeta hakkama, ja need ilmuvad nĂ”nda vĂ”i teisiti rakenduste koodis, kuid need on kindlasti palju Ă”hemad. Nendes ei ole funktsioone, mida konkreetne rakendus ei vaja, nagu nĂ€iteks SQL-i keelepĂ€ringute toetamine. Teiseks, avaneb vĂ”imalus rakendada optimaalselt rakenduslike operatsioonide mappimist diskihalduri pĂ€ringutele. Kui SQLite oma töös lĂ€htub keskmiste vajaduste keskmiselt rakendusest, siis olete rakenduse arendajana hĂ€sti teadlik peamistest koormuse stsenaariumidest. Tootlikuma lahenduse eest tuleb maksta kĂ”rgemat hinda nii esialgse lahenduse arendamise kui ka selle edasise toe eest.

3. Kolm LMDB alustala

Vaadates LMDB-d linnulennult, on aeg laskuda sĂŒg deeper. JĂ€rgmised kolm osa kĂ€sitlevad peamisi tugisambasid, millel hoone arhitektuur pĂ”hineb:

  1. MĂ€lu kaardistatud failid kui mehhanism ketas-tööks ja sisemiste andmestruktuuride sĂŒnkroonimiseks.
  2. B+-puu kui salvestatud andmete struktuuri korraldamise viis.
  3. Copy-on-write kui lÀhenemine tehingute ACID-omaduste tagamiseks ja mitme versiooni haldamiseks.

3.1. Alustala nr 1. MĂ€lu kaardistatud failid

MĂ€lufailid on nii olulised arhitektuuri elemendid, et need kajastuvad isegi salvestusruumi nimekirjas. Kittimiste ja salvestatud teabele juurdepÀÀsu sĂŒnkroniseerimise kĂŒsimused on tĂ€ielikult operatsioonisĂŒsteemi ĂŒlesanne. LMDB ei sisalda endas mingeid vahemĂ€lusid. See on autori teadlik valik, kuna andmete lugemine otse kaardistatud failidest vĂ”imaldab rakenduse pĂ”hjal palju nurki lĂ”igata. Allpool on toodud kindlasti mitte ammendav nimekiri mĂ”nest neist.

  1. Andmete jĂ€rjepidevuse sĂ€ilitamine salvestusruumis, kui sellega töötavad mitu protsessi, muutub operatsioonisĂŒsteemi ĂŒlesandeks. JĂ€rgmises osas kĂ€sitletakse seda mehhanismi ĂŒksikasjalikult ja koos joonistega.
  2. VahemĂ€lude puudumine vabastab LMDB tĂ€ielikult dĂŒnaamilise jaotusega seotud kuludest. Andmete lugemine on praktikas lihtsalt viitamine Ă”igele aadressile virtuaalses mĂ€lus ja mitte midagi enamat. See kĂ”lab nagu ulme, kuid salvestusruumi lĂ€htefailides on kĂ”ik salloc'i kutsed koondatud salvestuse konfigureerimise funktsiooni.
  3. Vahetevaheline vahemĂ€lu puudumine tĂ€hendab, et sĂŒnkroniseerimine nende juurde pÀÀsemise osas ei pĂ”hjusta mingeid blokeeringuid. Samaaegselt vĂ”ib olla suvaline hulk lugejaid, kes ei kohtu andmete teel ĂŒhtegi mĂ€lukeelt. SeetĂ”ttu on lugemise kiirus ideaalne lineaarselt skaleeritav iga CPU arvu jĂ€rgi. LMDB-s on sĂŒnkroniseeritud ainult muudatusoperatsioonid. Iga hetke jooksul vĂ”ib kirjutaja olla vaid ĂŒks.
  4. Madal vahemĂ€lu ja sĂŒnkroniseerimislĂ€henemise loogika vabastab koodi ÀÀrmiselt keerulistest vigadest, mis on seotud mitme niidi tööga. Usenix OSDI 2014 konverentsil oli kaks huvitavat uurimistööd andmebaaside kohta: „KĂ”ik failisĂŒsteemid ei ole vĂ”rdsed: rakenduste kokkuvarisemise jĂ€rjepidevuse valmistamine on keeruline“ ja „Andmebaaside piinamine lĂ”bu ja kasu nimel“. Nendest saab teavet nii LMDB enneolematust usaldusvÀÀrsusest kui ka praktiliselt veatut ACID-tehingute omaduste rakendust, mis ĂŒletab samas SQLite'i.
  5. LMDB minimalistlikkus vÔimaldab masina esitluse koodi tÀielikult paigutada protsessori L1-vahemÀllu, mis toob kaasa kiirusomadused.

Kahjuks ei ole iOS-is mĂ€lu kaardistatud failidega kĂ”ik nii roosiline, nagu sooviks. Seotud puuduste teadlikumaks arutamiseks on oluline meenutada selle mehhanismi rakendamise ĂŒldpĂ”himĂ”tteid operatsioonisĂŒsteemides.

Ülevaade mĂ€lu kaardistatud failidest

LMDB and its pros and cons in iOS applicationsIga kĂ€ivitatava rakendusega seondub operatsioonisĂŒsteemile ĂŒksus nimega protsess. Iga protsessile eraldatakse pidev aadressivahemik, kus ta paigutab kogu vajalikud tööks. Aadresside madalamates osades paiknevad koodi ning kĂ”vakettale kirjutatud andmete ja ressursside sektsioonid. SeejĂ€rel jĂ€rgneb ĂŒlespoole kasvav dĂŒnaamiline aadressiruum, mis on meile tuttav nime all heap. See sisaldab aadresse ĂŒksustele, mis ilmuvad programmi töö kĂ€igus. Üksuse ĂŒlemises osas asub mĂ€lu ala, mida rakendus kasutab steki jaoks. See kasvab ja kahaneb, teisisĂ”nu, selle suurus on samuti dĂŒnaamiline. Et stekki ja heap'i ei segataks ja teineteist ei segaks, on need paigutatud aadressiruumi erinevatesse otste. Kahe dĂŒnaamilise sektsiooni vahel ĂŒlal ja all on auk. Aadresse keskmises osas kasutab operatsioonisĂŒsteem, et seostada protsessiga erinevaid ĂŒksusi. Konkreetsemalt vĂ”ib see seostada mingi pideva aadresside kogumiga — faili kettal. Sellist faili nimetatakse mĂ€llu kaardistatud failiks.

Eraldatud protsessorile adresseeritud mĂ€luruum on tohutu. Teoreetiliselt on aadresside arv piiratud vaid nĂ€itajate suurusega, sĂ”ltudes sĂŒsteemi bitilisusest. Kui see oleks 1-1 vastavuses fĂŒĂŒsilise mĂ€luga, siis esimene protsess neelaks kogu RAMi, ja multitaskingust ei saaks juttugi olla.

Kuid oma kogemustest teame, et kaasaegsed operatsioonisĂŒsteemid saavad samal ajal kĂ€itada piiramatul hulgal protsesse. See on vĂ”imalik tĂ€nu sellele, et nad jagavad protsessidele mĂ€luruumi vaid paberil, samas kui tegelikku fĂŒĂŒsilist mĂ€lu laaditakse ainult selle osa jaoks, mis on hetkel vajalik. SeetĂ”ttu nimetatakse protsessiga seotud mĂ€lu virtuaalseks.

LMDB and its pros and cons in iOS applications

OperatsioonisĂŒsteem korraldab nii virtuaalset kui fĂŒĂŒsilist mĂ€lu kindlate suurustega lehtedena. Kui mĂ”ni virtuaalmĂ€lu leht osutub vajalikuks, laadib operatsioonisĂŒsteem selle fĂŒĂŒsilisse mĂ€llu ja koostab nende vahel vastavuse spetsiaalses tabelis. Kui vabu kohti ei ole, kopeeritakse ĂŒks varem laaditud leht kettale, ja nĂ”utav leht asetatakse selle kohale. Seda protseduuri, millega me peagi tagasi tuleme, nimetatakse vahetuseks (swapping). Allolev joonis illustreerib kirjeldatud protsessi. Sellel on leht A aadressiga 0, mis on laaditud ja asetatud pĂ”himuudesse lehe aadressiga 4. See fakt leidis kajastust vastavustabelis lahtris number 0.

LMDB and its pros and cons in iOS applications

Failide mĂ€lus kuvatud ajalugu on sama. Loogiliselt vĂ€idetakse, et need paigutatakse pidevalt ja koos kogu ulatuses virtuaalsesse aadressiruumi. Siiski jĂ”uavad nad fĂŒĂŒsilisse mĂ€llu lehekaupa ja ainult nĂ”udmisel. Selliste lehtede muutmine sĂŒnkroniseeritakse diskifailiga. Seega on vĂ”imalik failide sisendi/vĂ€ljundi teostamine, töötades lihtsalt mĂ€lus baitidega — kĂ”ik muutused kantakse operatsioonisĂŒsteemi tuuma poolt automaatselt algsesse faili.​
​
Allolev pilt nĂ€itab, kuidas LMDB sĂŒnkroniseerib oma olekut, töötades andmebaasiga erinevatest protsessidest. Kaardistades erinevate protsesside virtuaalmĂ€lu ĂŒhte ja samasse faili, kohustame me tegelikult operatsioonisĂŒsteemi transitiivselt sĂŒnkroniseerima teatud plokke nende aadressiruumi vahel, kuhu LMDB vaatab.​
​

LMDB and its pros and cons in iOS applications

Oluline nĂŒanss on see, et LMDB muudab vaikimisi andmefaili lĂ€bi sĂŒsteemikĂ”ne write, samas kui fail ise on avatud ainult lugemiseks. Selle lĂ€henemisega on kaks olulist tagajĂ€rge.

Esimene jĂ€reldus kehtib kĂ”igi operatsioonisĂŒsteemide puhul. Selle olemus seisneb andmebaasi kaitsmas tahtmatu kahjustamise eest vigase koodi tĂ”ttu. Nagu teada, vĂ”ivad protsessi kĂ€ivitatavad kĂ€sud juurdepÀÀseda andmetele mis tahes kohast oma aadressiruumi. Samas, nagu just mainisime, tĂ€hendab faili avamine lugemis-kirjutamisreĆŸiimis, et iga kĂ€sk vĂ”ib seda ka modifitseerida. Kui ta teeb seda ekslikult, pĂŒĂŒdes nĂ€iteks kirjutada massiivi elementi, mille indeks ei eksisteeri, vĂ”ib ta sellisel juhul juhuslikult muuta sellele aadressile kaardistatud faili, mis viib andmebaasi rikkumiseni. Kui fail on avatud ainult lugemiseks, siis vastava aadressiruumi muutmine viib programmi ootamatu lĂ”petamiseni signaaliga SIGSEGV, ja fail jÀÀb terviklikuks.

Teine jĂ€reldus on spetsiifiline juba iOS-ile. Ei autor ega ĂŒkski teine allikas ei maini seda selgelt, kuid ilma selleta ei oleks LMDB selle mobiilse operatsioonisĂŒsteemi jaoks kasutatav. Sellele pĂŒhendatakse jĂ€rgmine jagu.

iOS-is mÀllu laaditud failide spetsiifika

2018. aastal toimunud WWDC-l toimus suurepĂ€rane ettekande "iOS Memory Deep Dive". Ettekandes rÀÀgitakse, et iOS-is kuuluvad kĂ”ik fĂŒĂŒsilises mĂ€lus asuvad lehed ĂŒhte kolmest tĂŒĂŒbist: dirty, compressed ja clean.

LMDB and its pros and cons in iOS applications

Puhas mĂ€lu — see on lehtede kogum, mille saab fĂŒĂŒsilisest mĂ€lust valutult eemaldada. Nendes leiduvad andmed saab vajadusel uuesti algallikatest laadida. Üksnes lugemiseks mĂ”eldud mĂ€luga kaardistatud failid kuuluvad just sellesse kategooriasse. iOS ei pelga igal ajal lehti mĂ€lu tĂ€itmise ajal vĂ€lja laadida, kuna need on garanteeritult sĂŒnkroniseeritud failiga kettal.
​
MÀÀrdunud mĂ€lu hĂ”lmab kĂ”iki muudetud lehti, sĂ”ltumata nende algsest asukohast. EelkĂ”ige klassifitseeritakse sellesse ka mĂ€luga kaardistatud failid, mida on muudetud kirjutamise kaudu nendele vastavasse virtuaalsesse mĂ€llu. Avades LMDB lipuga MDB_WRITEMAP, pĂ€rast muudatuste tegemist saab seda isiklikult kinnitada.​

Kui rakendus hakkab tarbima liiga palju fĂŒĂŒsilist mĂ€lu, siis iOS kompressib selle dirty lehekĂŒljed. Dirty ja kompressitud lehekĂŒlgede mĂ€lukoormus koos moodustab rakenduse nii-öelda memory footprint. Kui see saavutab teatud piirvÀÀrtuse, siis sekkub sĂŒsteemne daemon OOM killer ja sunnib protsessi lĂ”petama. See on iOS eripĂ€ra vĂ”rreldes lauaarvutite opsĂŒsteemidega. Erinevalt neist ei ole iOS-is vĂ”imalik memory footprinti vĂ€hendada lehekĂŒlgede vahetamise kaudu fĂŒĂŒsilisest mĂ€lust kettale. PĂ”hjustest vĂ”ib vaid spekuleerida. VĂ”ib-olla on lehekĂŒlgede intensiivne liikumine kettale ja tagasi mobiilseadmete jaoks liiga energiat nĂ”udev vĂ”i pĂŒĂŒab iOS sÀÀsta SSD-de lahtrite ĂŒlekirjutamise ressursse, vĂ”i ei olnud projekteerijate jaoks sĂŒsteemi ĂŒldine jĂ”udlus, kus kĂ”ik pidevalt vahetub, rahuldav. Kuidas iganes, fakt jÀÀb faktiks.

Hea uudis, nagu varem mainitud, on see, et LMDB ei kasuta vaikimisi failide uuendamiseks mmap mehhanismi. SeetÔttu klassifitseerib iOS kuvatud andmed kui puhas mÀlu, mis ei mÔjuta mÀlu jalajÀlge. Selle tÔestamiseks saab kasutada Xcode'i tööriista nimega VM Tracker. Alloleval ekraanipildil on kujutatud iOS rakenduse Oblaak virtuaalse mÀlu seisundit töötamise ajal. Alguses oli initsialiseeritud 2 LMDB instantsi. Esimesele oli lubatud kaardistada oma fail 1GiB virtuaalsele mÀlule, teisele - 512MiB. Kuigi mÔlemad salvestused vÔtavad teatud koguse residentset mÀlu, ei pane kumbki neist kaasa mÀÀrdunud suurusele.

LMDB and its pros and cons in iOS applications

Ja nĂŒĂŒd on aeg halbadeks uudisteks. 64-bitistes lauaarvutite operatsioonisĂŒsteemides kĂ€ivitusmehhanismi tĂ”ttu vĂ”ib iga protsess hĂ”ivata nii palju virtuaalset aadressiruumi, kui lubab vabade kohtade olemasolu kĂ”vakettal selle potentsiaalse vahetuse jaoks. Vahetuse asendamine iOS-is kompressiooniga vĂ€hendab teoreetilist maksimaalset raunditavate ressursside arvu. NĂŒĂŒd peavad kĂ”ik aktiivsed protsessid mahtuma pĂ”hisse (loe: operatiivmĂ€lu), ning kĂ”ik, mis ei mahtunud, tuleb sundida lĂ”petama. Sellest rÀÀgitakse ka eespool mainitud aruandes, kui ka ametlikus dokumentatsioonis. Selle tulemusena piirab iOS rangelt mĂ€lu hulka, mis on saadaval mmap-i kaudu eraldamiseks. Siin on siin seda saab uurida eksperimentaalseid mĂ€lumahtude piire, mida on vĂ”imalik erinevates seadmetes selle sĂŒsteemi kĂ”ne abil eraldada. KĂ”ige kaasaegsematel iOS nutitelefonidel on saadaval 2 gigabayti, samas kui tippversioonidel iPadidel kuni 4 gigabayti. Praktiliselt tuleb aga arvestada kĂ”ige madalamate toetatavate seadmete mudelitega, kus olukord on ĂŒsna kehv. Veelgi hullem, kui vaadata rakenduse mĂ€lu staatust VM Tracker'is, vĂ”ib avastada, et LMDB ei ole ainus, kes pretendeerib memory-mapped mĂ€lule. Suured osad varastavad sĂŒsteemi allocatorid, ressurssifailid, pildi töötlemise raamistikud ja teised vĂ€iksemad röövlid.

Katsede tulemuste pÔhjal pilves jÔudsime jÀrgmiste kompromissvÀÀrtusteni, mida eraldatakse LMDB mÀlule: 384 megabaiti 32-bitiste seadmete jaoks ja 768 64-bitiste jaoks. PÀrast selle mahu kasutamist hakkavad kÔik muutmisoperatsioonid lÔpule viima koodiga MDB_MAP_FULL. Selliseid vigu jÀlgime meie jÀlgimises, kuid neid on piisavalt vÀhe, et neid sellel etapil vÔiks ignoreerida.

Üks vĂ€hem ilmne pĂ”hjus mĂ€lukaubanduse liigseks tarbimiseks vĂ”ivad olla pikaajalised tehingud. Selle kahe nĂ€htuse omavahelise seose mĂ”istmiseks aitab meil kahe ĂŒlejÀÀnud LMDB tiigri arutamine.

3.2. Tiiger nr 2. B+-puu

Key-value salvestuskihi peal tabelite emuleerimiseks peab selle API-s olema jÀrgmised toimingud:

  1. Uue elemendi lisamine.
  2. Elemendi otsimine antud vÔtmega.
  3. Elemendi eemaldamine.
  4. Itereerimine vÔtmevahemike kaudu nende sorteerimise jÀrjekorras.

LMDB and its pros and cons in iOS applicationsKĂ”ige lihtsam and structure, millega kĂ”iki nelja toimingut on lihtne teostada, on binaarne otsingupuu. Iga tema sĂ”lm esindab vĂ”tme, jagades kogu alamkogumi tĂŒtarvĂ”tmeid kaheks alampuuks. Vasakul on need, mis on vanemast vĂ€iksemad, ja paremal need, mis on suuremad. Sorteeritud vĂ”tme kogumi saamine saavutatakse ĂŒhe klassikalise puu lĂ€bimise meetodi kaudu.

Binaarset puud on kaks fundamentaalset puudust, mis takistavad nende efektiivsust kettastruktuuride andmetena. Esiteks on nende tasakaaluaste ettearvamatu. On mĂ€rkimisvÀÀrne risk saada puid, mille erinevate oksade kĂ”rgus vĂ”ib tugevalt erineda, mis halvendab oluliselt otsingu algoritmilist keerukust vĂ”rreldes oodatuga. Teiseks, sĂ”lmede vaheline rikkaid ristviiteid röövivad binaarsed puud mĂ€lu lokaliseerimisest. LĂ€hedased sĂ”lmed (suhete poolest) vĂ”ivad asuda tĂ€iesti erinevates lehtedes virtuaalses mĂ€lus. Selle tagajĂ€rjel vĂ”ib isegi mitme naabrussĂ”lme lihtne lĂ€bimine puud vÀÀrtuda sarnase arvu lehekĂŒlgede kĂŒlastamise. See on probleem isegi siis, kui arutame binaarsete puude efektiivsust in-memory andmestruktuuridena, sest pidev lehtede rotatsioon protsessori vahemĂ€lus on kulukas rÔÔm. Kui aga jutt kĂ€ib sageli sĂ”lmedega seotud lehtede diskilt ĂŒles tĂ”stmisest, siis on olukord tĂ€iesti kurb.

LMDB and its pros and cons in iOS applicationsB-puude, olles binaarpuude evolutsioon, lahendavad eelnevalt mainitud probleemid. Esiteks on nad ise balansseeruvad. Teiseks, iga nende sÔlm jagab hulga laste vÔtmeid mitte 2, vaid M jÀrjestatud alamhulkadeks, kusjuures M vÔib olla suhteliselt suur, ulatudes sadadeni vÔi isegi tuhandeteni.

Selle tÔttu:

  1. Igas sÔlmes on suur hulk juba jÀrjestatud vÔtmeid ja puude kÔrgeus on vÀga madal.
  2. PUU omandab mĂ€lu kohalikkuse omaduse, kuna sarnaste vĂ”tmete vÀÀrtused asuvad loomulikult ĂŒksteise lĂ€hedal samades vĂ”i naaber sĂ”lmedes.
  3. VĂ€henevad ĂŒlemineku sĂ”lmede arv puust laskumisel otsinguoperatsiooni ajal.
  4. VÀhenevad lugemise sihtmÀrkide sÔlmede arv range-pÀringute ajal, kuna igas neist on juba suur hulk jÀrjestatud vÔtmeid.

LMDB and its pros and cons in iOS applications

LMDB-s andmete salvestamiseks kasutatakse ĂŒhte B-puude variatsiooni, mida nimetatakse B+-puuks. Ülaltoodud skeemil on kujutatud kolme tĂŒĂŒpi sĂ”lmi, mis selles esinevad:

  1. Tipus asub juur (root). See materialiseerib andmebaasi kontseptsiooni salvestuses. Ühes LMDB instantsis saab luua mitu andmebaasi, mis jagavad kaartide virtuaalset aadressiruumi. IgaĂŒks neist algab omaenda juurest.
  2. Alumise taseme moodustavad lehed (leaf). Just nemad sisaldavad andmebaasis hoitavaid vĂ”tme-vÀÀrtuse paare. See ongi B+-puude eripĂ€ra. Kui tavaline B-puu hoiab vÀÀrtuste osi kĂ”ikide tasemete sĂ”lmedes, siis B+-variandis ainult kĂ”ige alumisel tasemel. Seda fakti silmas pidades, nimetame edaspidi LMDB-s kasutatavat puu alamtĂŒĂŒpi lihtsalt B-puuks.
  3. Juure ja lehtede vahel asub 0 vĂ”i enam tehnilist taset, kus asuvad navigeerimis (branch) sĂ”lmed. Nende ĂŒlesanne on jagada sorteeritud vĂ”tmete kogum lehtede vahel.

FĂŒĂŒsilised sĂ”lmed on eelnevalt mÀÀratletud pikkusega mĂ€lublockid. Nende suurus on mĂ€lulehtede suurusele operatsioonisĂŒsteemis, millest oleme varem rÀÀkinud, kordaja. Allpool on esitatud sĂ”lme struktuur. Pealkirjas on metaandmed, kĂ”ige silmatorkavam neist — kontrollsummad. JĂ€rgneb teave offset'ide kohta, mille alusel asuvad andmeĂŒhikud. Andmed vĂ”ivad olla kas vĂ”tmed, kui rÀÀgime navigatsioonisĂ”lmedest, vĂ”i tĂ€ielikud vĂ”tme-vÀÀrtuse paarid lehtede puhul. TĂ€iendavalt veebilehtede struktuurist saab lugeda töös «Hinnang kĂ”rgtehnoloogiliste vĂ”tme-vÀÀrtuse salvestite kohta».

LMDB and its pros and cons in iOS applications

PÀrast sÔlme-lehtede sisuga tutvumist esitame LMDB B-puude lihtsustatud vormis jÀrgmiselt.

LMDB and its pros and cons in iOS applications

SÔlmede lehed paiknevad diskil jÀrjestikku. Suuremad lehed asuvad faili lÔpu poole. Nii öeldud meta-leht (meta page) sisaldab teavet offset'ide kohta, mille abil leida kÔigi puude juured. LMDB avamisel skaneerib fail lehti tagurpidi, otsides kehtivat meta-lehte, mille kaudu leitakse olemasolevad andmebaasid.

LMDB and its pros and cons in iOS applications

NĂŒĂŒd, kui on arusaam andmete organisatsiooni loogilisest ja fĂŒĂŒsilisest Struktuurist, ehk on vĂ”imalik minna edasi LMDB kolmanda samba kĂ€sitlemisega. Just selle abil toimuvad kĂ”ik muutused hoidlas tehingu- ja eraldusprotsessis, andes andmebaasile tervikuna ka multi-versioonilisuse tunnuse.

3.3. Samba nr 3. Copy-on-write

MĂ”ned B-puuga seotud toimingud hĂ”lmavad mitmeid muudatusi selle sĂ”lmedes. Üheks nĂ€iteks on uue vĂ”tme lisamine sĂ”lmesse, kus on juba saavutatud maksimaalne maht. Sellisel juhul on kĂ”igepealt vajalik sĂ”lm jagada kaheks ja teiseks lisada viide uuele vĂ€lja kasvanud lastesĂ”lmele tema vanemasse. See protseduur on potentsiaalselt vĂ€ga ohtlik. Kui mingil pĂ”hjusel (krahh, toitekatkestus jne) toimub ainult osa muudatusi jĂ€rjest, jÀÀb puu jĂ€rjepidevusseolekusse.

Ühe traditsioonilise lahendusena andmebaasi tĂ”rgetest kaitsmiseks on lisaks B-puule andmestruktuuri diskimooduli — tehingute logi, mida tuntakse ka kui write-ahead log (WAL) — lisamine. See on fail, kuhu kirjutatakse oodataolev toiming enne B-puu enda muutmist. Seega, kui isediagnoosimise kĂ€igus avastatakse andmete kahjustus, pöördub andmebaas logi poole, et ennast korda seada.

LMDB on valinud andmebaasi tÔrgetest kaitsmiseks teistsuguse meetodi, mida nimetatakse copy-on-write. Selle pÔhimÔte seisneb selles, et andmete uuendamisel olemasoleval lehel koopiat ei muudeta, vaid kopeeritakse tÀielikult ja kÔik muudatused tehakse juba koopias.

LMDB and its pros and cons in iOS applications

Edasi, et uuendatud andmed oleksid kergesti kÀttesaadavad, tuleb muuta viidet uuele sÔlmele vanemas oma vanem sÔlmes. Kuna ka seda tuleb modifitseerida, kopeeritakse see eelnevalt. Protsess jÀtkub rekursiivselt juurest. Viimaks muudetakse andmeid meta-lehel.

LMDB and its pros and cons in iOS applications

Kui uuendamise protsessi ajal juhtub ootamatu katkestus, siis ei pruugi uus meta-leht ĂŒldse luua vĂ”i ei salvestata seda kettale tĂ€ielikult, mille tulemusel on selle kontrollsummad vigased. Ühes neist olukordadest on uued lehed kĂ€ttesaamatud, samas kui vanad lehed jÀÀvad puutumatuks. See vabastab LMDB vajadusest pidada kirjutamislogi andmete jĂ€rjepidevuse tagamiseks. Ülaltoodud kirjeldatud andmestruktuur tĂ€idab samal ajal ka selle funktsiooni. Tehingulogide puudumine nĂ€htavas vormis on ĂŒks LMDB omadustest, mis tagab andmete lugemise kĂ”rge kiirus.

LMDB and its pros and cons in iOS applications

Tekst, mille nimi on append-only B-puu, tagab loomulikul viisil tehingute isoleerituse ja mitme versiooni. LMDB-s seondub iga avatud tehingu puhul praegune puu juur. Kuni tehing ei ole lĂ”pule viidud, ei muudeta ega kasutata uuesti seotud puu lehti uute andmeversioonide jaoks. Seega saab töötada ka lĂ”putult tĂ€pselt nende andmete komplektiga, mis oli aktuaalne tehingu avamise hetkel, isegi kui salvestus jĂ€tkab aktiivset vĂ€rskendamist. See on mitme versiooni olemus, mis teeb LMDB-st ideaalse andmeallika meie lemmikute jaoks. UICollectionView. Tehingu avamisel ei ole vaja suurendada rakenduse mĂ€lu tarbimist, kiirustades vajalikke andmeid mĂ”nda in-memory struktuuri, kartes jÀÀda tĂŒhjade kĂ€tega. See omadus eristab LMDB-d SQLite'ist, mis ei saa sellise tĂ€ieliku isolatsiooniga kiidelda. Kui avada viimases kaks tehingut ja kustutada ĂŒks rida ĂŒhe nende raames, ei saa seda rida teises tehingus enam kĂ€tte.

​Medali tagakĂŒljeks on potentsiaalselt oluliselt suurem virtuaalse mĂ€lu kulu. Tootepildil on nĂ€ha, milline nĂ€eb vĂ€lja andmebaasi struktuur, kui selle modifitseerimine toimub samal ajal kolme avatud lugemise tehinguga, mis vaatavad erinevatele andmebaasi versioonidele. Kuna LMDB ei saa taaskasutada sĂ”lmi, mis on seotud aktiivsete tehingutega, ei jÀÀ ladustamisele muud vĂ”imalust, kui paigutada mĂ€lu veel ĂŒks neljas juur ja veel kord kloonida muudetavad lehekĂŒljed.

LMDB and its pros and cons in iOS applications

Siin oleks kasulik meenutada mĂ€lu kaardistamise failide jaotust. Tundub, et tĂ€iendav virtuaalmĂ€lu kulu ei tohiks meid liialt hĂ€irida, kuna see ei too kaasa rakenduse mĂ€lu jalajĂ€lge. Siiski on tĂ€helepanuvÀÀrne, et iOS on ehtsalt kitsi selle eraldamisega, ja me ei saa nagu serveris vĂ”i lauaarvutis laialdaselt jagada 1 terabaiti LMDB piirkonda, mĂ”tlemapanevalt sellest omadusest tĂ€iesti mööda. Kui vĂ”imalik, tuleks pĂŒĂŒda teha tehingute eluiga vĂ”imalikult lĂŒhikeseks.

4. Andmeskeemi projekteerimine key-value API peal

AnalĂŒĂŒsime API-d, alustades LMDB pakutavatest pĂ”histruktuuridest: keskkond ja andmebaasid, vĂ”tmed ja vÀÀrtused, tehingud ja kursori.

MĂ€rkus koodilistingute kohta

KÔik LMDB avaliku API funktsioonid tagastavad oma töö tulemuse veakoodina, kuid kÔigil jÀrgnevates koodilistingutes on selle kontrollimine vahele jÀetud kokkuvÔtlikkuse huvides. Praktikas kasutasime me hoopis oma forki C++ wrapper'it lmdbxx, kus vead materialiseeruvad C++ erandite nÀol.

Kiiruselt kĂ”ige efektiivsema viisina LMDB ĂŒhendamiseks iOS vĂ”i macOS projektiga pakun vĂ€lja oma CocoaPodi POSLMDB.

4.1. PÔhiabstraktsioonid

Keskkond (environment)

Struktuur MDB_env on LMDB sisemise oleku hoidla. Funktsioonide pere prefiksiga mdb_env lubab konfigureerida teatud atribuute. Lihtsaimal juhul nÀeb mootori initsialiseerimine vÀlja jÀrgmiselt.

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

Mail.ru Cloud rakenduses muutsime vaikevÀÀrtusi ainult kahe parameetri jaoks.

Esimene neist on virtuaalse aadressiruumi suurus, kuhu andmesalvestuse fail kaardistatakse. Kahjuks vĂ”ib isegi samas seadmes konkreetne vÀÀrtus mĂ€rkimisvÀÀrselt erineda kĂ€ivituse kaupa. Selle eripĂ€ra arvestamiseks iOSis valitakse mĂ€lumaht dĂŒnaamiliselt. Alustades mingist vÀÀrtusest, vĂ€hendatakse seda jĂ€rjestikku, kuni funktsioon mdb_env_open tagastab tulemuse, mis ei ole ENOMEM. Teoreetiliselt on vĂ”imalik ka vastupidine tee — kĂ”igepealt eraldada mootorile minimaalne mĂ€lu ja seejĂ€rel, kui tekivad vead. MDB_MAP_FULL, suurendama seda. Kuid see on palju keerulisem. PĂ”hjus peitub selles, et mĂ€lu ĂŒmberjaotamise protseduur (remap) funktsiooni abil mdb_env_set_map_size makeb kĂ”ik entiteedid (kursused, tehingud, vĂ”tmed ja vÀÀrtused), mis on varem mootori poolt saadud. Sellise sĂŒndmuse arvestamine koodis muudab selle oluliselt keerulisemaks. Kui virtuaalne mĂ€lu on siiski teile vĂ€ga oluline, siis vĂ”ib see olla pĂ”hjus, miks tasub vaadata edasi arenenud forkide poole MDBX, kus ĂŒheks lubatud omaduseks on „automaatne andmebaasi suuruse kohandamine reaalajas“.

Teine parameeter, mille vaikeseade ei sobinud, reguleerib niiditurbemehhanismi. Kahjuks on vÀhemalt iOS 10-l probleeme niidilokaalse salvestamise toe osas. SeetÔttu avatakse eespool toodud nÀites salvestus lipuga MDB_NOTLS. Lisaks oli vajalik ka forkida C++ mÀhis lmdbxx, et eemaldada selle omadusega muutujad ka sellest.

Andmebaasid

Andmebaas on eraldi B-puu instants, millest rÀÀkisime varem. Selle avamine toimub tehingu sees, mis esialgu vÔib tunduda veidi kummaline.

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

TĂ”epoolest, LMDB-s toimub tehing mitte konkreetse andmebaasi, vaid andmehoidla kontekstis. See lĂ€henemisviis vĂ”imaldab teostada aatomite operatsioone erinevates andmebaasides olevate ĂŒksustega. Teoreetiliselt avab see vĂ”imaluse modelleerida tabeleid erinevate andmebaasidena, kuid ma valisin varem teistsuguse tee, mida kirjeldan allpool.

VÔtmed ja vÀÀrtused

Struktuur MDB_val mudel ei erista vÔtme ja vÀÀrtuse kontseptsiooni. Andmehoidla ei tea nende semantikast midagi. Nad on vaid mÀÀratletud suurusega baitide massiiv. Maksimaalne vÔtme suurus on 512 baiti.

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

Andmehoidla jĂ€rjestab vĂ”tmed kasvavas jĂ€rjekorras, kasutades vĂ”rdlejat. Kui pole mÀÀratud oma, kasutatakse vaikimisi, mis jĂ€rjestab need baitide kaupa leksikograafilises jĂ€rjekorras.​

Tehingud

Tehingute struktuur on detailne. Eelnevas peatĂŒkis, seetĂ”ttu kordaksin siin neid pĂ”hiomadusi lĂŒhidalt:

  1. Toetab kÔiki pÔhijooni ACID: aatomika, jÀrjepidevus, isoleeritus ja usaldusvÀÀrsus. Pean mÀrkima, et macOS-i ja iOS-i osas on durability puhul rike, mis on MDBX-is parandatud. TÀiendava teabe leiate nende README.
  2. Mitmeprotsessilisuse lĂ€henemine on kirjeldatud skeemiga Â«ĂŒks kirjanik / mitu lugejat». Kirjanikud blokivad ĂŒksteist, kuid mitte lugejaid. Lugejad ei blokeeri ei kirjanikke ega teisi lugejaid.
  3. Toetab sisemist tehingute korrutamist.
  4. Toetab mitme versiooni kasutamist.

Mitme versiooni tugi LMDB-s on nii hea, et soovin seda teostuses demonstreerida. Allpool olevast koodist on nÀha, et iga tehing töötab just selle andmebaasi versiooniga, mis oli avamise hetkel aktuaalne, olles tÀielikult isoleeritud kÔikidest jÀrgnevates muudatustest. Andmehoidla initsialiseerimine ja testkirje lisamine ei paku midagi huvitavat, seega on need rituaalid jÀetud spoilerisse.

Testkirje lisamine

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;

// Avame 2 tehingut, millest igaĂŒks vaatab
tutvustanud andmebaasi versiooni ĂŒhe kirje kohta.
mdb_txn_begin(env, NULL, 0, &txn1); // lugemiseks ja kirjutamiseks
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn2); // ainult lugemiseks

// Esimese tehingu raames eemaldame andmebaasist olemasoleva kirje.
mdb_del(txn1, dbi, &key, NULL);
// Kinnitage eemaldamine.
mdb_txn_commit(txn1);

// Avame kolmanda tehingu, mis vaatab
eaktsialiseeritud andmebaasi versiooni, kus kirje on juba kadunud.
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn3);
// Veendume, et otsitud vÔtme alusel kirje ei eksisteeri enam.
assert(mdb_get(txn3, dbi, &key, &val) == MDB_NOTFOUND);
// LÔpetame tehingu.
mdb_txn_abort(txn3);

// Veendume, et teise tehingu raames, mis oli avatud kirje olemasolu ajal,
// on selle ikka veel vÔimalik leida vÔtme alusel.
assert(mdb_get(txn2, dbi, &key, &val) == MDB_SUCCESS);
// Kontrollime, et vĂ”tme alusel on saadud mitte mingit prĂŒgi, vaid kehtivad andmed.
assert(*(int *)val.mv_data == 997);
// LÔpetame tehingu, mis töötas koos, kuigi aegunud, kuid jÀrjekindla andmebaasiga.
mdb_txn_abort(txn2);

Soovitan proovida sama trikki SQLite'iga ja vaadata, mis juhtub.

Mitmeversioonilisus toob iOS-arendaja ellu palju meeldivaid funktsioone. Selle omaduse abil on lihtne ja mugav reguleerida andmeallika (data source) vĂ€rskenduse kiirus ekraanivormide jaoks, lĂ€htudes kasutajakogemusest. NĂ€iteks vĂ”ivad Mail.ru pilve rakenduse funktsiooni alla laadida sisu sĂŒsteemi meediagalerii. Hea ĂŒhenduse korral suudab klient lisada serverisse mitu fotot sekundis. Kui pĂ€rast iga ĂŒleslaadimist ajak Aktualiseerida UICollectionView kasutaja pilves oleva meediakoguga, siis vĂ”ib 60 fps ja sujuva kerimise selle protsessi ajal unustada. Peale sagedaste ekraani vĂ€rskenduste ennetamiseks on vajalik mingil moel piirata andmete muutmise kiirus aluses UICollectionViewDataSource.

Kui andmebaas ei toeta multiversioonilisust ja vÔimaldab töötada ainult praeguse kehtiva olekuga, siis stabiilse ajas kinnitatud andmesnapshoti loomiseks tuleb see kopeerida kas mingisse in-memory andmestruktuuri vÔi ajutisse tabelisse. MÔlemad lÀhenemised on vÀga kulukad. In-memory ladustamise puhul toovad kaasa kulud nii mÀlus, mis on seotud konstrueritud objektide salvestamisega, kui ka ajaga, mis on seotud liialdustega ORM-i teisendustes. Mis puudutab ajutist tabelit, siis see on veelgi kallim lahendus, millel on mÔtet ainult keerukates juhtumites.

LMDB multiversioonilisus lahendab stabiilse andmeallika sĂ€ilitamise ĂŒlesande vĂ€ga elegantset. Piisab lihtsalt tehingu avamisest ja voilĂ  — seni, kuni me selle lĂ”petame, on andmehulk meil garanteeritult fikseeritud. Ajakohastamise kiirus on nĂŒĂŒd tĂ€ielikult ja tĂ€ielikult esituskihtide kĂ€tes, ilma oluliste ressursside kulu.

Kursorid

Kursorid pakuvad mehhanismi paari vÔtme-vÀÀrtuse jÀrkjÀrguliseks iteratsiooniks B-puu kaudu. Ilma nendeta oleks andmebaasi tabelite tÔhus modelleerimine enne seisukohta vÔimatu.

4.2. Tabelite modelleerimine

VÔtmete jÀrjekorrastatuse omadus vÔimaldab luua kÔrgema taseme konstruktsioone, nagu tabel, baaspÔhimÔtete peale. Vaatame seda protsessi pilku alla peamise tabeli nÀitel, kus on vahemÀlus kogu kasutaja failide ja kaustade teave.

Tabeli skeem

Üks levinumaid stsenaariume, mille jaoks peab kaustade puu struktuur olema kohandatud – on kĂ”ikide elementide valimine, mis asuvad antud kataloogis. Hea andmete organiseerimise mudel, mis sobib selliste tĂ”husate pĂ€ringute jaoks, on Adjacency List. Selle teostamiseks tuleb key-value salvestuses korraldada failide ja kaustade vĂ”tmed nii, et need grupeeritakse vastavalt vanemkausta kuuluvusele. Lisaks, et kuvada kausta sisu Windowsi kasutaja jaoks tuttaval kujul (esiteks kaustad, seejĂ€rel failid, ning mĂ”lemad sorteeritakse tĂ€hestiku jĂ€rjekorras), tuleb vĂ”tmesse lisada vastavad tĂ€iendavad vĂ€ljad.

Alloleval pildil on nĂ€idatud, kuidas ĂŒlesande pĂ”hjal vĂ”iks vĂ”tmete esitus vĂ€lja nĂ€ha baitide massiivina. Alustatakse, paigutades baitidega vanemkausta identifikaatori (punased), seejĂ€rel – tĂŒĂŒbiga (rohelised) ja lĂ”puks – nimega (sinised). Kui need on LMDB vaikimisi vĂ”rdlejaga leksikograafilises jĂ€rjekorras sorteeritud, siis korraldatakse need nĂ”utud viisil. JĂ€rjestikune vĂ”tmete lĂ€bimine sama punase prefiksiga annab meile nendega seotud vÀÀrtused sellises jĂ€rjestuses, nagu need peavad olema esitatud kasutajaliideses (paremal), ilma et oleks vaja mingit tĂ€iendavat jĂ€reltöötlemist.

LMDB and its pros and cons in iOS applications

VÔtmete ja vÀÀrtuste serialiseerimine

Maailmas on vĂ€lja mĂ”eldud palju meetodeid objektide serialiseerimiseks. Kuna meie ainus nĂ”ue oli kiirus, valisime enda jaoks kĂ”ige kiirema vĂ”imaliku — C keele struktuuri instantsi mĂ€ludumpi. Nii saab kataloogelementi nĂ€idata jĂ€rgmise struktuuriga. NodeKey.

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

SĂ€ilitamiseks NodeKey salvestusruumis tuleb objektis MDB_val andmete aadress suunata struktuuri algusesse ja nende suurus arvutada funktsiooniga sizeof.

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

Esimeses peatĂŒkis andmebaasi valimise kriteeriumide kohta mainisin olulise tegurina dĂŒnaamiliste allokatsioonide minimeerimist CRUD operatsioonide raames. Funktsiooni kood serialize nĂ€itab, kuidas LMDB puhul on vĂ”imalik uute kirjeid andmebaasi lisamisel tĂ€ielikult vĂ€ltida. Serverilt saabunud baitide massiiv transformeeritakse esmalt virnstruktuurideks ning seejĂ€rel talletatakse need triviaalsetena ladustamisse. Arvestades, et LMDB-s ei ole ka dĂŒnaamilisi allokatsioone, vĂ”ib saada fantastilise olukorra iOS-i mÔÔtmete jĂ€rgi — töötada andmetega kogu nende teekonnal veebist kettale ainult virna mĂ€lu!

vÔtmete jÀrjestamine binaarse vÔrreldava meetodiga

VÔtmete jÀrjestusesuhte mÀÀrab spetsiaalne funktsioon, mida nimetatakse vÔrreldavaks. Kuna mootor ei tea midagi nende sees olevate baitide semantika kohta, ei jÀÀ vaikimisi vÔrreldavale midagi muud, kui sorteerida vÔtmed leksikograafilises jÀrjekorras, tuginedes nende baitide vÔrdlemisele. Selle kasutamine struktuuride jÀrjestamiseks on sarnane lÔhe pööramisega. Siiski, lihtsate juhtumite puhul pean ma seda meetodit vastuvÔetavaks. Alternatiiv on kirjeldatud allpool, ja siin toon vÀlja paar komistamist antud teel.

Esimene asi, mida meeles pidada, on primitiivsete andmastatĂŒĂŒpide esitus mĂ€lus. Nii et kĂ”igil Apple'i seadmetel hoitakse tĂ€isarvulisi muutujaid formaadis Little Endian. See tĂ€hendab, et vĂ€hem oluline bait asub vasakul ning tĂ€isarve ei saa sorteerida nende baitide pĂ”hjaliste vĂ”rdlemisega. NĂ€iteks katse teha seda numbrite komplektiga vahemikus 0 kuni 511 annab jĂ€rgmise tulemuse.

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

Selle probleemi lahendamiseks peavad tÀisarvud olema salvestatud vÔtmesse sobivas formaadis baitide vÔrdlejale. Vajalikku teisendust aitavad tÀita funktsioonid perekonnast hton* (eriti htons nÀite kahe baitide numbrite jaoks).

Reastamine mĂ€rkide esitamiseks programmeerimises on, nagu teatakse, terviklik lugu. Kui stringide semantika ja mĂ€lus nende esitamiseks kasutatav kodeering eeldab, et iga sĂŒmbol vĂ”ib sisaldada rohkem kui ĂŒhte bait, siis on mĂ”istlik loobuda defolti vĂ”rdlejast.

Teine asi, mida meeles pidada — ĂŒhtlustamise pĂ”himĂ”tted struktuuri vĂ€ljade kompilaatorist. Nende tĂ”ttu vĂ”ivad mĂ€lus vĂ€ljade vahel tekkida tĂŒhjade vÀÀrtustega baitide korraldus, mis muidugi rikub baitide kaupa sorteerimist. PrĂŒgi kĂ”rvaldamiseks tuleb kas deklareerida vĂ€ljad rangelt kindlaksmÀÀratud jĂ€rjekorras, jĂ€rgides joondamise reegleid, vĂ”i kasutada struktuuri deklareerimisel atribuuti packed.

VÔtmete jÀrjestamine vÀlist poolverifikaatori abil

VĂ”tmete vĂ”rdlemise loogika vĂ”ib osutuda liiga keeruliseks binaarsete vĂ”rdlejate jaoks. Üks paljusid pĂ”hjusi on tehnikavĂ€ljade olemasolu struktuuride sees. Illustreerin nende tekkimist tuttava direktori elemendi vĂ”tme nĂ€itel.

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

Hoolimata oma lihtsusest tarbib see enamikul juhtudel liiga palju mĂ€lu. Nimepuhver vĂ”tab 256 baiti, kuigi keskmiselt ei ĂŒleta failide ja kaustade nimed harva 20-30 tĂ€hemĂ€rki.

Üks standardmeetod kirje suuruse optimeerimiseks on selle 'lĂ”ikamine' vastavalt tegelikule suurusele. Selle pĂ”hiolemus seisneb selles, et kĂ”ik muutuva pikkusega vĂ€ljade sisu salvestatakse struktuuri lĂ”pus asuvasse puhverdatud mĂ€lu, samas kui nende pikkused salvestatakse eraldi muutujatesse. Selle lĂ€henemise kohaselt vĂ”ti NodeKey muudetakse jĂ€rgmiselt.

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

JÀtkates, serialiseerimisel mÀÀratakse andmete suuruseks mitte sizeof kogu struktuuri suurus, vaid kÔikide fikseeritud pikkusega vÀljade suurus pluss reaalselt kasutatava osa puhvri suurus.

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

Tehtud refaktoreerimise tulemusena saime mÀrgatava kokkuhoiu vÔtme ruumipaigutusest. Kuid tehnilise vÀljaga nameLength, ei sobi vaikimisi binaarne vÔrdleja enam vÔtmete vÔrdlemiseks. Kui me ei asenda seda oma funktsiooniga, on nime pikkus prioriteediks sorteerimisel rohkem kui nimi ise.

LMDB vÔimaldab igale andmebaasile mÀÀrata oma vÔtmete vÔrdlemise funktsiooni. See toimub funktsiooni abil mdb_set_compare tÀpselt enne avamist. Ilmselgetel pÔhjustel ei saa andmebaasi kogu elu jooksul muuta. Komparaator saab sisendiks kaks binaarfaili vÔtit ning vÀljastab vÔrdlustulemuse: vÀiksem (-1), suurem (1) vÔi vÔrdsed (0). Pseudokood nÀeb vÀlja jÀrgnev: NodeKey nÀgema nii.

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

Nii kaua, kui andmebaasis on kĂ”ik vĂ”tmed ĂŒhte tĂŒĂŒpi, on nende baitide esitluse sundimine rakendusstruktuuri vĂ”tme tĂŒĂŒbiks seaduslik. Siin on ĂŒks nĂŒanss, kuid sellest rÀÀgitakse allpool jaotises „Kirjete lugemine“.

VÀÀrtuste serialiseerimine

LMDB salvestusvÔtmete vÔrdlemine toimub vÀga intensiivselt. Nende omavaheline vÔrdlemine toimub igas rakendustegevuses ja kogu lahenduse jÔudlus sÔltub vÔrdleja kiirusest. Ideaalsetes tingimustes peaks vaikimisi binaarne vÔrdleja olema piisav, kuid kui tuleb kasutada oma lahendust, peaks vÔtmete deserialiseerimise protseduur olema vÔimalikult kiire.

Andmebaas ei huvita eriti vÀÀrtuse osa (value). Selle muutmine baitide esituses objektiks toimub ainult siis, kui rakenduskood seda vajab, nÀiteks ekraanil kuvamiseks. Kuna see juhtub suhteliselt harva, siis pole sellele protseduurile kiirusenÔuded nii kriitilised ja selle rakenduses saame palju vabamalt orienteeruda mugavusele. NÀiteks nende metaandmete serialiseerimiseks, mis puudutavad veel mittelaaditud faile, kasutame NSKeyedArchiver.

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

Siiski on olukordi, kus jĂ”udlus on endiselt tĂ€htis. NĂ€iteks, et sĂ€ilitada meta teavet kasutaja pilvefailide struktuuri kohta, kasutame sama objekti mĂ€ludumpi. Üks vĂ€ljakutse nende serialiseeritud esitluse genereerimisel on see, et kaustade elemendid mudeldatakse klasside hierarhiaga.

LMDB and its pros and cons in iOS applications

Selle rakendamiseks C keeles tĂ”stetakse spetsiifilised laste vĂ€ljad eraldi struktuuridesse ja nende seos pĂ”hiklassiga mÀÀratakse union tĂŒĂŒbi kaudu. Aktiivne sisu ĂŒhenduses mÀÀratakse tehnilise atribuudi type kaudu.

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

Kirjete lisamine ja uuendamine

Serialiseeritud vÔti ja vÀÀrtus saab lisada salvestusse. Selleks kasutatakse funktsiooni mdb_put.

// key Đž value ĐžĐŒĐ”ŃŽŃ‚ топ MDB_val​
mdb_put(..., &key, &value, MDB_NOOVERWRITE);

Konfiguratsiooni etapis on vĂ”imalik salvestamisele lubada vĂ”i keelata mitu kirje, millel on sama vĂ”ti. Kui vĂ”tmete dubleerimine on keelatud, siis kirje sisestamisel saab mÀÀrata, kas olemasolevat kirjet vĂ”ib uuendada vĂ”i mitte. Kui ĂŒlekatte tegemine vĂ”ib toimuda ainult koodi vea tĂ”ttu, siis saab selle eest kaitsta, mĂ€rkides lipu NOOVERWRITE.

Kirjade lugemine

Kirjade lugemiseks on LMDB-s ette nÀhtud funktsioon mdb_get. Kui vÔtme-vÀÀrtuse paar on varem salvestatud struktuurides, siis nÀeb see protseduur vÀlja jÀrgmiselt.

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

Esitatud loend nĂ€itab, kuidas struktuuride kaudu serialiseerimine vĂ”imaldab vabaneda dĂŒnaamilistest jaotustest mitte ainult kirjutamisel, vaid ka andmete lugemisel. Funktsioonist saadud mdb_get nĂ€idik viib otse sinna, kus andmebaas hoiab objekti baitide esindust virtuaalses mĂ€lus. Tegelikult on meil olemas peaaegu tasuta ORM, mis tagab vĂ€ga kĂ”rge andme lugemise kiiruse. Kuigi lĂ€henemine on ilus, tuleb meeles pidada mitmeid kaasnevaid eripĂ€rasid.

  1. Readonly tehingu puhul jÀÀb struktuuri- ja vÀÀrtuse nĂ€idik kehtivaks vaid kuni tehingu sulgemiseni. Nagu varasemalt mainitud, jÀÀvad B-puu lehed, kus objekt asub, copy-on-write pĂ”himĂ”tte tĂ”ttu muutumatuks, kuni neid viidatakse vĂ€hemalt ĂŒhe tehingu kaudu. Samas, niipea kui viimane seotud tehing lĂ”peb, vĂ”ivad lehed saada uute andmete jaoks uuesti kasutatud. Kui on vajalik, et objektid ĂŒle elaksid neid loonud tehingu, tuleb need siiski kopeerida.
  2. Readwrite tehingu puhul jÀÀb struktuuri- ja vÀÀrtuse nÀidik kehtivaks vaid esimesse muudatuste proseduurini (andmete salvestamine vÔi kustutamine).
  3. Kuigi struktuur NodeValue see also section «Order of keys with an external comparator», it can be accessed calmly through a pointer to its fields. Just be careful not to dereference it!
  4. Under no circumstances should the structure be modified through the obtained pointer. All changes must be made only through the method mdb_put. However, no matter how much you want to do this, it won't work, as the memory area where this structure is located is mapped in readonly mode.
  5. Remapping the file into the process's address space to, for example, increase the maximum storage size using the function mdb_env_set_map_size completely invalidates all transactions and related entities in general and pointers to read objects in particular.

Finally, there's another feature so insidious that revealing its essence simply cannot fit into another point. In the chapter about B-trees, I provided a diagram of its pages in memory. It indicates that the address of the beginning of the buffer with serialized data can be completely arbitrary. Because of this, the pointer to them obtained in the structure MDB_val ja ja viidatud struktuuri osas ei ole see ĂŒldiselt joondatud. Samas nĂ”uavad teatud kiibi arhitektuurid (iOS-i puhul on see armv7), et iga andmeaadress oleks masinasĂ”na suuruse korrelatsioonis, ehk sĂŒsteemi bitisusega (armv7 puhul on see 32 bitti). TeisisĂ”nu, selline operatsioon nagu *(int *foo)0x800002 neile on vĂ”rdne pĂ”genemisega ja viib hukkamiseni otsusega EXC_ARM_DA_ALIGN. Sellisest kurvast saatusest pÀÀsemiseks on kaks vĂ”imalust.

Esimene viis on eelnevalt andmete kopeerimine eelnevalt joondatud struktuuri. NÀiteks peegeldub see jÀrgmises kohandatud vÔrdlejas.

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

Alternatiivne tee on eelnevalt teavitada kompilaatorit, et vÔtme ja vÀÀrtuse struktuurid vÔivad olla joondamata, kasutades atribuuti aligned(1). ARM-i puhul on sarnase efekti saavutamine vÔimalik saavutada ka packed atribuudi abil. Arvestades, et see toetab ka struktuuri kasutatava ruumi optimeerimist, paistab see viis mulle eelistatavam, kuigi see toob kaasa andmete juurdepÀÀsu operatsioonide kallinemine.

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

Vahemiku pÀringud

LMDB-s on mÀÀratud kursori abstraktsioon kirjekogumi itereerimiseks. Kuidas sellega töötada, kÀsitleme juba tuttava kasutaja pilve metaandmete tabeli nÀitel.

Failide loendi kuvamise raames tuleb leida kĂ”ik vĂ”tmed, millega on seotud tema alamfailid ja kaustad. Eelnevates alakapitlites oleme jĂ€rjestanud vĂ”tmed NodeKey selliselt, et need oleksid kĂ”igepealt jĂ€rjestatud vanema kausta ID jĂ€rgi. Seega tehniliselt on kausta sisu saamise ĂŒlesanne kursori seadmine soovitud prefiksiga vĂ”tmete rĂŒhma ĂŒlemisele piirile ja seejĂ€rel itereerimine alumise piirini.

LMDB and its pros and cons in iOS applications

Ülemise piiri leidmine on vĂ”imalik "otse" jĂ€rjestikuse otsimisega. Selleks seadistatakse kursori algus kogu andmebaasi vĂ”tmete loendis ja seejĂ€rel suurendatakse seda, kuni selle all on vĂ”tme ID vanema kausta numbriga. Selle lĂ€henemise kaks ilmsed puudust on:

  1. Otsingu lineaarne keerukus, kuigi on teada, et puudes ja eriti B-puudes saab seda teostada logaritmilise ajaga.
  2. MÔttetult laaditakse kÔik otsitavale eelnevad lehed failist peamÀllu, mis on ÀÀrmiselt kallis.

Õnneks on API LMDB-s ette nĂ€htud tĂ”hus viis kursori algse positsioneeringu jaoks. Selleks tuleb luua selline vĂ”ti, mille vÀÀrtus on ette teada, et olla vĂ€iksem vĂ”i vĂ”rdne ĂŒlemise piiri vĂ”tmega. NĂ€iteks kasutades ĂŒlemisel joonisel olevat loendit, saame teha sellise vĂ”tme, kus vĂ€li parentId on 2, ja kĂ”ik teised on tĂ€idetud nullidega. Selline osaliselt tĂ€idetud vĂ”ti antakse funktsiooni mdb_cursor_get kutses, koos nĂ€idatud operatsiooniga 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);

Kui vĂ”tmete ĂŒlemine piir on leitud, siis jĂ€tkame selle kaudu iteratsiooni, kuni kas seisame silmitsi teise vĂ”tmega parentId, vĂ”i vĂ”tmed ei lĂ”ppe tĂ€ielikult.

do {​
    rc = mdb_cursor_get(cursor, &key, &value, MDB_NEXT);​
    // töötlemine...​
} while (MDB_NOTFOUND != rc && // kontrolli tabeli lĂ”ppu​
         IsTargetKey(key));    // kontrolli vĂ”tme rĂŒhma lĂ”ppu​​

Hea uudis on see, et mdb_cursor_get iitriitimise kÀigus saame mitte ainult vÔtme, vaid ka vÀÀrtuse. Kui valikutingimuste tÀitmiseks on vajalik kontrollida ka запОсО value-osast, on need tÀiesti kergesti kÀtte saadavad ilma tÀiendavate pingutusteta.

4.3. Suhete modelleerimine tabelite vahel

Oleme selles etapis kĂ€sitlenud kĂ”iki tahveldatud andmebaasi projekteerimise ja töötamise aspekte. VĂ”ib öelda, et tabel on sorteeritud rekordite kogum, mis koosneb sarnastest vĂ”tme-vÀÀrtuse paaridest. Kui kuvada vĂ”ti ristkĂŒlikuna ja selle seotud vÀÀrtus parallelepipedina, saame visuaalse andmebaasi skeemi.

​

LMDB and its pros and cons in iOS applications

Kuid reaalses elus on harva vĂ”imalik nii vĂ€hese vaevaga lĂ€bi saada. Sageli on andmebaasis vaja, esiteks, mitut tabelit ja teiseks, teha valikuid jĂ€rjestuses, mis erineb esmase vĂ”tme omast. Selle viimane jaotis on pĂŒhendatud nende loomisele ja omavahel sidumisele.

Indekseerimistabelid

Pilve rakenduses on jaotis „Galerii“. Seal kuvatakse kogu pilve meedia sisu, sorteeritud kuupĂ€eva jĂ€rgi. Sellise valimise optimaalseks rakendamiseks tuleb pĂ”hitaabeli kĂ”rvale luua veel ĂŒks uus vĂ”tmetĂŒĂŒp. Neis sisaldub faili loomise kuupĂ€ev, mis töötab esmase sorteerimiskriteeriumina. Kuna uued vĂ”tmed viitavad samadele andmetele nagu pĂ”hitaabeli vĂ”tmed, nimetatakse neid indeksvĂ”tmeteks. Alloleval pildil on need esile tĂ”stetud oranĆŸiga.

LMDB and its pros and cons in iOS applications

Et eristada erinevate tabelite vĂ”tmeid ĂŒhes andmebaasis, on igale lisatud tĂ€iendav tehniline vĂ€li tableId. Kui ta teha sorteerimise kĂ”ige prioriteetsemaks, saame vĂ”tmed kĂ”igepealt rĂŒhmitada tabelite kaupa ja juba tabelite sees oma reeglite kohaselt.

IndeksvĂ”ti viitab samadele andmetele nagu esmane. Selle omaduse otsekohene rakendamine, seostades selle esmase vĂ”tme value-osa koopiaga, ei ole mitmest aspektist vaadates ĂŒldse optimaalne:

  1. Ahnake ruumiga, kuna metadata vĂ”ib olla ĂŒsna rikkalik.
  2. Töötamise seisukohalt, kuna sÔlmede metaandmete uuendamisel tuleb kirjutada kahe vÔtme kaudu.
  3. Koodi toe seisukohalt, sest kui unustame andmete vĂ€rskendamise ĂŒhe vĂ”tme osas, vĂ”ib tekkida keeruline andmete jĂ€rjepidevuse viga salvestuses.

Vaadakem edasi, kuidas neid puudusi kÔrvaldada.

Tabelitevaheliste suhete korraldamine

Indekseeritud tabeli ja pĂ”hietabeli seostamiseks sobib hĂ€sti muster „vĂ”ti vÀÀrtusena“. Nagu nimigi ĂŒtleb, on indeksikirje vÀÀrtusosana primaarse vĂ”tme koopia. See lĂ€henemine kĂ”rvaldab kĂ”ik eelnevalt loetletud puudused, mis on seotud primaarsete kirjade vÀÀrtuse koopia salvestamisega. Ainsaks puuduseks on see, et indekseeritud vĂ”tme vÀÀrtuse saamiseks on vaja teha kaks pĂ€ringut andmebaasi ĂŒhe asemel. Skeemiliselt nĂ€eb saadud andmebaasi skeem vĂ€lja jĂ€rgmine.

LMDB and its pros and cons in iOS applications

Teine muster tabelitevahelise seose korraldamiseks on â€žĂŒlemÀÀrane vĂ”ti“. Selle eesmĂ€rk on lisada vĂ”tmesse tĂ€iendavaid atribuute, mis ei ole vajalikud sortimiseks, vaid seotud vĂ”tme rekonstrueerimiseks. Mail.ru pilves rakenduses on selle kasutamise kohta reaalsed nĂ€ited, kuid sĂŒgava sukeldumise vĂ€ltimiseks spetsiifiliste iOS-raamistike konteksti toome vĂ€lja vĂ€ljamĂ”eldud, kuid seetĂ”ttu arusaadavama nĂ€ite.

Pilves mobiilirakendustes on leht, kus kuvatakse kĂ”ik failid ja kaustad, millele kasutaja on teistele inimestele ligipÀÀsu andnud. Kuna selliseid faile on ĂŒsna vĂ€he, ja seotud teabe osas, mis puudutab avalikkust (kellele on ligipÀÀs antud, milliste Ă”igustega jne), on palju, pole mĂ”istlik peamise tabeli vÀÀrtuste osa selle teabega ĂŒle koormata. Kuid kui soovitakse neid faile ilma internetita kuvada, siis tuleb neid siiski kusagile salvestada. Loomulik lahendus on selle jaoks eraldi tabeli loomine. Alloleval skeemil on selle vĂ”tmel eelprefiks «P», ning kohatĂ€idet «propname» saab asendada konkreetsema vÀÀrtusega «avalik teave».

LMDB and its pros and cons in iOS applications

KĂ”ik unikaalsed metaandmed, mille jaoks uus tabel loodi, kantakse kirje value-osasse. Samas ei soovi me duplikeerida neid faili- ja kaustandmeid, mis juba peamises tabelis on. Selle asemel lisatakse vĂ”tmesse „P“ liigselt andmeid vÀÀrtuste „node ID“ ja „timestamp“ nĂ€ol. Nende abil on vĂ”imalik konstrueerida indeksivĂ”ti, mille alusel saada esmavĂ”ti ja lĂ”puks saada nodi metaandmed.

KokkuvĂ”te​

Hindame LMDB rakendamise tulemusi positiivselt. PÀrast seda vÀhenes rakenduse peatuste arv 30%.

LMDB and its pros and cons in iOS applications

Tehtud töö tulemused on leidnud vastukaja ka vĂ€ljaspool iOS-i meeskonda. Praeguseks on ĂŒks peamisi jaotisi „Failid“ Androidi rakenduses samuti ĂŒle lĂ€inud LMDB kasutamisele ning muud osad on tulekul. C-keel, milles key-value andmehoidjat rakendati, osutus heaks abiks, et algselt luua selle ĂŒmber rakenduslik kattekiht platvormideĂŒleselt C++ keeles. Saadud C++ raamatukogude sujuvaks ĂŒhendamiseks platvormikoodiga Objective-C ja Kotlinis kasutati koodigeneraatorit. Djinni Dropboxilt, kuid see on juba hoopis teine lugu.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster