
2019. aasta sĂŒgisel toimus Mail.ru pilveteenuste iOS meeskonnas kauaoodatud sĂŒndmus. Peamiseks andmebaasiks rakenduse oleku pĂŒsivaks salvestamiseks sai ĂŒsna eksootiline lahendus mobiilimaailmas. (LMDB). Allpool on teie tĂ€helepanu vÀÀriv detailne ĂŒlevaade neljas osas. Esiteks arutame, miks selline ebatavaline ja keeruline valik tehti. SeejĂ€rel liigume edasi LMDB arhitektuuri kolme pĂ”hikonkreetse asja juurde: mĂ€llu kaardistatud failid, B+-puu, copy-on-write lĂ€henemine tehingute ja mitme versiooni rakendamiseks. LĂ”puks, magus osa â praktiline osa. Selles vaatleme, kuidas madalamale tasemele vastava key-value API pĂ”hjal projekteerida ja ellu viia andmeskeem mitme tabeliga, sealhulgas indeksi.
Sisukord
3.1.
3.2.
3.3.
4.1.
4.2.
4.3.
1. Rakendamise motivatsioon
Kord umbes 2015. aastal tekkis meil mure, et meie rakenduse liides laguneb liiga sageli. Me ei teinud seda lihtsalt niisama. Me saime rohkem kaebusi selle ĂŒle, et rakendus mĂ”nikord ei reageeri kasutaja toimingutele: nupud ei tööta, nimekirjad ei kerita jne. Kui palju selliseid mÔÔtmisi tehti, on AvitoTechis, seega toon siin esile vaid numbreid.

MÔÔtmise tulemused olid meie jaoks kĂŒlm duĆĄĆĄ. Tuli vĂ€lja, et kĂŒlmusest tingitud probleeme on palju rohkem kui mingeid teisi. Kui enne selle fakti teadvustamist oli peamine tehniline kvaliteedi nĂ€itaja crash free, siis pĂ€rast fookus freeze free-ile.
Kasutades ja tehes ja analĂŒĂŒsi, sai selgeks, kes on peamine vaenlane â raske Ă€ri loogika, mis töötas rakenduse pĂ”hijoonel. Loomulik reaktsioon sellele segadusele oli tungiv soov see Ă€ra jagada töövoogudesse. Sellise probleemi sĂŒsteemseks lahendamiseks kasutasime kergekaaluliste nĂ€itlejate pĂ”hjal mitme lĂ”ime arhitektuuri. Selle rakendust iOS maailmas ma pĂŒhendasin kollektiivses Twitteris ja . Praeguses jutustuses soovin rĂ”hutada neid lahenduse aspekte, mis mĂ”jutasid andmebaasi valikut.
Aktaormodeli sĂŒsteemi korralduses eeldab, et mitme lĂ”ime kasutamine saab selle teiseks olemuseks. Mudeli objektid armastavad ĂŒletada lĂ”imede piire. Ja nad teevad seda mitte aeg-ajalt ja kuskil, vaid peaaegu pidevalt ja igal pool.

Andmebaas on ĂŒks peamisi komponente esitatud skeemis. Selle peamine ĂŒlesanne on makropatterni rakendamine . Kui ettevĂ”tlusmaailmas korraldatakse selle abiga andmete sĂŒnkroniseerimist teenuste vahel, siis aktorite arhitektuuri puhul â lĂ”imede vahel. SeetĂ”ttu vajasime sellist andmebaasi, mille kasutamine mitme lĂ”ime keskkonnas ei tekitaks isegi minimaalset keerukust. EelkĂ”ige tĂ€hendab see, et saadud objektid peavad olema vĂ€hemalt lĂ”imede ohutud ja ideaalis tĂ€iesti muutumatud. Nagu teada, saab viimaseid kasutada samaaegselt mitmest lĂ”imest, vajamata mitte mingeid lukustusi, mis avaldab soodsat mĂ”ju jĂ”udlusele.
Teine oluline tegur, mis mĂ”jutas andmebaasi valikut, oli meie pilve API. See sai inspiratsiooni sĂŒnkroniseerimise lĂ€henemisviisist, mida rakendati git'is. Nagu tema, sihime , mis pilveklientide jaoks tundub rohkem kui sobiv. Eeldati, et nad laevad ainult kord tĂ€isolekusse pilve ja seejĂ€rel toimub sĂŒnkroniseerimine enamikus juhtudest muudatuste rakendamise kaudu. Kahjuks on see vĂ”imalus endiselt alles teoreetilises piirkonnas, ja praktikas ei ole kliendid patch'idega töötamist veel Ă”ppinud. Sellel on mitmeid objektiivseid pĂ”hjusi, mille me, et mitte viivitada sissejuhatusega, jĂ€tame kĂ”rvale. Praegu on palju huvitavamad Ă”petlikud jĂ€reldused Ă”ppetundi kohta, mis toimub, kui API ĂŒtles "A", ja selle tarbija ei öelnud "B".
Nii et, kui te kujutate ette git'i, mis kĂ€su pull tĂ€itmisel rakendab plaastrite asemel tĂ€ieliku seisundi vĂ”rdlemist kohaliku ja serveri vahel, siis on teil piisavalt tĂ€pne arusaam sellest, kuidas toimub sĂŒnkroonimine pilveteenustes. Ei ole raske arvata, et selleks, et see toimuks, tuleb mĂ€lu allokeerida kahe DOM-puust, mis sisaldavad metaandmeid kĂ”ikide serveri ja kohalike failide kohta. Tulemuseks on see, et kui kasutaja hoiab pilves 500 000 faili, tuleb sĂŒnkroonimiseks taastada ja hĂ€vitada kaks puud, milles on 1 miljon sĂ”lme. Iga sĂ”lm on seotud objektrĂŒhmaga. Selle valguses olid profiliseerimise tulemused ootuspĂ€rased. Selgus, et isegi ilma sulandumise algoritmi arvestamata on juba tohutu hulga vĂ€ikeste objektide loomine ja edasine hĂ€vitamine kulukas. Olukorda halvendab asjaolu, et pĂ”hisĂŒnkroonimise operatsioon on paljude kasutajaskenaaride osa. Tulemuseks fikseerime teise olulise kriteeriumi andmebaasi valikul â CRUD-operatsioonide teostamise vĂ”imaluse ilma dĂŒnaamilise objekti allokatsioonita.
Teised nÔuded on traditsioonilisemad ja nende loetelu on jÀrgmine.
- Konteinerite ohutus.
- Mugav mitme protsessi kasutamine. See on tingitud soovist kasutada sama andmebaasi instantsi staatuse sĂŒnkroonimiseks mitte ainult protsesside vahel, vaid ka peamise rakenduse ja iOS laienemiste vahel.
- VÔimalus esitada salvestatud entiteedid kui muutumatuid objekte.
- CRUD-operatsioonide raames puudub dĂŒnaamiline allokatsioon.
- Toetatakse tehingute pÔhiomadusi : aatomilisus, jÀrjepidevus, isoleeritus ja usaldusvÀÀrsus.
- Kiirus kÔige populaarsemates kasutusjuhtudes.
Hea valik selliste nĂ”udmistega oli ja jÀÀb SQLite. Olles uurinud alternatiive, sattusin ma raamatule . Tema juhtimisel kirjutati benchmark, mis vĂ”rdles erinevate andmebaaside töösuunatavuse kiirusetulemusi reaalses pilveskenaaris. Tulemused ĂŒletasid isegi kĂ”ige julgemad ootused. KĂ”ige populaarsemates juhtumites - kursori saamine sorteeritud nimekirjas kĂ”igist failidest ja sorteeritud nimekirjas kĂ”ikidest failidest antud kataloogis - osutus LMDB 10 korda kiiremaks kui SQLite. Valik osutus ilmseks.

2. LMDB positsioneerimine
LMDB on vÀike (ainult 10K rida) raamatukogu, mis rakendab andmebaaside kÔige madalamat aluskihi - salvestust.

Esitatud skeem nĂ€itab, et LMDB vĂ”rdlemine SQLite-ga, mis rakendab ka kĂ”rgemaid tasemeid, ei ole eriti korrektne, vĂ”rreldes SQLite-d Core Data-ga. Ăks ĂŒhtekuuluvate konkurentide toommine oleks Ă”iglasem vĂ”rrelda selliste salvestusmootoritega nagu BerkeleyDB, LevelDB, Sophia, RocksDB jne. On isegi arendusi, kus LMDB toimib SQLite storage engine'i komponendina. Esimese sellise eksperimentaalse katse viis 2012. aastal lĂ€bi Howard Chu . LumoSQL selle LinuxConfAu. LMDB-d kasutatakse peamiselt rakenduslike andmebaaside mootori rollis. Selle raamatukogu ilmumine on seotud arendajatega
, kes ei olnud BerkeleyDB-st oma projekti aluseks eriti rahul. Alustades tagasihoidlikust raamatukogust btree «The Lightning Memory-mapped Database» yleo «LMDB mootori â eriline meister» MDBX bugide parandamisega. .
selle mitmete vajaduste jaoks, ja alates 9. versioonist eelistas Xcode Mootor on nĂ€htavale tulnud ka mobiiliarenduse maailmas. Selle kasutamise jĂ€lgi on vĂ”imalik leida. DĆŸĂnd.
DĆŸĂnd. iOS klient Telegramile. LinkedIn lĂ€ks veelgi kaugemale, valides vaikimisi andmete vahemĂ€luks LMDB, mis on kodumaine andmete vahemĂ€lu raamistik Rocket Data, millest rÀÀgiti tema artiklis 2016. aastal.
LMDB vĂ”itleb edukalt oma koha eest pĂ€ikese all, olles jĂ€tnud BerkeleyDB, mille Oracle on kontrolli alla vĂ”tnud. Raamatukogu on tuntud oma kiirus ja usaldusvÀÀrsus vĂ”rreldes teistega. Nagu teada, tasuta lĂ”unaid ei ole, ja tahaksin rĂ”hutada trade-off'i, millega tuleb silmitsi seista, valides LMDB ja SQLite vahel. Ălaltoodud skeem nĂ€itab selgelt, kuidas saavutatakse kĂ”rgem kiirus. Esiteks, me ei maksa lisakihi abstraktsioonide eest, mis katab ketta salvestamist. Loomulikult ei saa head arhitektuur ilma nendeta hakkama, ja need ilmuvad kindlasti rakenduse koodis, kuid need on palju Ă”hemad. Neil ei ole funktsioone, mis ei ole konkreetse rakenduse jaoks vajalikud, nĂ€iteks SQL-i pĂ€ringute tugi. Teiseks, tekib vĂ”imalus rakendada optimaalselt rakenduslike operatsioonide kaardistamine kettasalvestuse pĂ€ringutele. Kui SQLite toetub keskmiste rakenduste keskmistele vajadustele, siis te, kui rakenduse arendaja, olete hĂ€sti kursis peamiste koormusstsenaariumitega. TĂ”husama lahenduse eest tuleb maksta kĂ”rgemat hinda nii algse lahenduse vĂ€ljatöötamisel kui ka selle edasises toetuses.
3. Kolm LMDB alustala
Vaadates LMDB-d linnulennult, on aeg sĂŒgavamale laskuda. JĂ€rgmised kolm peatĂŒkki keskenduvad peamistele alustaladele, millest salvestus arhitektuur koosneb:
- MĂ€lu kaardistatud failid kui ketta töötlemise ja sisemiste andmestruktuuride sĂŒnkroniseerimise mehhanism.
- B+-puu andmete salvestamise struktuuri organiseerimise meetodina.
- Copy-on-write lÀhenemisviisina ACID-omaduste ja mitme versiooni tagamise jaoks.
3.1. Alustala â1. MĂ€lu kaardistatud failid
MĂ€lule salvestatud failid on nii olulised arhitektuuri element, et need on isegi salvestuse nime osaks. VahemĂ€lu ja salvestatud teabele juurdepÀÀsu sĂŒnkroniseerimise kĂŒsimused on tĂ€ielikult usaldatud operatsioonisĂŒsteemile. LMDB ei sisalda endas mingeid vahemĂ€lu. See on autori teadlik otsus, kuna andmete lugemine otse mĂ€lust vĂ”imaldab rakenduse töötlemisel mitmeid nurki kĂ€rpida. Allpool toome vĂ€lja vaid osalise loendi mĂ”nest neist.
- Andmete jĂ€rjepidevuse sĂ€ilitamine salvestuses, kui sellega tegeleb mitu protsessi, muutub operatsioonisĂŒsteemi kohustuseks. JĂ€rgmises jaotises kĂ€sitletakse seda mehhanismi ĂŒksikasjalikult ja koos piltidega.
- VahemĂ€lu puudumine vabastab LMDB tĂ€ielikult dĂŒnaamiliste jaotuste seotud kuludest. Andmete lugemine on praktikas lihtsalt Ă”igesse virtuaalse mĂ€lu aadressi seadmine ja mitte midagi enamat. See kĂ”lab nagu ulme, kuid salvestuse lĂ€htekoodis on kĂ”ik salloc'i kutsed koondatud salvestuse konfigureerimise funktsiooni.
- VahemĂ€lu puudumine tĂ€hendab ka lukustuste puudumist, mis on seotud nende juurde pÀÀsemise sĂŒnkroniseerimisega. Lugusid, keda vĂ”ib samal ajal eksisteerida piiramatult, ei kohtu oma teel andmetele ĂŒhegi mutex-iga. Selle tĂ”ttu on lugemise kiirus ideaalne lineaarne skaleeritavus CPU arvu poolest. LMDB-s on sĂŒnkroniseeritud vaid muutvad operatsioonid. Kirjutaja vĂ”ib olla igal hetkel vaid ĂŒks.
- Minimum vahemĂ€lu ja sĂŒnkroniseerimise loogikat vabastab koodi ÀÀrmiselt keerulistest vigadest, mis on seotud mitme lĂ”ime keskkonnas töötamisega. Usenix OSDI 2014 konverentsil oli kaks huvitavat andmebaasi uurimistööd: ja . Nendest vĂ”ib leida teavet nii LMDB ĂŒletamatust usaldusvÀÀrsusest kui ka ACID-omaduste peaaegu veatust rakendamisest, mis ĂŒletab seda samas SQLite-s.
- LMDB minimalism vÔimaldab masina esitusel oma koodi tÀielikult paikneda protsessori L1-vahemÀlus koos sealt tulenevate kiirusomadustega.
Kahjuks ei ole iOS-is mĂ€lu kaardistatud failidega kĂ”ik nii roosiline, nagu vĂ”iks loota. Et arutada nende puudusi teadlikumalt, tuleb meenutada selle mehhanismi rakendamise ĂŒldpĂ”himĂ”tteid operatsioonisĂŒsteemides.
Ălevaade mĂ€lu kaardistatud failidest
Iga kĂ€ivitatava rakendusega seondub operatsioonisĂŒsteemis ĂŒksus, mille nimi on protsess. Iga protsessile eraldatakse pidev aadressivahemik, kuhu paigutatakse kĂ”ik, mis on vajalik tema tööks. Madalamates aadressides asuvad koodi ja kĂ”vadest andmetest koosnevad sektsioonid ning ressursside osad. JĂ€rgneb ĂŒlespoole kasvav dĂŒnaamilise aadressiruumi plokk, mida me paremini tunneme kui heap. Selles asuvad nende ĂŒksuste aadressid, mis ilmnevad rakenduse kĂ€itamise kĂ€igus. Ălal asub mĂ€lu osa, mida kasutab rakenduse virn. See laieneb ja tĂ”mbub kokku, teisisĂ”nu, selle suurus on samuti dĂŒnaamiline. Selleks, et virn ja heap omavahel ei seguneks ja ei hĂ€iriks, on nad eraldatud aadressiruumi erinevatesse otsadesse. Kaks dĂŒnaamilist sektsiooni ĂŒlemises ja alumises osas jĂ€tavad vahe. OperatsioonisĂŒsteem kasutab selle keskmise osa aadresse, et siduda protsessidega eri ĂŒksusi. Eriti vĂ”ib see seostada mingile pidevale aadresside kogumile - faili kettal. Sellist faili nimetatakse mĂ€lu kaardistatud failiks.
Protsessile eraldatud aadressiruumi maht on tohutu. Teoreetiliselt on aadresside arv piiratud vaid sĂŒsteemi bititasemega mÀÀratud nĂ€itaja suurusega. Kui sellel oleks 1-1 seos fĂŒĂŒsilise mĂ€luga, siis esimene protsess neelaks kogu RAM-i ning mitme ĂŒlesande tĂ€itmisest ei vĂ”ikski juttu olla.
Kuid meie kogemuse pĂ”hjal teame, et tĂ€napĂ€evased operatsioonisĂŒsteemid suudavad samaaegselt kĂ€itada piiramatult palju protsesse. See on vĂ”imalik, kuna nad eraldavad protsessidele mĂ€lu ainult paberi peal, kuid tegelikult laadivad pĂ”hifĂŒĂŒsilisse mĂ€llu vaid selle osa, mis on tĂ€nasel hetkel nĂ”utud. SeetĂ”ttu nimetatakse protsessiga seotud mĂ€lu virtuaalseks.

OperatsioonisĂŒsteem korraldab virtuaalset ja fĂŒĂŒsilist mĂ€lu kindla suurusega lehtedena. Kui mĂ”ni virtuaalse mĂ€lu leht on nĂ”utud, laadib operatsioonisĂŒsteem selle fĂŒĂŒsilisse mĂ€llu ja seab nende vahel vastavuse spetsiaalsesse tabelisse. Kui vabad kohad puuduvad, kopeeritakse ĂŒks varasemalt laaditud leht kettale ja nĂ”utud leht asub selle kohale. Seda protseduuri, millega me varsti tagasi tuleme, nimetatakse vahetuseks (swapping). Allolev joonis illustreerib kirjeldatud protsessi. Sel on leht A, mille aadress on 0, laaditud ja paigutatud pĂ”hiketta lehele, mille aadress on 4. See fakt kajastub vastavustabelis lahtris number 0.

Kuvatud failide mĂ€lu ajalugu on tĂ€pselt sama. Loogiliselt need nĂ€iliselt pidevalt ja terviklikult asetsevad virtuaalses aadressiruumis. Kuid fĂŒĂŒsilisse mĂ€llu jĂ”uavad nad lehtedena ja vaid nĂ”udmisel. Selliste lehtede muutmine sĂŒnkroniseeritakse kettafailiga. Nii on vĂ”imalik teostada failide sisendi/vĂ€ljundi operatsioone, töötades lihtsalt mĂ€lus baitidega â kĂ”ik muudatused kantakse automaatselt operatsioonisĂŒsteemi tuuma poolt algsete failideni.
â
Allolev pilt nĂ€itab, kuidas LMDB sĂŒnkroniseerib oma seisundit töötades andmebaasiga erinevate protsesside vahel. Kaardistades erinevate protsesside virtuaalse mĂ€lu ĂŒhe ja sama failiga, kohustame de facto operatsioonisĂŒsteemi sĂŒnkroniseerima teatud nende aadressiruumide blokke, kuhu LMDB vaatab.
â

Oluline nĂŒanss on see, et LMDB muudab andmefaili vaikimisi sĂŒsteemi kutse write kaudu, samas kui fail ise on avatud ainult lugemiseks. Selle lĂ€henemisviisi on kaks olulist tagajĂ€rge.
Esimene tagajĂ€rg on kĂ”igile operatsioonisĂŒsteemidele ĂŒhine. Selle pĂ”hiolemus on kaitse andmebaasi tahtmatu kahjustamise eest vale koodi poolt. Nagu on teada, vĂ”ivad protsessi tĂ€idetavad kĂ€sud vabalt pÀÀseda ligi mis tahes andmetele selle aadressiruumi mis tahes kohas. Samas, nagu me just mainisime, tĂ€hendab faili kuvamine lugemise ja kirjutamise reĆŸiimis, et iga kĂ€sk vĂ”ib seda ka muuta. Kui ta teeb seda ekslikult, nĂ€iteks pĂŒĂŒdes tegelikult ĂŒmber kirjutada massiivi elementi mitteseksist indeksiga, vĂ”ib see kogemata muuta sellele aadressile kaardistatud faili, mis viib andmebaasi riknemiseni. Kui aga fail on kaardistatud lugemiseks, siis katse muuta vastavat aadressiruumi toob kaasa programmi ootamatu lĂ”petamise signaaliga SIGSEGV, ja fail jÀÀb terviklikuks.
Teine tagajĂ€rg on juba spetsiifiline iOS-ile. Ei autor ega ĂŒkski teine allikas ei maini seda otseselt, kuid ilma selleta ei oleks LMDB selles mobiiloperatsioonisĂŒsteemis kasutatav. Selle kĂ€sitlemiseks on pĂŒhendatud jĂ€rgmine jaotis.
Kuvatud mÀlufailide spetsiifika iOS-is
2018. aastal oli WWDC-l suurepĂ€rane ettekande . Sellega rÀÀgitakse, et iOS-is kuuluvad kĂ”ik fĂŒĂŒsilises mĂ€lus asuvad lehed ĂŒhte kolmest tĂŒĂŒbid: dirty, compressed ja clean.

Clean memory on lehtede kogum, mis vĂ”ib probleemideta fĂŒĂŒsilisest mĂ€lust vĂ€lja laadida. Nendes asuvaid andmeid saab vajadusel uuesti laadida nende algsetest allikatest. Lugemiseks mĂ”eldud kaardistatud failid kuuluvad just sellesse kategooriasse. iOS ei karda igal ajal kaardistatud lehti mĂ€lust vĂ€lja laadida, kuna need on garanteeritult sĂŒnkroonitud failiga kettal.
â
Dirty memory sisaldab kÔiki muudetud lehti, olenemata sellest, kus nad esialgu asusid. EelkÔige liigitatakse sellesse ka kaardistatud failid, mida on muudetud kirjutamise kaudu neile seotud virtuaalsesse mÀllu. Avades LMDB flagiga MDB_WRITEMAP, saate pÀrast muudatuste tegemist sellega ise veenduda.
Kui rakendus hakkab tarbima liiga palju fĂŒĂŒsilist mĂ€lu, allutab iOS selle dirty lehekĂŒljed kompressioonile. Rakenduse mĂ€lu jalajĂ€lg koosneb dirty ja kompressitud lehekĂŒlgedest. Kui see saavutab teatud kĂŒnnise, sekkub sĂŒsteemne demon OOM killer ja lĂ”petab protsessi sunniviisil. See on iOS-i eripĂ€ra vĂ”rreldes lauaarvutite operatsioonisĂŒsteemidega. Erinevalt neist ei ole iOS-is lehekĂŒlgede vahetamiseks fĂŒĂŒsilisest mĂ€lust kettale memory footprint'i vĂ€hendamine ette nĂ€htud. PĂ”hjustest saab vaid spekuleerida. VĂ”ib-olla on lehekĂŒlgede intensiivne liigutamine kettale ja tagasi liiga energiakulukas mobiildevice'ide jaoks vĂ”i iOS sÀÀstab SSD-de lahtrite ĂŒle kirjutamise ressursse, vĂ”i polnud disainerite rahul sĂŒsteemi ĂŒldise jĂ”udlusega, kus kĂ”ik pidevalt vahetub. Kuidas iganes see ka poleks, fakt jÀÀb faktiks.
Hea uudis, nagu juba mainitud, on see, et LMDB ei kasuta vaikimisi mmap mehhanismi failide vĂ€rskendamiseks. Seega klassifitseeritakse kuvatavad andmed iOS-i poolt kui puhas mĂ€lu ja need ei suurenda memory footprint'i. Seda saab kontrollida Xcode'i tööriista VM Tracker abil. Alloleval ekraanipildil on nĂ€idatud iOS-i rakenduse Oblaak virtuaalmĂ€lu olek töö ajal. Alguses oli sellel initsialiseeritud 2 LMDB instantsi. Esimesele lubati oma faili kuvada 1GiB virtuaalses mĂ€lus, teisele â 512MiB. Kuigi mĂ”lemad andmehoidlad vĂ”tavad teatud hulga residentset mĂ€lu, ei aita kumbki dirty size'i suurendada.

Ja nĂŒĂŒd on aeg halbadeks uudisteks. 64-bitiste lauaarvutite operatsioonisĂŒsteemides vĂ”imaldab vahetusmehhanism iga protsessile vĂ”tta niipalju virtuaalset aadressiruumi, kui lubab vaba ruum kĂ”vakettal tema potentsiaalse vahetuse jaoks. Vahetuse asendamine kompressiooniga iOS-is vĂ€hendab teoreetilist maksimaali radikaalselt. NĂŒĂŒd peavad kĂ”ik elavad protsessid mahtuma pĂ”himenĂŒĂŒsse (loe: operatiivmĂ€lu), ja kĂ”ik, mis ei mahu, tuleb sunniviisiliselt lĂ”petada. Sellest rÀÀgitakse ka eelnevas. , kui ka SeetĂ”ttu piirab iOS rangelt mĂ€lu suurust, mis on saadaval mmap-i kaudu eraldamiseks. Siin on Saame vaadata empiirilisi piire mĂ€lu mahtude osas, mida on vĂ”imalik eraldada erinevatele seadmetele selle sĂŒsteemi kĂ”ne abil. KĂ”ige kaasaegsematel iOS-i nutitelefonidel on eraldatud 2 gigabaidi, samas kui tipptasemel iPad'i mudelitel on see 4. Praktiliselt tuleb siiski orienteeruda kĂ”ige madalama klassi toetamiste seadmetele, kus olukord on vĂ€ga halb. Veel hullem, kui vaadata rakenduse mĂ€lu olekut VM Trackeris, vĂ”ib avastada, et LMDB ei ole kaugeltki ainus, kes taotleb memory-mapped mĂ€lu. Suured osad mĂ€lu kasutavad sĂŒsteemi jaotajad, ressursifailid, pilditöötluse raamistikud ja teised vĂ€iksemad röövlid.
Eksperimentide tulemuste pÔhjal pilves jÔudsime jÀrgmiste kompromisssete vÀÀrtusteni eraldatud LMDB mÀlu osas: 384 megabaiti 32-bitiste seadmete jaoks ja 768 megabaiti 64-bitiste jaoks. PÀrast selle mahu kasutamist lÔpetavad kÔik muudatustega seotud toimingud koodi MDB_MAP_FULL. Selliseid vigu oleme jÀlginud meie monitooringus, kuid neid on piisavalt vÀhe, et neid praeguses etapis saaks ignoreerida.
MĂ€lu ĂŒleliigse tarbimise mitteilmne pĂ”hjuse vĂ”ib olla pikaajalised tehingud. Et mĂ”ista, kuidas need kaks nĂ€htust on seotud, aitab meil kahe ĂŒlejÀÀnud LMDB 'hiigla' vaatamine.
3.2. Hiiglane nr 2. B+-puu
Key-value andmehoidla kohal tabelite emuleerimiseks peab selle API-s olema jÀrgmised toimingud:
- Uue elemendi sisestamine.
- Elemendi otsimine antud vÔtme alusel.
- Elemendi eemaldamine.
- VÔtmepaaride sorteeritud jÀrjekorras lÀbimine.
Lihtsaim andmestruktuur, mille abil on vĂ”imalik hĂ”lpsasti teostada kĂ”iki nelja toimingut, on binaarne otsingupuu. Iga sĂ”lm esindab key, jagades kogu alamkomplekti alamvĂ”tmete vahel kaheks alampuuks. Vasakul on need, mis on vĂ€iksemad vanemast, ja paremal need, mis on suuremad. Korrastatud vĂ”tmete kogumi saamine saavutatakse ĂŒhe klassikalise puu lĂ€bimisega.
Binaarpuudel on kaks fundamentaalset puudust, mis takistavad neil olla tÔhusad diskistruktuuride andmestruktuuridena. Esiteks on nende tasakaalu aste ettearvamatu. On suur risk saada puid, mille erinevate harude kÔrgus vÔib oluliselt erineda, mis halvendab oluliselt otsingu algoritmilist keerukust vÔrreldes ootustega. Teiseks, sÔlmede vaheliste ristviidete rohkus kaotab binaarpuude mÀlus paiknemise lokaalsuse. LÀhedased sÔlmed (seoses nendevaheliste sidemetega) vÔivad asuda tÀiesti erinevates lehtedes virtuaalses mÀlus. Selle tagajÀrjel vÔib isegi mÔne naabersÔlme lihtne lÀbimine nÔuda vÔrreldavat hulka lehti. See on probleem isegi siis, kui rÀÀgime binaarpuude efektiivsusest in-memory andmestruktuuridena, kuna pidev lehtede rotatsioon protsessori mÀlus on kulukas. Kui aga jutt kÀib seotud sÔlmede lehtede sagedasest tÔstmisest kettalt, muutub olukord tÔeliselt kehv. .
B-puud, olles binaarpuude evolutsioon, lahendavad eespool nimetatud probleemid. Esiteks, nad on isetasakaaluvad. Teiseks, iga nende sĂ”lm jagab hulga alamvĂ”tmeid mitte 2, vaid M jĂ€rjekorras alarĂŒhmaks, kusjuures M-i arv vĂ”ib olla ĂŒsna suur, ulatudes mitmesaja vĂ”i isegi tuhande piirini.
Selle tulemusel:
- Igas sÔlmes on suur hulk juba jÀrjestatud vÔtmeid ja puud osutuvad vÀga madalaks.
- Puul on mĂ€lus paiknemise lokaalsuse omadus, kuna sarnaste vÀÀrtustega vĂ”tmed paiknevad loomulikult ĂŒksteise kĂ”rval samas vĂ”i naaber sĂ”lmedes.
- Otsinguoperatsiooni kĂ€igus vĂ€heneb puu allapoole liikudes ĂŒlemineku sĂ”lmede arv.
- Piiratud on loetavate sihtsÔlmede arv vahemiku pÀringutes, kuna igas neist on juba suur hulk jÀrjestatud vÔtmeid.

LMDB-s andmete salvestamiseks kasutatakse B-puu ĂŒhte variatsiooni, mida nimetatakse B+-puuks. Ălaltoodud joonisel on kujutatud kolme tĂŒĂŒpi sĂ”lmi, mis selles esinevad:
- Tipus asub juur (root). See materialiseerib enamasti vaid andmebaasi kontseptsiooni salvestuses. Ăhes LMDB instantsis on vĂ”imalik luua mitu andmebaasi, mis jagavad omavahel kaardistatud virtuaalset aadressiruumi. IgaĂŒks neist algab omaenda juurest.
- KĂ”ige alumisel tasemel asuvad lehed (leaf). Need sisaldavad ainult andmebaasis hoitavaid vĂ”tme-vÀÀrtus paare. See on B+-puu eripĂ€ra. Kui tavaline B-puu salvestab vÀÀrtusosad kĂ”igi tasemete sĂ”lmedes, siis B+-variatsioon ainult kĂ”ige alumisel tasandil. Pidades seda fakti meeles, nimetame edasi LMDB-s kasutatavat puu alamtĂŒĂŒp lihtsalt B-puuks.
- Juurte ja lehtede vahele asub 0 vĂ”i enam tehnilist taset navigeerimis (branch) sĂ”lmedega. Nende ĂŒlesanne on jagada sorteeritud vĂ”tmete kogumit lehtede vahel.
FĂŒĂŒsiliselt on sĂ”lmed eelnevalt mÀÀratud pikkusega mĂ€lublokid. Nende suurus on mĂ€lulehtede suuruse korrutis, millest rÀÀkisime varem. Allpool on kujutatud sĂ”lme struktuuri. Pealkirjas asub meta-informatsioon, kĂ”ige ilmekam nĂ€ide on kontrollsummal. SeejĂ€rel on info ofsettidest, mille jĂ€rgi asuvad andmepesad. Andmetena vĂ”ivad esindada kas vĂ”tmed, kui rÀÀgime navigeerimis-sĂ”lmedest, vĂ”i tĂ€ielikud vĂ”tme-vÀÀrtus paarid lehtede puhul. Rohkem lehtede struktuuri kohta saab lugeda tööst .

PÀrast sisemist sÔlmede-lehtede sisu selgitamist esitame LMDB B-puude lihtsalt jÀrgmise kujuga.

SÔlmedega lehed asuvad jÀrkjÀrgult kettal. Suuremalt numbritelt sÔlmed asuvad faili lÔpupoole. Nii-öelda meta-leht (meta page) sisaldab teavet ofsettidest, mille jÀrgi saab leida kÔigi puude juured. LMDB faili avamisel skaneerib see lehekaupa faili lÔpust algusse valideeritud meta-lehe leidmiseks ja juba selle kaudu leiab olemasolevad andmebaasid.

NĂŒĂŒd, kui on loodud ettekujutus andmete korralduse loogilisest ja fĂŒĂŒsilisest struktuurist, saab edasi liikuda LMDB kolmanda toe kĂ€sitlemise juurde. Just selle abil toimuvad kĂ”ik muudatused salvestuses tehinguliselt ja ĂŒksteisest isoleeritult, andes andmebaasile tervikuna ka mitme versiooni omaduse.
3.3. Tugi â3. Copy-on-write
MĂ”ned B-puu operatsioonid hĂ”lmavad mitme muudatuse tegemist selle sĂ”lmedes. Ăks nĂ€ide on uue vĂ”tme lisamine sĂ”lmele, kus on juba saavutatud maksimaalne mahutavus. Sellisel juhul tuleb esmalt jagada sĂ”lm kaheks ja teiseks lisada viide uuele eraldunud tĂŒtarsĂ”lmele selle vanemasĂ”lmes. See protseduur on potentsiaalselt vĂ€ga ohtlik. Kui mingil pĂ”hjusel (krahh, voolukatkestus jne) toimub ainult osa muudatusi seeriast, jÀÀb puu konsistentsi seisundisse.
Ăks traditsioonilisi lahendusi andmebaasi töökindluse tagamiseks on B-puu kĂ”rval lisadiskistruktuuri â tehingute logi, mida tuntakse ka kui write-ahead log (WAL). See on fail, kuhu kirjutatakse enne B-puu enda muutmist soovitud operatsioon. Nii, kui enesediagnoosi kĂ€igus tuvastatakse andmete rikkumine, konsultatsioon andmebaasi logiga, et ennast korda seada.
LMDB on vigade tagamise mehhanismi valimisel valinud teise meetodi, mida nimetatakse copy-on-write. Selle pÔhiolemus on see, et andmeid olemasolevas lehes ei uuendata, vaid need kopeeritakse tÀielikult ja kÔik muudatused tehakse juba koopia peale.

Edasi, et uuendatud andmed oleksid kÀttesaadavad, tuleb muuta viiteid nimeka sÔlme suhtes tema vanemasÔlmes. Kuna selleks tuleb ka seda modifitseerida, kopeeritakse see ka eelnevalt. Protsess jÀtkub rekursiivselt juureni. Viimaks muudetakse andmed meta-lehe peal.

Kui uuendamise protseduuri ajal peaks toimuma ootamatu protsessi lĂ”petamine, siis kas uut meta-lehte ei luleta ĂŒldse vĂ”i ei salvestata seda kettale lĂ”puni, mille tulemuseks on vale kontrollsumma. Ăhes nendes kahest olukorrast ei ole uued lehed kĂ€ttesaadavad ja vanad jÀÀvad terveks. See vabastab LMDB vajadusest pidada write ahead log'i andmete konsistentsi tagamiseks. Ălaltoodud kettastruktuur tĂ€idab samal ajal ka selle funktsiooni. Tehingulogide puudumine otseselt on ĂŒks LMDB omadusi, mis tagab kiire andme lugemise.

Saadud struktuur, mida nimetatakse append-only B-puuks, tagab loomulikult tehingute isolatsiooni ja mitu versiooni. LMDB-s seondub iga avatud tehinguga ajakohane puu juur. Kuni tehing pole lĂ”petatud, ei muudetud ega kasutatud seonduvaid lehti uute andme versioonide jaoks kunagi. Nii on vĂ”imalik töötada lĂ”pmatult tĂ€pselt nende andmete kogumiga, mis oli avamise hetkel kehtiv, isegi kui salvestus jĂ€tkab aktiivset uuendamist. See on mitme versiooni olemus, mis teeb LMDB-st ideaalse andmeallika meie armastatud UICollectionView. Tehingu avamisel ei ole vaja suurendada rakenduse mĂ€lu jalajĂ€lge, kiirustades asjakohaste andmete vĂ€ljavĂ”tmisega mingisse in-memory struktuuri, kartes end surnud nurka jÀÀda. See omadus eristab LMDB-d SQLite-st, millel ei ole sellist tĂ€ielikku isolatsiooni. Avades viimases kaks tehingut ja kustutades ĂŒhe kirje, ei saa seda kirjet enam teises allesolevas tehingus kĂ€tte.
VastuvĂ”tu tagakĂŒljeks on potentsiaalselt oluliselt suurem virtuaalmĂ€lu kasutamine. Kaldkriipsul on nĂ€idatud, kuidas andmebaasi struktuur vĂ€lja nĂ€eb, kui selle modifitseerimist tehakse samal ajal kolme avatud lugemise tehinguga, mis vaatavad erinevaid andmebaasi versioone. Kuna LMDB ei saa taaskasutada juuri, mis on seotud aktiivsete tehingutega, ei jÀÀ salvestamisele muud teha, kui mĂ€lus luua veel ĂŒks neljas juur ja taas kord kloonida selle alla modifitseeritavad lehed.

Siin on sobilik meenutada mĂ€lu kaardistatud failide osa. Tundub, et tĂ€iendav virtuaalmĂ€lu kasutamine ei tohiks meid liiga hĂ€irida, kuna see ei mĂ”juta rakenduse mĂ€lu jalajĂ€lge. Kuid samas on mĂ€rgitud, et iOS on selle jaotamise osas vĂ€ga kitsi ning me ei saa nagu serveris vĂ”i lauaarvutis LMDB-le ĂŒhe terabaidi piirkonda vabatahtlikult anda ja sellest omadusest ĂŒldse mitte mĂ”elda. Vaja on pĂŒĂŒda hoida tehingute eluaeg vĂ”imalikult lĂŒhikesena.
4. Andmeskeemi kavandamine key-value API peal
API analĂŒĂŒsi alustame LMDB poolt pakutavate pĂ”histruktuuride uurimisega: keskkond ja andmebaasid, vĂ”tmed ja vÀÀrtused, tehingud ja kursorid.
MĂ€rkus koodiloendite kohta
KĂ”ik LMDB avaliku API funktsioonid tagastavad oma töö tulemuse veakoodina, kuid jĂ€rgnevatel loenditel on selle kontrollimise osa lohakam plaan, et hoida kokku teksti. Praktikas kasutasime me salvestussĂŒsteemiga suhtlemiseks oma C++ ĂŒmbermÔÔtjat , milles vead materialiseeruvad C++ eranditena.
Kiireim viis LMDB projekti integreerimiseks iOS vÔi macOS jaoks on minu CocoaPod .
4.1. PÔhistruktuurid
Keskkond (environment)
Struktuur MDB_env on LMDB sisemiste olekute hoidla. Funktsioonide kogum eessÔnaga mdb_env vÔimaldab konfigureerida mÔningaid selle omadusi. KÔige lihtsamal juhul nÀeb mootori initsialiseerimine vÀlja selline.
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 vaikimisi ainult kaht parameetrit.
Esimene neist on virtuaalse aadressiruumi suurus, millele salvestusfail kaardistatakse. Kahjuks vĂ”ib isegi samal seadmel konkreetne vÀÀrtus oluliselt erineda kĂ€ivituse kaupa. Selle iOS-i eripĂ€ra arvessevĂ”tmiseks valitakse maksimaalne salvestusruum dĂŒnaamiliselt. Alustades mingist vÀÀrtusest, vĂ€hendatakse seda jĂ€rk-jĂ€rgult poole vĂ”rra seni, kuni funktsioon mdb_env_open tagastab tulemuse, mis erineb ENOMEM. Teoorias on olemas ka vastupidine tee â kĂ”igepealt eraldada mootorile minimaalne mĂ€lu ja seejĂ€rel, kui vigu tekib, suurendada seda. Kuid see on palju keerulisem. PĂ”hjus on selles, et mĂ€lu taasalustamise protseduur (remap) funktsiooni MDB_MAP_FULLmdb_env_set_map_size abil kehtetuks kĂ”ik entiteedid (kursused, tehingud, vĂ”tmed ja vÀÀrtused), mis on eelnevalt mootorilt saadud. Sellise sĂŒndmuste kĂ€igu arvesse vĂ”tmine koodis muudab selle oluliselt keerulisemaks. Kui aga virtuaalne mĂ€lu on teile tĂ”eliselt oluline, siis vĂ”ib see olla pĂ”hjus tutvuda kaugele arenenud forkiga , kus ĂŒks lubatud funktsioone on «automaatne andmebaasi suuruse kĂ”va reguleerimine». Teine parameeter, mille pĂ”hivÀÀrtus meile ei sobinud, reguleerib lĂ”imede ohutuse tagamise mehhanismi. Kahjuks on vĂ€hemalt iOS 10-s probleeme lĂ”imede kohaliku salvestuse toetamisega. SeetĂ”ttu avatakse eespool toodud nĂ€ites salvestus lipu
MDB_NOTLS . Lisaks oli vajalik kaforkida , et selle atribuudi muutujad seal vÀlja lÔigata. Andmebaas on eraldi B-puu instants, millest me eelnevalt rÀÀkisime. Selle avamine toimub tehingu raames, mis vÔib alguses tunduda veidi kummaline.
Andmebaasid
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, tehing LMDB-s on salvestuse entiteet, mitte konkreetne andmebaas. Selline kontseptsioon vĂ”imaldab teostada aatomseid operatsioone entiteetide ĂŒle, mis asuvad erinevates andmebaasides. Teoorias avab see vĂ”imaluse modelleerida tabeleid erinevate andmebaasidena, kuid mina valisin sel ajal teistsuguse tee, mis on allpool pĂ”hjalikult kirjeldatud.VĂ”tmed ja vÀÀrtused
MDB_val
Struktuur MDB_val mudelib kontseptsiooni nii vÔtme kui ka vÀÀrtusena. Salvestus ei oma vÀhimatki arusaama nende semantikat. Tema jaoks on midagi teist lihtsalt 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;ââKomparaatori abil jĂ€rjestab salvestus vĂ”tmed kasvavas jĂ€rjestuses. Kui seda enda omaga ei asendata, kasutatakse vaikimisi, mis jĂ€rjestab need baitide kaupa leksikograafilises jĂ€rjekorras.
Tehingud
Tehingute mehhanism on detailne kirjeldus , seetĂ”ttu kordaksin siin lĂŒhidalt nende peamisi omadusi:
- KÔigi pÔhiosade tugi : atomaarne, jÀrjepidevus, isoleeritus ja usaldusvÀÀrsus. Pean mÀrkima, et osas durability on macOS-il ja iOS-il olemas viga, mis on parandatud MDBX-is. TÀiendavalt saab lugeda nende .
- LĂ€henemine mitme niidi toetusele on kirjeldatud skeemiga âĂŒks kirjutaja / mitu lugejatâ. Kirjutajad blokeerivad ĂŒksteist, kuid ei blokeeri lugejaid. Lugejad ei blokeeri ei kirjutajaid ega ĂŒksteist.
- Sisseehitatud tehingute tugi.
- Mitme versiooni toetamine.
Mitme versiooni tugi LMDB-s on nii hea, et soovin seda nÀidata tegevuses. Allpool olevast koodist on nÀha, et iga tehing töötab just selle andmebaasi versiooniga, mis oli avamise ajal aktuaalne, olles tÀielikult isoleeritud kÔigist jÀrgnevatest muudatustest. Salvestuse initsialiseerimine ja testandme salvestamine ei paku midagi huvitavat, seega on need rituaalid jÀetud spoilerisse.
Testandme 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
// andmebaasi versiooni, kus on ĂŒks kirje.
mdb_txn_begin(env, NULL, 0, &txn1); // lugemis-kirjutamine
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn2); // ainult lugemine
// Esiteks tehingus eemaldame andmebaasist olemasoleva kirje.
mdb_del(txn1, dbi, &key, NULL);
// Kinnitage eemaldamine.
mdb_txn_commit(txn1);
// Avame kolmanda tehingu, mis vaatab
// andmebaasi aktuaalset versiooni, kus kirje ei ole enam.
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn3);
// Veendume, et otsitud vÔtmega seonduvat kirjet enam ei eksisteeri.
assert(mdb_get(txn3, dbi, &key, &val) == MDB_NOTFOUND);
// LÔpetame tehingu.
mdb_txn_abort(txn3);
// Veendume, et teise tehingu raames, mis avati kirje olemasolu ajal
// andmebaasis, on kirje endiselt leitav vÔtme jÀrgi.
assert(mdb_get(txn2, dbi, &key, &val) == MDB_SUCCESS);
// Kontrollime, et vÔtme jÀrgi saame mitte lihtsalt rÀmpsdata, vaid kehtivad andmed.
assert(*(int *)val.mv_data == 997);
// LÔpetame tehingu, mis töötab ehkki aegunud, kuid konsistentse andmebaasiga.
mdb_txn_abort(txn2);Soovitan proovida sama trikki SQLite'iga ja vaadata, mis vÀlja tuleb.
Mitme versiooni olemasolu toob iOS arendaja ellu vĂ€ga meeldivaid boonuseid. Selle omaduse abil saab andmeallika (data source) vĂ€rskendamise kiirusest mugavalt ja lihtsalt reguleerida ekraanivormide jaoks, lĂ€htudes kasutajakogemusest. NĂ€iteks vĂ”tame Mail.ru pilve rakenduse funktsiooni, kus sisu laadimine toimub sĂŒsteemi meediagalerii kaudu. Headel ĂŒhendustel suudab klient serverisse lisada mitu fotot sekundis. Kui iga laadimise jĂ€rel ajakohastada UICollectionView kasutaja pilves oleva meediasisuga, siis vĂ”ib 60 fps-st ja sujuvast kerimisest selle protsessi ajal unustada. Et vĂ€ltida sagedasi ekraani vĂ€rskendusi, on vajalik kuidagi piirata andmete muutmise kiirusel aluse. UICollectionViewDataSource.
Kui andmebaas ei toeta mitme versiooni kasutamist ja vÔimaldab töötada vaid praeguse kehtiva olekuga, tuleb stabiilse ajas sÀiliva andmesnapshooti loomiseks see kopeerida kas mingisse mÀlustruktuuri vÔi ajutisse tabelisse. MÔlemad lÀhenemisviisid on vÀga kulukad. MÀlus hoidmise puhul kaasnevad kulud mÀluga, mis on seotud konstrueeritud objektide hoidmisega, samuti ajaga, mis on seotud liigsete ORM-muutmistega. Mis puudutab ajutist tabelit, siis see on veelgi kallim lahendus, millel on mÔttekus vaid keerukates juhtumites.
LMDB mitme versiooni kasutamine lahendab stabiilse andmeallika sĂ€ilitamise ĂŒlesande vĂ€ga elegantselt. Piisab lihtsalt tehingu avamisest ja voilĂ â seni, kuni me selle ei lĂ”peta, on andmestik meil garanteeritult fikseeritud. Selle vĂ€rskendamise kiirus on nĂŒĂŒd tĂ€ielikult ja tĂ€ielikult esitluskihti kontrolli all, ilma igasuguste oluliste ressursside kuludeta.
Kursorid
Kursorid pakuvad mehhanismi paigutatud iteratsiooniks vĂ”tme-vÀÀrtuse paaride ĂŒle, lĂ€bides B-puude. Ilma nendeta oleks vĂ”imatu tĂ”husalt modelleerida andmebaasi tabeleid, mille arutlemiseks me liikume.
4.2. Tabelite modelleerimine
VÔtmepaaride jÀrjekorra omadus vÔimaldab ehitada pÔhiabstraktsiooni peal kÔrgetasemelisi nagu tabel. Vaatleme seda protsessi pilguga, vÔtame nÀiteks pÔhiteabe tabeli pilveklientidest, kus on vahemÀllu salvestatud teave kÔigi kasutaja failide ja kaustade kohta.
Tabeli skeem
Ăks sagedasi stsenaariume, mille jaoks peab kaustade puu struktuur olema kohandatud, on kĂ”ikide elementide valimine, mis asuvad mÀÀratud kaustas. Hea andmete korraldamise mudel tĂ”husate taotluste jaoks sellise ĂŒlesande jaoks on . Selle elluviimiseks vĂ”tme-vÀÀrtuse hoidlal tuleb sorteerida failide ja kaustade vĂ”tmed nii, et need rĂŒhmitatakse vastavalt vanemkausta kuuluvusele. Lisaks, et kuvada kausta sisu kasutaja Windowsi tuttavas vaates (kĂ”igepealt kaustad, seejĂ€rel failid, ja mĂ”lemad on sorteeritud tĂ€hestikulises jĂ€rjekorras), tuleb vĂ”tmesse lisada vastavad tĂ€iendavad vĂ€ljad.
Allpool on esitatud, kuidas lĂ€htudes antud ĂŒlesandest vĂ”ivad vĂ”tmed vĂ€lja nĂ€ha baitide massiivina. Esiteks asetatakse vanem directory identifikaatorid (punased), siis tĂŒĂŒp (rohelised) ja lĂ”puks nimi (sinised). Markeeritud LMDB vaikimisi vĂ”rdleja poolt leksikograafilises jĂ€rjekorras, need asetatakse nĂ”utud viisil. Ăhe ja sama punase prefiksiga vĂ”tmete jĂ€rjestikune lĂ€bikĂ€ik annab meile seotud vÀÀrtused selles jĂ€rjekorras, milles need peavad kasutajaliideses (paremal) olema vĂ€lja toodud, ilma et oleks vaja veel mingit tĂ€iendavat jĂ€reltöötlemist.

VÔtmete ja vÀÀrtuste serialiseerimine
Maailmas on vĂ€lja mĂ”eldud palju objekti serialiseerimise meetodeid. Kuna meil ei olnud muid nĂ”udeid peale kiirus, valisime enda jaoks kĂ”ige kiirema â C keele struktuuri meenutava mĂ€lu dumpimise. Nii saab kausta elemendi vĂ”tit modelleerida 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 paigutada andmete viit algstruktuuri aadressile 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 kriteeriumitest mainisin olulise valikufaktorina dĂŒnaamiliste eraldamiste vĂ€hendamist CRUD operatsioonide raames. Funktsiooni koodi serialize nĂ€itab, kuidas LMDB puhul saab uusi kirjeid andmebaasi sisestamisel need tĂ€ielikult vĂ€ltida. Serverist saabunud baitide massiiv muudetakse esmalt virnastavateks struktuurideks, seejĂ€rel dumpitakse need triviaalsetel viisidel salvestusse. Arvestades, et LMDB sees ei ole samuti dĂŒnaamilisi eraldamisi, saame fantastilise olukorra iOS-i mÔÔtkavas - andmete töötlemiseks nende teel vĂ”rgust kettani kasutatakse ainult virramĂ€lu!
VÔtmete jÀrjestamine binaarse vÔrdlejaga
VÔtmete jÀrjekord mÀÀratakse spetsiaalse funktsiooni, mida nimetatakse vÔrdlejaks. Kuna mootori jaoks ei ole mingit teavet nende sees olevate baitide semantika kohta, peab vaikimisi vÔrdleja rahulduma vÔtmete jÀrjekorra mÀÀramisega leksikograafilises jÀrjekorras, tuginedes nende baitide vÔrdlemisele. Selle kasutamine struktuuride jÀrjestamiseks on sarnane liha lÔikamisega puu nahaga. Siiski leian, et lihtsates olukordades on see meetod vastuvÔetav. Alternatiiv on kirjeldatud allpool, kuid siinkohal mainin paar hajutatud takistust sellel teel.
Esimene asi, mida meeles pidada, on primitiivsete andmetĂŒĂŒpide edastamine mĂ€lus. Nii et kĂ”ik Apple'i seadmed salvestavad tĂ€isarvulised muutujaid formaadis . See tĂ€hendab, et kĂ”ige vĂ€hem olulised bait asub vasakul ja tĂ€isarve ei saa nende baitide vĂ”rdlemisel sortida. NĂ€iteks proovides sortida numbrite kogum alates 0 kuni 511, saadakse jĂ€rgmine tulemus.
// 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Ă”tmes sobivas formaadis, mida saab kasutada baitide vĂ”rdlejaga. Vajalikku konverteerimist aitavad funktsioonid perede hulgast hton* (eriti htons kahe baitide numbrite jaoks, mille ĂŒle me rÀÀgime).
Programmeerimises on stringide esitamistunne, see on, nagu teate, terviklik . Kui stringide semantika ja nende mĂ€lus esitatav kodeering eeldab, et sĂŒmbol vĂ”ib koosneda rohkem kui ĂŒhest baitist, siis on parem kohe loobuda vaikimisi vĂ”rdleja kasutamisest.
Teine asi, mida meeles pidada â kompilaatorite printsiibid. Nende tĂ”ttu vĂ”ivad mĂ€lus vĂ€ljaede vahel tekkida rĂ€mpsvÀÀrtused, mis muidugi rikub baitide sortimist. RĂ€mpsu kĂ”rvaldamiseks tuleb kas vĂ€ljaede kuulutada kindlas jĂ€rjestuses, meeles pidada allakirjutatud printsiibu reegleid, vĂ”i kasutada struktuuri kuulutamisel atribuuti packed.
VÔtmete jÀrjestamine vÀlist vÔrdlejaga
VĂ”tmete vĂ”rdlemise loogika vĂ”ib osutada liiga keeruliseks binaarsele vĂ”rdlejale. Ăks paljusid pĂ”hjuseid on see, et struktuurides on tehnilised vĂ€ljaed. Illustreerin nende esinemist juba tuntud vĂ”tme nĂ€itel kaustade elemendi jaoks.
typedef struct NodeKey {
EntityId parentId;
uint8_t type;
uint8_t nameBuffer[256];
} NodeKey;Kuigi see on vĂ€ga lihtne, tarbib see enamikul juhtudel liiga palju mĂ€lu. Puhver nime jaoks vĂ”tab 256 baiti, kuigi keskmiselt ĂŒletavad failide ja kaustade nimed harva 20-30 tĂ€hemĂ€rki.
Ăks standardseid vĂ”tme suuruse optimeerimise meetodeid on selle "lĂ”ikamine" tegeliku suuruse jĂ€rgi. Selle olemus seisneb selles, et kĂ”ik muutuva pikkusega vĂ€ljade sisu salvestatakse struktuuri lĂ”ppu puhvris ja nende pikkused on eraldi muutujaid. Vastavalt sellele lĂ€henemisele vĂ”tme NodeKey muudetakse jĂ€rgmiselt.
typedef struct NodeKey {â
EntityId parentId;â
uint8_t type;â
uint8_t nameLength;â
uint8_t nameBuffer[256];â
} NodeKey;Edasi seriaalimise korral mÀÀratakse andmete suuruseks mitte sizeof kogu struktuur, vaid kÔik fikseeritud pikkusega vÀljad pluss reaalselt kasutatud puhvri osa suurus.
MDB_val serialize(NodeKey * const key) {
return MDB_val {
.mv_size = offsetof(NodeKey, nameBuffer) + key->nameLength,
.mv_data = (void *)key
};
}Refaktoreerimise tulemusena saavutame vÔtmete puhul mÀrkimisvÀÀrse ruumi kokkuhoiu. Kuid tehnilise vÀlja tÔttu nameLength, ei sobi vaikimisi binaarne vÔrdleja enam vÔtmete vÔrdlemiseks. Kui me seda ei asenda oma omaga, siis nimi pikkus on sorteerimisel olulisem tegur kui nimi ise.
LMDB vÔimaldab igale andmebaasile mÀÀrata oma vÔtmete vÔrdlemise funktsiooni. Seda tehakse funktsiooni mdb_set_compare kaudu, rangelt enne andmebaasi avamist. Ilmselgetel pÔhjustel ei saa seda andmebaasi elu jooksul muuta. VÔrdleja saab kaks binaarset vÔtit ja tagastab vÔrdlemise tulemuse: vÀhem (-1), rohkem (1) vÔi vÔrdsed (0). Pseudokood NodeKey nÀeb vÀlja jÀrgmiselt.
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 kĂ”ik andmebaasi vĂ”tmed on sama tĂŒĂŒpi, on nende baitide esituse tingimusteta teisendamine rakenduse vĂ”tmestruktuuri tĂŒĂŒbiks seaduslik. Siin on ĂŒks nĂŒanss, kuid selle kĂ€sitleme hiljem jaotises "Kirjete lugemine".
VÀÀrtuste seriaalimine
LMDB salvestatud rekordite vÔtmed töötavad ÀÀrmiselt intensiivselt. Nende omavaheline vÔrdlemine toimub igas rakendusoperatsioonis, ja vÔrdleja töökiirus mÔjutab kogu lahenduse jÔudlust. Ideaalmaailmas piisaks vÔtmete vÔrdlemiseks vaikesissetatud binaarsest vÔrdlejast, kuid kui on vaja kasutada omaenda, peaks vÔtmete deserialiseerimise protseduur olema nii kiire kui vÔimalik.
AndmevĂ€lja (value) osas ei huvita andmebaasi eriti. Selle muutmine baitide representatsioonist objektiks toimub ainult siis, kui seda rakenduskoodis tĂ”eliselt vajatakse, nĂ€iteks selle kuvamiseks ekraanil. Kuna seda juhtub suhteliselt harva, ei ole kiirusnĂ”uded selle protseduuri jaoks nii kriitilised ja selle rakendamisel saame me palju vabamalt mugavuse poole pĂŒĂŒelda. NĂ€iteks kasutame metafailide, mis veel ei ole laaditud, serialiseerimiseks NSKeyedArchiver.
NSData *data = serialize(object);â
MDB_val value = {â
.mv_size = data.length,â
.mv_data = (void *)data.bytesâ
};Siiski on juhtumeid, kus jĂ”udlus on siiski oluline. NĂ€iteks kasutame kasutaja pilve failistruktuuri metaandmete salvestamisel ikka sama objekti mĂ€lu dumpi. Ălesande eripĂ€ra nende serialiseeritud esitusviisi genereerimisel on see, et katalooge modelleeritakse klasside hierarhiaga.

C keeles rakendades kantakse erikuuluvused eristruktuuridesse ja nende seos pÔhistruktuuriga mÀÀratakse unioni vÀlja kaudu. Liidu praegune sisu mÀÀratakse tehnilise atribuudi kaudu type.
typedef struct NodeValue {â
EntityId localId;â
EntityType type;â
union {â
FileInfo file;â
DirectoryInfo directory;â
} info;â
uint8_t nameLength;â
uint8_t nameBuffer[256];â
} NodeValue;âSalvestuste lisamine ja uuendamine
Serialiseeritud vÔti ja vÀÀrtus saab salvestustesse lisada. Selleks kasutatakse funktsiooni mdb_put.
// key Đž value ĐžĐŒĐ”ŃŃ ŃОп MDB_valâ
mdb_put(..., &key, &value, MDB_NOOVERWRITE);Konfiguratsioonietapis vÔib salvestusele lubada vÔi keelata mitme sama vÔtmega kirje salvestamise. Kui vÔtmete dubleerimine on keelatud, siis kirje sisestamise ajal saab mÀÀrata, kas olemasolevat kirjet on lubatud uuendada vÔi mitte. Kui kattumine vÔib toimuda vaid koodi vea tÔttu, siis saab seda ennetada, mÀÀrates lipu. NOOVERWRITE.
Kirjete lugemine
Kirjete lugemiseks on LMDB-s ette nÀhtud funktsioon mdb_get. Kui vÔtme-vÀÀrtuse paar on varem dump-struktuurides esitatud, 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 koodinĂ€ide nĂ€itab, kuidas struktuuride dumpimise kaudu serialiseerimine vĂ”imaldab vabaneda dĂŒnaamilistest allokatsioonidest mitte ainult salvestamise, vaid ka andmete lugemise ajal. Funktsioonist saadud mdb_get pöördub otse sinna aadressi, kus andmebaas hoiab objekti baitpĂ”hiseid representatsioone. Tegelikult saame millegi ORM-i, mis praktiliselt tasuta tagab vĂ€ga kĂ”rge andmete lugemiskiirus. Kogu selle lĂ€henemise ilu juures tuleb siiski meeles pidada mitu seotud omadust.
- Readonly tehingu puhul jÀÀb viide struktuuri-vÀÀrtusele kehtivaks vaid seni, kuni tehing on suletud. Nagu eelnevalt mainitud, jÀÀvad B-puu lehed, kus objekt asub, copy-on-write pĂ”himĂ”tte tĂ”ttu muutumatuks, kuni vĂ€hemalt ĂŒks tehing viitab neile. Samas, kui viimane nendega seotud tehing lĂ”ppeb, vĂ”ivad lehed uuesti andmete jaoks kasutada. Kui on vajalik, et objektid ellu jÀÀksid nende sĂŒnnitehingust, tuleb need siiski kopeerida.
- Readwrite tehingu puhul on viide saadud struktuuri-vÀÀrtusele kehtiv vaid kuni esimese muudatuse (andmete kirjutamine vÔi kustutamine) tegemiseni.
- Hoolimata sellest, et struktuur
NodeValueei ole tĂ€ieĂ”iguslik, vaid kĂ€rbitud (vt alajaotist âVĂ”tmete jĂ€rjestamine vĂ€lise vĂ”rdlejagaâ), vĂ”ib viidatud kaudu rahulikult selle vĂ€ljadele juurde pÀÀseda. Peamine on seda mitte dereferenseerida! - Struktuuri ei tohi mingil juhul muudetud viisil lĂ€bi saadud nĂ€itaja. KĂ”ik muudatused peavad toimuma ainult meetodi kaudu
mdb_put. Siiski, hoolimata soovist seda teha, ei Ă”nnestu see, kuna selle struktuuri asukoha mĂ€lupiirkond on seotud readonly-reĆŸiimis. - Faili remapimine protsessi aadressiruumi, nĂ€iteks maksimaalse salvestusmahu suurendamiseks funktsiooni kaudu
abil kehtetuks kĂ”ik entiteedid (kursused, tehingud, vĂ”tmed ja vÀÀrtused), mis on eelnevalt mootorilt saadud. Sellise sĂŒndmuste kĂ€igu arvesse vĂ”tmine koodis muudab selle oluliselt keerulisemaks. Kui aga virtuaalne mĂ€lu on teile tĂ”eliselt oluline, siis vĂ”ib see olla pĂ”hjus tutvuda kaugele arenenud forkigakeelab kĂ”ik tehingud ja nendega seotud entiteedid ĂŒldiselt ning nĂ€itajad loetud objektide puhul eraldi.
LĂ”puks on veel ĂŒks omadus nii petlik, et selle olemuse avamine ei mahtu lihtsalt veel ĂŒhte punkti. B-puu peatĂŒkis tĂ”in ma vĂ€lja skeemi selle lehtede paigutusest mĂ€lus. Sealt jĂ€reldub, et serialiseeritud andmete puhvri alguse aadress vĂ”ib olla tĂ€iesti meelevaldne. SeetĂ”ttu saadud nĂ€itaja nendele, mis on struktuuris MDB_val ja mis on muudetud struktuuri nĂ€itajaks, osutub ĂŒldiselt mittesobivaks. Samas mĂ”ne kiibi arhitektuurid (iOS-i puhul armv7) nĂ”uavad, et mis tahes andmete aadress oleks sĂ”na suuruse vĂ”i teisisĂ”nu, sĂŒsteemi bitisuse jĂ€rgi (armv7 puhul on see 32 bitti). TeisisĂ”nu, selline operatsioon nagu *(int *foo)0x800002 moodustab pĂ”genemise ja viib surmaotsuseni EXC_ARM_DA_ALIGN. Sellise kurva saatuse vĂ€ltimiseks on kaks viisi.
Esimene seisneb andmete eelnevas kopeerimises selgelt sobivasse struktuuri. NÀiteks kajastub 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 ei pruugi olla sobivad, kasutades atribuudina aligned(1). ARM-is on sama efekti vÔimalik kasutades atribuudina packed. Arvestades, et see toetab ka struktuuri mahukuse optimeerimist, nÀen seda meetodit eelistatavana, kuigi see toob kaasa andmete juurde pÀÀsemise kulude suurenemise.
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 ette nĂ€htud kursori abstraktsioon, et itereerida ĂŒle rekordigruppide. Kuidas sellega töötada, vaatame juba tuttava kasutaja pilve metaandmete tabeli nĂ€itel.
Failide loetelu kuvamise raames on vaja leida kĂ”ik vĂ”tmed, millega on seotud tema alamfailid ja kaustad. Varasemates alajaotustes oleme vĂ”tnud vĂ”tmed sorteeritud NodeKey nii, et need oleksid esmalt jĂ€rjestatud vanema katalooge identifikaatori jĂ€rgi. Seega tehniliselt ĂŒlesanne kausta sisu saamisest tĂ”lgendub kursori paigaldamiseks vĂ”tme prefiksiga rĂŒhma ĂŒlemisse piirini ning seejĂ€rel itereerida allapoole.

Ălemise piiri saab "otseselt" jĂ€rjestikuse otsingu abil leida. Selleks paigaldatakse kursori algus kogu vĂ”tme nimekirja ja siis inkremeeritakse nii kaua, kuni selle all on vanema katalooge identifikaator. Selle lĂ€henemisviisi on kaks ilmset puudust:
- Otsingu lineaarne keerukus, kuigi on teada, et puudes ja eelkÔige B-puudes saab seda teha logaritmilise ajaga.
- Mitte asjata tĂ”stetakse failist peamise mĂ€lusse kĂ”ik lehekĂŒljed, mis eelnevad otsitavale, mis on ÀÀrmiselt kulukas.
Ănneks on LMDB API-s ette nĂ€htud efektiivne viisi kursori algseks paigutamiseks. Selleks tuleb moodustada selline vĂ”ti, mille vÀÀrtus on kindlasti vĂ€iksem vĂ”i vĂ”rdne vĂ”tmega, mis asub vahemiku ĂŒlemisel piiril. NĂ€iteks rakendades ĂŒlaltoodud joonisel olevale nimekirjale, saame luua sellise vĂ”tme, mille vĂ€li parentId on vĂ”rdne 2, ja kĂ”ik ĂŒlejÀÀnud on tĂ€idetud nullidega. Selline osaliselt tĂ€idetud vĂ”ti edastatakse funktsiooni mdb_cursor_get operatsiooniga MDB_SET_RANGE.
NodeKey upperBoundSearchKey = {@n .parentId = 2,@n .type = 0,@n .nameLength = 0@n};@nMDB_val value, key = serialize(upperBoundSearchKey);@nMDB_cursor *cursor;@nmdb_cursor_open(..., &cursor);@nmdb_cursor_get(cursor, &key, &value, MDB_SET_RANGE);Kui vĂ”tmete grupi ĂŒlemine piir on leitud, itereerime selle ĂŒle seni, kuni kas kohtame kas vĂ”tme, mis on erinev parentId, vĂ”i vĂ”tmed lĂ”ppevad tĂ€ielikult.
do {@n rc = mdb_cursor_get(cursor, &key, &value, MDB_NEXT);@n // töötlemine...@n} while (MDB_NOTFOUND != rc && // tabeli lÔpu kontroll@n IsTargetKey(key)); // vÔtmete grupi lÔpu kontrollMugav on see, et itereerimise raames mdb_cursor_get kaudu saame mitte ainult vÔti, vaid ka vÀÀrtus. Kui valikutingimuste tÀitmiseks on vaja kontrollida ka vÀÀrtuse osa vÀljade pÔhjal, siis on need tÀiesti kergesti kÀttesaadavad ilma tÀiendavate pingutusteta.
4.3. Suhete modelleerimine tabelite vahel
Siiani oleme suutnud kĂ€sitleda kĂ”iki aspekte, mis puudutavad ĂŒhe tabeli andmebaasi projekteerimist ja kasutamist. Ătleks, et tabel on jĂ€rjestatud kirje kogum, mis koosneb ĂŒhesugustest vĂ”ti-vÀÀrtus paaridest. Kui kujutada vĂ”tme ristkĂŒlikuna ja sellega seotud vÀÀrtust paralleelpindana, saame andmebaasi visuaalse skeemi.
â
![]()
Kuid reaalelus harva Ônnestub sedavÔrd vÀhe, pÔhjustamata suuri probleeme. Tihti on andmebaasis vajalik, esiteks, mitu tabelit, ja teiseks, teostada valikuid muul viisil kui esmase vÔtme jÀrgi. Selle viimase jaotise eesmÀrk on just nende loomine ja omavaheline sidumine.
Indekseerimise tabelid
Pilve rakenduses on jaotus 'Galerii'. Seal kuvatakse meedia sisu kogu pilves, sorteeritud kuupĂ€eva jĂ€rgi. Selle valiku optimaalseks teostamiseks on lisaks pĂ”hitaabelile vaja luua veel ĂŒks tabel uue vĂ”tme tĂŒĂŒbiga. Sellest leiab vĂ€lja loomise kuupĂ€eva vĂ€lja, mis on peamine sorteerimise kriteerium. Kuna uued vĂ”tmed viitavad neile samadele andmetele, mis on pĂ”hitaabelis, nimetatakse neid indekseerivateks. Alloleval pildil on need esitatud oranĆŸiga.

Selleks, et ĂŒhes andmebaasis eristada erinevate tabelite vĂ”tmeid, on kĂ”igile lisatud tĂ€iendav tehniline vĂ€li tableId. Kui teha see kĂ”ige olulisemaks sortimise aluseks, saavutame vĂ”tmete grupi esmalt tabelite kaupa ja seejĂ€rel oma reeglite jĂ€rgi tabelite sees.
Indekseeriv vÔti viitab samadele andmetele nagu esmamine. Selle omaduse sirgjooneline teostamine esmase vÔtme vÀÀrtuse osa koopiaga seondudes on mitmest vaatenurgast ebatÔhus.
- Kohandatud ruumi kasutamise seisukohalt, arvestades, et metainformatsioon vĂ”ib olla ĂŒsna rikkalik.
- Töötamise efektiivsuse seisukohalt, kuna metainfot vÀrskendades tuleb kahe vÔtme jÀrgi kirjutada.
- Koodi toetuse seisukohalt, kui me unustame ĂŒhe vĂ”tme andmed ajakohastada, saame me raskesti jĂ€lgitava andmete jĂ€rjepidevuse vea andmehoidlas.
Edasi vaatame, kuidas neid puudusi likvideerida.
Seoste loomine tabelite vahel
Ajakohase tabeli seostamiseks pĂ”hitabletiga sobib hĂ€sti muster âvĂ”ti vÀÀrtusenaâ. Nagu nimigi ĂŒtleb, toimib vÀÀrtusena indeksikande koopia primaarvĂ”tme vÀÀrtusest. See lĂ€henemine likvideerib kĂ”ik eelnevalt loetletud puudused, mis on seotud primaarandme vÀÀrtuse koopia salvestamisega. Ainsaks tasuks on see, et indeksivĂ”tme jĂ€rgi vÀÀrtuse saamiseks tuleb teha 2 pĂ€ringut andmebaasi asemel 1. Saadud andmebaasi skeem nĂ€eb vĂ€lja jĂ€rgnev.

Ehkki teiseks tabelitevaheliste seadete korraldamise mustriks on âliigne vĂ”tiâ. Selle pĂ”hisisu seisneb vĂ”tme tĂ€iendavate atribuutide lisamises, mis ei ole vajalikud sorteerimiseks, vaid seotud vĂ”tme taastamiseks. Mail.ru pilve rakenduses on selle kasutamiseks reaalsed nĂ€ited, kuid sĂŒgavale iOS raamistikku tungimise vĂ€ltimiseks toon vĂ€lja vĂ€ljamĂ”eldud, kuid rohkem arusaadava nĂ€ite.
Pilve mobiiliklientides on leht, kus kuvatakse kĂ”ik failid ja kaustad, millele kasutaja on teistele inimestele ligipÀÀsu andnud. Kuna selliseid faile on suhteliselt vĂ€he, ja erinevat tĂŒĂŒpi sellega seotud spetsiifiline teave avalikkuse kohta on palju (kellele on antud juurdepÀÀs, milliste Ă”igustega jne), oleks ebatĂ”hus koormata peamise tabeli vÀÀrtusosa selle teabega. Siiski, kui soovitakse selliseid faile ekraanil kuvada, tuleb neid mingil viisil siiski hoida. Loomulik lahendus on selle jaoks eraldi tabeli loomine. Alloleval skeemil on selle vĂ”tme eesliide âPâ, samas kui kohatĂ€itja âpropnameâ vĂ”ib asendada konkreetsema vÀÀrtusega âavalik teaveâ.

KÔik unikaalsed metaandmed, mille jaoks uus tabel loodi, kantakse kirje value-osasse. Samas ei soovi dubleerida andmeid failide ja kaustade kohta, mis juba pÔhitoimingutesse talletatud on. Selle asemel lisatakse vÔtmesse "P" liialdatud andmed nagu "node ID" ja "timestamp". Just nende abil saab konstrueerida indeksivÔtme, mille kaudu leida primaarvÔti, mille kaudu lÔpuks saada node'i metaandmed.
KokkuvÔte
Hindame LMDB rakenduse elluviimist positiivselt. PÀrast selle kasutuselevÔttu vÀhenes rakenduse hangumise arv 30%.

Tehttud töö tulemused said vastukaja ka vĂ€ljaspool iOS meeskonda. Praegu on ĂŒks peamisi jaotisi âFailidâ Androidi rakenduses samuti ĂŒle lĂ€inud LMDB kasutamisele ja teised komponendid on samuti teel. C keel, milles key-value andmehoidla on rakendatud, on olnud suureks abiks, et esialgu teha rakenduslik sidumine selle ĂŒmber platvormideĂŒleselt C++ keeles. Sujuv ĂŒhendamine saadud C++ raamatukoguga Objective-C ja Kotlin platvormikoodiga saavutas koodigeneratsiooni tööriist. Dropboxist, kuid see on juba hoopis teine lugu.
Allikas: habr.com
