De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Yn 'e hjerst fan 2019 barde in langverwachte barren yn it Mail.ru Cloud iOS-team. De haaddatabank foar oanhâldende opslach fan tapassing fan tapassing is heul eksoatysk wurden foar de mobile wrâld Lightning Memory-Mapped Database (LMDB). Under de besuniging biede wy jo in detaillearre resinsje derfan yn fjouwer dielen. Lit ús earst prate oer de redenen foar sa'n net-triviale en drege kar. Dan sille wy fierder gean om de trije pylders te beskôgjen yn it hert fan 'e LMDB-arsjitektuer: bestannen yn kaart brocht mei ûnthâld, B + -beam, copy-on-write oanpak foar ymplemintaasje fan transaksje en multiversion. As lêste, foar dessert - it praktyske diel. Dêryn sille wy sjen hoe't jo in databankskema ûntwerpe en ymplementearje mei ferskate tabellen, ynklusyf in yndeks, boppe op 'e leech-nivo kaai-wearde API.

Ynhâld

  1. Motivaasje foar útfiering
  2. LMDB Posysje
  3. Trije pylders fan LMDB
    3.1. Walfisk #1. Unthâld-mapped triemmen
    3.2. Walfisk #2. B+-beam
    3.3. Walfisk #3. Kopiearje-op-skriuwe
  4. Untwerp fan in gegevensskema boppe op 'e kaai-wearde API
    4.1. Basis abstraksjes
    4.2. Tabel Modeling
    4.3. Modeling relaasjes tusken tabellen

1. Motivaasje foar útfiering

Ien jier yn 2015 namen wy de muoite om te mjitten hoe faak de ynterface fan ús applikaasje efterbliuwt. Wy diene dit foar in reden. Wy hawwe faker klachten krigen dat de applikaasje soms ophâldt te reagearjen op brûkersaksjes: knoppen kinne net yndrukt wurde, listen scrollen net, ensfh. Oer de meganika fan mjittingen ferteld op AvitoTech, dus hjir jou ik allinich de folchoarder fan nûmers.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

De mjitresultaten waarden foar ús in kâlde dûs. It die bliken dat der folle mear problemen wurde feroarsake troch befriezen as hokker oar. As foardat it realisearjen fan dit feit de wichtichste technyske yndikator fan kwaliteit wie crashfrij, dan nei de fokus ferskood op freeze frij.

Bouwe hawwe dashboard mei freezes en nei útjeften kwantitatyf и kwaliteit analyze fan harren redenen, de wichtichste fijân waard dúdlik - swiere saaklike logika útfierd yn de wichtichste tried fan de applikaasje. De natuerlike reaksje op dizze skande wie in brânende winsk om it yn wurkstreamen te skowen. Om dit probleem systematysk op te lossen, hawwe wy taflecht ta in multi-threaded arsjitektuer basearre op lichtgewicht akteurs. Ik wijd it oan syn oanpassing foar de iOS-wrâld twa triedden op kollektyf Twitter en artikel oer Habré. As ûnderdiel fan it hjoeddeiske ferhaal wol ik dy aspekten fan it beslút beklamje dy't de kar fan de databank beynfloede.

It akteursmodel fan systeemorganisaasje giet derfan út dat multithreading syn twadde essinsje wurdt. Modelobjekten dêryn wolle graach streamgrinzen oerstekke. En dat dogge se net soms en hjir en dêr, mar hast konstant en oeral...

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

De databank is ien fan 'e hoekstienkomponinten yn it presintearre diagram. Syn wichtichste taak is it útfieren fan it makropatroan Dielde databank. As it yn 'e ûndernimmingswrâld wurdt brûkt om gegevenssyngronisaasje tusken tsjinsten te organisearjen, dan yn it gefal fan akteursarsjitektuer - gegevens tusken threaden. Sa hienen wy in databank nedich dy't net sels minimale swierrichheden soe feroarsaakje by it wurkjen mei har yn in multi-threaded omjouwing. Benammen betsjut dit dat objekten dy't derfan krigen binne op syn minst thread-feilich wêze moatte, en by útstek folslein net-feroarlik. Sa't jo witte, de lêste kin tagelyk brûkt wurde út ferskate triedden sûnder taflecht te nimmen oan eltse beskoattelje, dat hat in geunstich effekt op prestaasjes.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjesDe twadde wichtige faktor dy't de kar fan databank beynfloede wie ús wolk API. It waard ynspirearre troch de syngronisaasje oanpak oannommen troch git. Lykas him, wy rjochte op offline-earste API, dy't mear as passend sjocht foar wolkkliïnten. Der waard oannommen dat se de folsleine steat fan 'e wolk mar ien kear útpompe, en dan soe syngronisaasje yn' e oerweldigjende mearderheid fan 'e gefallen foarkomme troch feroaringen út te rôljen. Och, dizze kâns is noch allinich yn 'e teoretyske sône, en kliïnten hawwe net leard hoe't se mei patches yn' e praktyk wurkje. Dêr binne in oantal objektive redenen foar, dy't wy, om de yntroduksje net te fertrage, efter heakjes litte. No, wat fan folle mear belang is, binne de ynstruktive konklúzjes fan 'e les oer wat bart as in API "A" seit en har konsumint gjin "B" seit.

Dus, as jo git foarstelle, dy't, by it útfieren fan in pull-kommando, ynstee fan it tapassen fan patches op in lokale momintopname, syn folsleine tastân fergelike mei de folsleine serverstatus, dan sille jo in frij krekt idee hawwe fan hoe't syngronisaasje yn 'e wolk bart kliïnten. It is maklik te rieden dat om it te ymplementearjen, jo twa DOM-beammen yn it ûnthâld moatte tawize mei meta-ynformaasje oer alle server- en lokale bestannen. It docht bliken dat as in brûker 500 tûzen bestannen yn 'e wolk opslacht, dan is it nedich om twa beammen mei 1 miljoen knopen opnij te meitsjen en te ferneatigjen om it te syngronisearjen. Mar elke knooppunt is in aggregaat mei in grafyk fan subobjekten. Yn dit ljocht waarden de profilearringsresultaten ferwachte. It die bliken dat sels sûnder rekken hâlden mei it gearfoegjen algoritme, de proseduere fan it meitsjen en dêrnei ferneatigje in grut oantal lytse objekten kostet in moaie penny. fan brûkersskripts. As resultaat reparearje wy it twadde wichtige kritearium by it kiezen fan in databank - de mooglikheid om CRUD-operaasjes út te fieren sûnder dynamyske tawizing fan objekten.

Oare easken binne tradisjoneeler en har hiele list is as folget.

  1. Thread feilichheid.
  2. Multiferwurking. Diktearre troch de winsk om deselde database-eksimplaar te brûken om steat te syngronisearjen net allinich tusken diskusjes, mar ek tusken de haadapplikaasje en iOS-útwreidingen.
  3. De mooglikheid om bewarre entiteiten te fertsjintwurdigjen as net-feroarbere objekten
  4. Gjin dynamyske allocaasjes binnen CRUD-operaasjes.
  5. Transaksjestipe foar basiseigenskippen acid: atomiteit, konsistinsje, isolaasje en betrouberens.
  6. Faasje op de meast populêre gefallen.

Mei dizze set fan easken wie en bliuwt SQLite in goede kar. As ûnderdiel fan it ûndersyk nei alternativen kaam ik lykwols in boek tsjin "Begjinne mei LevelDB". Under har lieding waard in benchmark skreaun dy't de snelheid fan wurk fergelike mei ferskate databases yn echte wolkenscenario's. It resultaat oertrof ús wyldste ferwachtingen. Yn 'e populêrste gefallen - in rinnerke krije op in sorteare list fan alle bestannen en in sorteare list fan alle bestannen foar in opjûne map - die bliken dat LMDB 10 kear rapper wie as SQLite. De kar waard dúdlik.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

2. LMDB Posysje

LMDB is in heul lytse bibleteek (allinich 10K rigen) dy't de leechste fûnemintele laach fan databases ymplemintearret - opslach.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

It boppesteande diagram lit sjen dat it fergelykjen fan LMDB mei SQLite, dy't ek hegere nivo's ymplementearret, oer it algemien net korrekter is as SQLite mei Core Data. It soe earliker wêze om deselde opslachmotoren te neamen as lykweardige konkurrinten - BerkeleyDB, LevelDB, Sophia, RocksDB, ensfh. D'r binne sels ûntjouwings wêr't LMDB fungearret as in opslachmotorkomponint foar SQLite. It earste sa'n eksperimint wie yn 2012 bestege by LMDB Howard Chu. Resultaten die bliken sa yntrigearjend dat syn inisjatyf waard oppakt troch OSS-entûsjasters, en fûn syn fuortsetting yn 'e persoan LumoSQL. Yn jannewaris 2020 wie de skriuwer fan dit projekt Den Shearer presintearre it by LinuxConfAu.

LMDB wurdt benammen brûkt as motor foar applikaasjedatabases. De biblioteek hat syn uterlik te tankjen oan de ûntwikkelders OpenLDAP, dy't tige ûntefreden wiene mei BerkeleyDB as basis foar har projekt. Utgeande fan in beskieden bibleteek btree, Howard Chu koe ien fan 'e populêrste alternativen fan ús tiid meitsje. Hy wijd syn tige koele ferslach oan dit ferhaal, en ek oan de ynterne struktuer fan LMDB. "The Lightning Memory-mapped Database". In goed foarbyld fan it feroverjen fan in opslachfoarsjenning waard dield troch Leonid Yuryev (aka yleo) fan Positive Technologies yn syn rapport by Highload 2015 "De LMDB-motor is in spesjale kampioen". Dêryn hat er it oer LMDB yn it ramt fan in soartgelikense taak fan it útfieren fan ReOpenLDAP, en LevelDB hat al ûnder ferlykjende krityk west. As gefolch fan 'e ymplemintaasje hie Positive Technologies sels in aktyf ûntwikkeljende gabel MDBX mei hiel lekker funksjes, optimalisaasjes en bugfixes.

LMDB wurdt faak brûkt as in as-is opslach. Bygelyks, Mozilla Firefox browser keazen it foar in oantal behoeften, en, útgeande fan ferzje 9, Xcode foarkar syn SQLite foar it bewarjen fan yndeksen.

De motor hat ek syn mark makke yn 'e wrâld fan mobile ûntwikkeling. Spoaren fan har gebrûk kinne wêze fyn yn 'e iOS-client foar Telegram. LinkedIn gie noch fierder en keas LMDB as de standert opslach foar har ynheemse data-caching-ramt Rocket Data, oer hokker ferteld yn syn artikel yn 2016.

LMDB fjochtet mei súkses foar in plak yn 'e sinne yn' e niche dy't troch BerkeleyDB efterlitten is nei't it ûnder de kontrôle fan Oracle kaam. De bibleteek is leaf foar syn snelheid en betrouberens, sels yn ferliking mei syn leeftydsgenoaten. Lykas jo witte, binne d'r gjin fergese lunches, en ik wol de ôfwikseling beklamje dy't jo sille moatte tsjinkomme as jo kieze tusken LMDB en SQLite. It diagram hjirboppe lit dúdlik sjen hoe't ferhege snelheid wurdt berikt. Earst betelje wy net foar ekstra lagen fan abstraksje boppe op skiifopslach. It is dúdlik dat in goede arsjitektuer noch net sûnder har kin, en se sille ûnûntkomber ferskine yn 'e applikaasjekoade, mar se sille folle subtiler wêze. Se sille gjin funksjes befetsje dy't net nedich binne troch in spesifike applikaasje, bygelyks stipe foar queries yn 'e SQL-taal. Twads, it wurdt mooglik om optimaal útfiere mapping fan tapassing operaasjes op fersiken nei skiif opslach. As SQLite yn myn wurk is basearre op de gemiddelde statistyske behoeften fan in gemiddelde applikaasje, dan binne jo, as applikaasje-ûntwikkelder, goed bewust fan 'e wichtichste senario's foar wurkdruk. Foar in mear produktive oplossing sille jo in ferhege priiskaartsje moatte betelje sawol foar de ûntwikkeling fan 'e earste oplossing as foar de folgjende stipe.

3. Trije pylders fan LMDB

Nei't de LMDB út in fûgelperspektyf sjoen hie, waard it tiid om djipper te gean. De folgjende trije seksjes sille wijd wurde oan in analyze fan 'e wichtichste pylders dêr't de opslacharsjitektuer op rust:

  1. Unthâld-mapped triemmen as meganisme foar wurkjen mei skiif en syngronisearje ynterne gegevens struktueren.
  2. B + -beam as organisaasje fan 'e struktuer fan bewarre gegevens.
  3. Kopiearje-op-skriuwe as in oanpak om ACID-transaksje-eigenskippen en multiversion te leverjen.

3.1. Walfisk #1. Unthâld-mapped triemmen

Unthâld-mapped triemmen binne sa'n wichtich arsjitektoanysk elemint dat se sels ferskine yn de namme fan it repository. Kwestjes fan caching en syngronisaasje fan tagong ta opsleine ynformaasje wurde folslein oerlitten oan it bestjoeringssysteem. LMDB befettet gjin caches yn himsels. Dit is in bewuste beslút fan 'e auteur, om't it lêzen fan gegevens direkt út mappen bestannen kinne jo in protte hoeken snije yn' e ymplemintaasje fan 'e motor. Hjirûnder is in fier fan folsleine list fan guon fan harren.

  1. It behâld fan de konsistinsje fan gegevens yn 'e opslach by it wurkjen mei it út ferskate prosessen wurdt de ferantwurdlikens fan it bestjoeringssysteem. Yn 'e folgjende paragraaf wurdt dizze meganika yn detail en mei foto's besprutsen.
  2. It ûntbrekken fan caches elimineert LMDB folslein út 'e overhead ferbûn mei dynamyske allocaasjes. It lêzen fan gegevens yn 'e praktyk betsjut in oanwizer ynstelle nei it juste adres yn firtuele ûnthâld en neat mear. It klinkt as science fiction, mar yn 'e opslachboarnekoade binne alle oproppen nei calloc konsintrearre yn' e opslachkonfiguraasjefunksje.
  3. It ûntbrekken fan caches betsjut ek it ûntbrekken fan slûzen ferbûn mei syngronisaasje fan harren tagong. Lêzers, wêrfan't der tagelyk in willekeurich tal lêzers fan wêze kin, komme net ien mutex tsjin op wei nei de gegevens. Hjirtroch hat de lêssnelheid ideale lineêre skalberens basearre op it oantal CPU's. Yn LMDB wurde allinich wizigingsoperaasjes syngronisearre. Der kin mar ien skriuwer tagelyk wêze.
  4. In minimum fan caching en syngronisaasje logika elimineert it ekstreem komplekse type flaters ferbûn mei wurkjen yn in multi-threaded omjouwing. D'r wiene twa ynteressante databasestúdzjes op 'e Usenix OSDI 2014 konferinsje: "Alle bestânsystemen binne net lyk makke: oer de kompleksiteit fan it meitsjen fan crash-konsistente applikaasjes" и "Marteljen fan databases foar wille en winst". Fan har kinne jo ynformaasje sammelje oer sawol de ungewoane betrouberens fan LMDB as de hast flawless ymplemintaasje fan ACID-transaksje-eigenskippen, dy't superieur is oan dy fan SQLite.
  5. It minimalisme fan LMDB lit de masinefertsjintwurdiging fan har koade folslein pleatse yn 'e L1-cache fan' e prosessor mei de folgjende snelheidskarakteristiken.

Spitigernôch, yn iOS, mei ûnthâld-mapped triemmen, alles is net sa wolkeloos as wy wolle. Om mear bewust te praten oer de tekoartkommingen dy't mei har ferbûn binne, is it needsaaklik om de algemiene prinsipes fan it útfieren fan dit meganisme yn bestjoeringssystemen te ûnthâlden.

Algemiene ynformaasje oer ûnthâld-mapped triemmen

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjesMei elke applikaasje dy't rint, assosjearret it bestjoeringssysteem in entiteit neamd in proses. Elk proses wurdt tawiisd in oanienwei berik fan adressen wêryn it pleatst alles wat it nedich is om te operearjen. Op de leechste adressen binne d'r seksjes mei koade en hurdkodearre gegevens en boarnen. Dêrnei komt in groeiend blok fan dynamyske adresromte, ús bekend ûnder de namme heap. It befettet de adressen fan entiteiten dy't ferskine tidens de wurking fan it programma. Oan 'e boppekant is it ûnthâldgebiet dat wurdt brûkt troch de applikaasjestapel. It groeit of krimpt; mei oare wurden, syn grutte hat ek in dynamyske aard. Om foar te kommen dat de steapel en de heap inoar drukke en bemuoie, sitte se oan ferskillende einen fan de adresromte. It bestjoeringssysteem brûkt adressen yn dizze middelste seksje om in ferskaat oan entiteiten te assosjearjen mei it proses. Benammen kin it in beskate trochgeande set fan adressen assosjearje mei in bestân op 'e skiif. Sa'n bestân wurdt memory-mapped neamd

De adresromte tawiisd oan it proses is enoarm. Teoretysk wurdt it oantal adressen allinich beheind troch de grutte fan 'e oanwizer, dy't bepaald wurdt troch de bitkapasiteit fan it systeem. As fysyk ûnthâld derop 1-op-1 wurde yn kaart brocht, dan soe it earste proses de hiele RAM opslokje, en der soe gjin sprake wêze fan multitasking.

Ut ús ûnderfining witte wy lykwols dat moderne bestjoeringssystemen tagelyk safolle prosessen kinne útfiere as winske. Dit is mooglik troch it feit dat se allinich in protte ûnthâld tawize oan prosessen op papier, mar yn 'e realiteit laden se allinich dat diel dat hjir en no yn fraach is yn' e fysike haadûnthâld. Dêrom wurdt it ûnthâld ferbûn mei in proses firtuele neamd.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

It bestjoeringssysteem organisearret firtuele en fysike ûnthâld yn siden fan in bepaalde grutte. Sadree't in bepaalde side fan firtuele ûnthâld yn fraach is, laadt it bestjoeringssysteem it yn fysyk ûnthâld en komt se oerien mei in spesjale tabel. As d'r gjin frije slots binne, dan wurdt ien fan 'e earder laden siden kopieare nei de skiif, en de iene yn' e fraach nimt syn plak. Dizze proseduere, dêr't wy ynkoarten op weromkomme, wurdt wikseljen neamd. De figuer hjirûnder yllustrearret it beskreaune proses. Dêrop waard side A mei adres 0 laden en pleatst op de haadûnthâldside mei adres 4. Dit feit waard wjerspegele yn 'e korrespondinsjetabel yn selnûmer 0.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

It ferhaal is krekt itselde mei triemmen yn kaart brocht nei it ûnthâld. Logysk binne se sabeare kontinu en folslein yn 'e firtuele adresromte lizze. Se geane lykwols fysyk ûnthâld yn side foar side en allinich op oanfraach. Modifikaasje fan sokke siden wurdt syngronisearre mei de triem op skiif. Op dizze manier kinne jo triem-I/O útfiere troch gewoan te wurkjen mei bytes yn it ûnthâld - alle wizigingen wurde automatysk troch de kernel fan it bestjoeringssysteem oerbrocht nei it boarnebestân.

De ôfbylding hjirûnder lit sjen hoe't LMDB syn steat syngronisearret by it wurkjen mei de databank út ferskate prosessen. Troch it firtuele ûnthâld fan ferskate prosessen yn itselde bestân yn kaart te bringen, ferplichtsje wy it bestjoeringssysteem de facto om bepaalde blokken fan har adresromten transityf mei elkoar te syngronisearje, wêr't LMDB sjocht.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

In wichtige nuânse is dat LMDB, standert, wiziget de gegevens triem fia de skriuw systeem oprop meganisme, en toant de triem sels yn read-allinne modus. Dizze oanpak hat twa wichtige gefolgen.

De earste konsekwinsje is mienskiplik foar alle bestjoeringssystemen. De essinsje dêrfan is om beskerming te foegjen tsjin ûnbedoelde skea oan 'e databank troch ferkearde koade. Lykas jo witte, binne de útfierbere ynstruksjes fan in proses fergees om tagong te krijen ta gegevens fan oeral yn syn adresromte. Tagelyk, sa't wy krekt ûnthâlden, it werjaan fan in bestân yn lês-skriuwmodus betsjut dat elke ynstruksje it ek kin wizigje. As se dit per fersin docht, besykje, bygelyks, in array-elemint feitlik te oerskriuwen by in net-besteande yndeks, dan kin se per ongelok it bestân feroarje dat is yn kaart brocht op dit adres, wat sil liede ta korrupsje fan 'e databank. As it bestân wurdt werjûn yn allinich-lêsmodus, dan sil in besykjen om de oerienkommende adresromte te feroarjen liede ta in needbeëiniging fan it programma mei in sinjaal SIGSEGV, en de triem sil yntakt bliuwe.

De twadde konsekwinsje is al spesifyk foar iOS. Noch de auteur noch in oare boarne neame it eksplisyt, mar sûnder it soe LMDB net geskikt wêze om te rinnen op dit mobile bestjoeringssysteem. De folgjende paragraaf is wijd oan har oerweging.

Spesifikaasjes fan bestannen yn kaart brocht mei ûnthâld yn iOS

D'r wie in prachtich rapport by WWDC yn 2018 "iOS Memory Deep Dive". It fertelt ús dat yn iOS alle siden dy't yn fysyk ûnthâld lizze ien fan 3 soarten binne: smoarch, komprimearre en skjin.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Skjin ûnthâld is in samling siden dy't sûnder pine út it fysike ûnthâld kinne wurde laden. De gegevens dy't se befetsje kinne as nedich wurde opnij laden fan har oarspronklike boarnen. Allinnich-lêzen ûnthâld-mapped triemmen falle yn dizze kategory. iOS is net benaud om op elk momint de siden yn kaart te bringen út it ûnthâld te ûntladen, om't se garandearre wurde syngronisearre mei it bestân op skiif.

Alle wizige siden einigje yn smoarch ûnthâld, nettsjinsteande wêr't se oarspronklik leine. Benammen ûnthâld-mapped triemmen wizige troch skriuwen nei it firtuele ûnthâld assosjearre mei harren sille wurde klassifisearre op dizze wize. Iepening LMDB mei flagge MDB_WRITEMAP, neidat jo der wizigingen yn makke hawwe, kinne jo dit persoanlik ferifiearje.​

Sadree't in applikaasje tefolle fysyk ûnthâld begjint te nimmen, stelt iOS it oan smoarge side-kompresje. It totale ûnthâld beset troch smoarge en komprimearre siden foarmje de applikaasje syn saneamde ûnthâld footprint. Sadree't it in bepaalde drompelwearde berikt, komt de OOM-killersysteemdaemon nei it proses en beëiniget it mei geweld. Dit is de eigenaardichheid fan iOS yn ferliking mei buroblêd-bestjoeringssystemen. Yn tsjinstelling, it ferminderjen fan it ûnthâld foetôfdruk troch it wikseljen fan siden fan fysyk ûnthâld nei skiif is net foarsjoen yn iOS. De redenen kinne allinnich wurde rieden op. Miskien is de proseduere fan yntinsyf ferpleatsen fan siden nei skiif en werom te enerzjyferbrûkt foar mobile apparaten, of iOS besparret de boarne fan it herskriuwen fan sellen op SSD-skiven, of miskien wiene de ûntwerpers net tefreden mei de algemiene prestaasjes fan it systeem, wêr't alles is hieltyd wiksele. Hoe dan ek, it feit bliuwt in feit.

It goede nijs, al earder neamd, is dat LMDB standert net it mmap-meganisme brûkt om bestannen te aktualisearjen. Dit betsjut dat de werjûn gegevens wurdt klassifisearre troch iOS as skjin ûnthâld en net bydraacht oan it ûnthâld footprint. Jo kinne dit ferifiearje mei in Xcode-ark neamd VM Tracker. It skermôfbylding hjirûnder toant de tastân fan it iOS firtuele ûnthâld fan 'e Cloud-applikaasje tidens operaasje. Oan it begjin waarden 2 LMDB-eksimplaren dêryn inisjalisearre. De earste mocht syn bestân werjaan op 1GiB fan firtuele ûnthâld, de twadde - 512MiB. Nettsjinsteande it feit dat beide opslach in beskate hoemannichte bewennerûnthâld ynnimme, draacht net ien fan har smoarge grutte by.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

En no is it tiid foar min nijs. Mei tank oan it wikselmeganisme yn 64-bit buroblêd-bestjoeringssystemen kin elk proses safolle firtuele adresromte ynnimme as de frije hurde skiifromte foar syn potinsjele swap mooglik makket. It ferfangen fan swap mei kompresje yn iOS ferminderet it teoretyske maksimum radikaal. No moatte alle libbene prosessen passe yn it haad (lêzen RAM) ûnthâld, en al dyjingen dy't net passe moatte wurde twongen om te beëinigjen. Dit wurdt oanjûn lykas yn it hjirboppe neamde meldeen yn offisjele dokumintaasje. As gefolch beheint iOS de hoemannichte ûnthâld dat beskikber is foar tawizing fia mmap sterk. Hjir hjir Jo kinne sjen nei de empiryske grinzen fan it bedrach fan ûnthâld dat koe wurde tawiisd oan ferskate apparaten mei help fan dizze systeem oprop. Op de meast moderne smartphone modellen, iOS hat wurden royaal troch 2 gigabyte, en op top ferzjes fan de iPad - troch 4. Yn 'e praktyk, fansels, jo moatte rjochtsje op de leechste stipe apparaat modellen, dêr't alles is hiel tryst. Noch slimmer, troch te sjen nei de ûnthâldstatus fan 'e applikaasje yn VM Tracker, sille jo fine dat LMDB fier fan de iennichste is dy't beweart dat se ûnthâld-mapt binne. Goede stikken wurde opiten troch systeemallokators, boarnebestannen, byldkaders en oare lytsere rôfdieren.

Op grûn fan de resultaten fan eksperiminten yn 'e wolk kamen wy ta de folgjende kompromiswearden foar it ûnthâld tawiisd troch LMDB: 384 megabytes foar 32-bit-apparaten en 768 foar 64-bit-apparaten. Nei't dit folume is brûkt, begjinne alle wizigingsoperaasjes te einigjen mei de koade MDB_MAP_FULL. Wy observearje sokke flaters yn ús tafersjoch, mar se binne lyts genôch dat se yn dit stadium kinne wurde negeare.

In net foar de hân lizzende reden foar oermjittich ûnthâld konsumpsje troch de opslach kin wêze langlibben transaksjes. Om te begripen hoe't dizze twa ferskynsels ferbûn binne, sille wy holpen wurde troch de oerbleaune twa pylders fan 'e LMDB te beskôgjen.

3.2. Walfisk #2. B+-beam

Om tabellen boppe op in opslach foar kaaiwearden te emulearjen, moatte de folgjende operaasjes oanwêzich wêze yn har API:

  1. It ynfoegje fan in nij elemint.
  2. Sykje nei in elemint mei in opjûne kaai.
  3. It fuortsmiten fan in elemint.
  4. Iterearje oer yntervallen fan toetsen yn 'e folchoarder dat se sorteare.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjesDe ienfâldichste gegevensstruktuer dy't alle fjouwer operaasjes maklik útfiere kin is in binêre sykbeam. Elk fan syn knooppunten stiet foar in kaai dy't de hiele subset fan bernekaaien dielt yn twa subtrees. De lofter befettet dyjingen dy't lytser binne as de âlder, en de rjochter befettet dyjingen dy't grutter binne. It krijen fan in bestelde set kaaien wurdt berikt troch ien fan 'e klassike beamtraversals

Binêre beammen hawwe twa fûnemintele gebreken dy't foarkomme dat se effektyf binne as in skiif-basearre gegevensstruktuer. As earste is de mjitte fan har lykwicht ûnfoarspelber. D'r is in grut risiko om beammen te krijen wêryn de hichten fan ferskate tûken sterk ferskille kinne, wat de algoritmyske kompleksiteit fan it sykjen signifikant fergruttet yn ferliking mei wat ferwachte wurdt. Twad, de oerfloed fan cross-links tusken knooppunten ûntnimme binêre beammen fan lokaasje yn it ûnthâld. Slúte knooppunten (yn termen fan ferbinings tusken harren) kinne lizze op folslein ferskillende siden yn firtuele ûnthâld. As konsekwinsje kin sels in ienfâldige trochgong fan ferskate oanbuorjende knopen yn in beam besykje in ferlykber oantal siden te besykjen. Dit is in probleem sels as wy prate oer de effektiviteit fan binêre beammen as in gegevensstruktuer yn it ûnthâld, om't konstant rotearjende siden yn 'e prosessor-cache gjin goedkeap wille is. As it giet om it faak opheljen fan siden ferbûn mei knopen fan skiif, wurdt de situaasje folslein jammerdearlik.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjesB-beammen, as in evolúsje fan binêre beammen, losse de problemen op dy't yn 'e foarige paragraaf identifisearre binne. Earst binne se selsbalansearjend. Twads splitst elk fan har knooppunten de set fan bernekaaien net yn 2, mar yn M bestelde subsets, en it oantal M kin frij grut wêze, yn 'e oarder fan ferskate hûnderten, of sels tûzenen.

Dêrby:

  1. Elke knooppunt befettet in grut oantal al bestelde kaaien en de beammen binne tige koart.
  2. De beam krijt it eigendom fan lokaasje fan lokaasje yn it ûnthâld, om't kaaien dy't ticht yn wearde binne natuerlik njonken elkoar op deselde of oanbuorjende knopen lizze.
  3. It oantal transitknooppunten by it delkommen fan in beam by in sykaksje wurdt fermindere.
  4. It oantal doelknooppunten lêzen tidens berikfragen wurdt fermindere, om't elk fan har al in grut oantal bestelde kaaien befettet.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

LMDB brûkt in fariaasje fan 'e B-beam neamd in B + beam om gegevens op te slaan. It boppesteande diagram lit de trije soarten knopen sjen dy't deryn besteane:

  1. Oan 'e boppekant is de woartel. It materialiseart neat mear as it konsept fan in databank yn in pakhús. Binnen ien LMDB-eksimplaar kinne jo ferskate databases oanmeitsje dy't in yn kaart brochte firtuele adresromte diele. Elk fan harren begjint út syn eigen woartel.
  2. Op it leechste nivo binne de blêden. Se en allinich se befetsje de kaai-wearde-pearen opslein yn 'e databank. Trouwens, dit is de eigenaardichheid fan B+-beammen. As in reguliere B-beam weardedielen opslacht yn knopen fan alle nivo's, dan is de B + fariaasje allinich op 'e leechste. Nei't wy dit feit hawwe reparearre, sille wy it subtype fan 'e beam brûkt yn LMDB fierder neame gewoan in B-beam.
  3. Tusken de woartel en blêden binne d'r 0 of mear technyske nivo's mei navigaasje (tûke) knopen. Harren taak is om de sortearre set fan kaaien te dielen tusken de blêden.

Fysiek binne knopen blokken fan ûnthâld fan in foarbepaalde lingte. Harren grutte is in meardere fan 'e grutte fan ûnthâldsiden yn it bestjoeringssysteem, dat wy hjirboppe besprutsen. De knooppuntstruktuer wurdt hjirûnder werjûn. De koptekst befettet meta-ynformaasje, wêrfan de meast foar de hân lizzende bygelyks de kontrôlesum is. Dêrnei komt ynformaasje oer de offsets wêryn de sellen mei gegevens sitte. De gegevens kinne toetsen wêze, as wy it oer navigaasjeknooppunten hawwe, as folsleine kaai-wearde-pearen yn it gefal fan blêden.​ Jo kinne mear lêze oer de struktuer fan siden yn it wurk "Evaluaasje fan winkels mei hege prestaasjes key-wearde".

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Nei't wy de ynterne ynhâld fan sideknooppunten behannele hawwe, sille wy de LMDB B-beam fierder fertsjinwurdigje op in ferienfâldige manier yn 'e folgjende foarm.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Siden mei knopen wurde sequentially op skiif pleatst. Heger nûmere siden lizze oan 'e ein fan it bestân. Op de saneamde metaside stiet ynformaasje oer de offsets wêrmei de woartels fan alle beammen te finen binne. By it iepenjen fan in bestân scant LMDB de bestân side foar side fan ein oant begjin op syk nei in jildige metaside en fynt dêrtroch besteande databases.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

No, mei in idee fan 'e logyske en fysike struktuer fan gegevensorganisaasje, kinne wy ​​​​trochgean om de tredde pylder fan LMDB te beskôgjen. It is mei har help dat alle opslachwizigingen transaksjoneel en yn isolaasje fan elkoar foarkomme, wêrtroch de databank as gehiel it eigendom fan multiversion jaan.

3.3. Walfisk #3. Kopiearje-op-skriuwe

Guon B-beam operaasjes befetsje it meitsjen fan in searje feroarings oan syn knopen. Ien foarbyld is it tafoegjen fan in nije kaai oan in knooppunt dat al syn maksimale kapasiteit hat berikt. Yn dit gefal is it nedich, yn it foarste plak, de knooppunt yn twa te splitsen, en as twadde, in keppeling ta te foegjen oan it nije ûntsteande bernknooppunt yn syn âlder. Dizze proseduere is mooglik tige gefaarlik. As der om ien of oare reden (crash, stroomûnderbrekking, ensfh.) mar in part fan 'e feroarings fan' e searje foarkomt, dan sil de beam yn in ynkonsistinte steat bliuwe.

Ien tradisjonele oplossing foar it meitsjen fan in databank flater-tolerant is it tafoegjen fan in ekstra op-skiif gegevens struktuer neist de B-beam - in transaksje log, ek wol bekend as in write-ahead log (WAL). It is in bestân oan 'e ein wêrfan de bedoelde operaasje strikt skreaun wurdt foardat de B-beam sels feroaret. Sa, as gegevens korrupsje wurdt ûntdutsen by selsdiagnoaze, de databank rieplachte it log om himsels yn oarder te setten.

LMDB hat in oare metoade keazen as in fouttolerânsjemeganisme, neamd copy-on-write. Syn essinsje is dat ynstee fan it bywurkjen fan gegevens op in besteande side, it earst folslein kopiearret en makket alle wizigingen yn 'e kopy.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Folgjende, om de bywurke gegevens beskikber te wêzen, is it nedich om de keppeling te feroarjen nei it knooppunt dat aktueel wurden is yn syn âlderknooppunt. Om't it hjirfoar ek oanpast wurde moat, wurdt it ek foarôf kopiearre. It proses giet rekursyf troch oant de woartel. It lêste ding om te feroarjen is de gegevens op 'e meta-side

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

As it proses ynienen yn 'e fernijingsproseduere crasht, dan sil of in nije meta-side net oanmakke wurde, of it sil net folslein nei skiif skreaun wurde, en syn kontrôlesum sil ferkeard wêze. Yn ien fan dizze twa gefallen sille nije siden net te berikken wêze, mar âlde wurde net beynfloede. Dit elimineert de needsaak foar LMDB om foarút log te skriuwen om gegevenskonsistinsje te behâlden. De facto nimt de struktuer fan gegevens opslach op 'e skiif beskreaun hjirboppe tagelyk syn funksje. It ûntbrekken fan in eksplisyt transaksjelogboek is ien fan 'e funksjes fan LMDB dy't hege gegevenslêssnelheid leveret

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

It resultearjende ûntwerp, neamd allinich B-beam, biedt fansels transaksje-isolaasje en multi-ferzje. Yn LMDB is elke iepen transaksje ferbûn mei de op it stuit relevante beamwurde. Oant de transaksje foltôge is, sille de siden fan 'e beam dy't dêrmei ferbûn binne nea feroare of opnij brûkt wurde foar nije ferzjes fan 'e gegevens. Sa kinne jo sa lang wurkje as jo wolle mei krekt de set gegevens dy't op dat stuit relevant wie de transaksje waard iepene, sels as de opslach op dit stuit aktyf bywurke wurdt. Dit is de essinsje fan multiversion, wêrtroch LMDB in ideale gegevensboarne is foar ús leafste UICollectionView. Nei't jo in transaksje hawwe iepene, is d'r gjin needsaak om de ûnthâldfoetôfdruk fan 'e applikaasje te fergrutsjen troch aktuele gegevens hastich út te pompen yn wat yn-ûnthâldstruktuer, út eangst om mei neat te bliuwen. Dizze funksje ûnderskiedt LMDB fan deselde SQLite, dy't net kin opskeppe fan sa'n totale isolemint. Nei't jo twa transaksjes yn 'e lêste hawwe iepene en in bepaald record binnen ien fan har wiske, sil it net mear mooglik wêze om itselde rekord te krijen binnen de twadde oerbleaune.

De oare kant fan 'e munt is it mooglik signifikant hegere konsumpsje fan firtuele ûnthâld. De slide lit sjen hoe't de databankstruktuer der útsjen sil as it tagelyk wizige wurdt mei 3 iepen lêzen transaksjes dy't nei ferskate ferzjes fan 'e databank sjogge. Om't LMDB gjin knopen opnij brûke kin dy't berikber binne fan woartels ferbûn mei aktuele transaksjes, hat de winkel gjin oare kar as in oare fjirde root yn it ûnthâld ta te jaan en de wizige siden derûnder wer te klonen.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Hjir soe it nuttich wêze om de seksje te ûnthâlden oer bestannen yn kaart brocht mei ûnthâld. It liket derop dat it ekstra konsumpsje fan firtuele ûnthâld ús net folle soargen moat, om't it net bydraacht oan 'e ûnthâldfoetôfdruk fan' e applikaasje. Tagelyk waard it lykwols opmurken dat iOS heul sûch is yn it tawizen, en wy kinne net, lykas op in server of buroblêd, in LMDB-regio fan 1 terabyte leverje en hielendal net oer dizze funksje tinke. As it mooglik is, moatte jo besykje it libben fan transaksjes sa koart mooglik te meitsjen.

4. Untwerp fan in gegevens skema boppe op de kaai-wearde API

Litte wy ús API-analyse begjinne troch te sjen nei de basisabstraksjes levere troch LMDB: omjouwing en databases, kaaien en wearden, transaksjes en rinnerkes.

In notysje oer koadelisten

Alle funksjes yn 'e publike LMDB API jouwe it resultaat fan har wurk werom yn' e foarm fan in flaterkoade, mar yn alle folgjende listings wurdt de ferifikaasje derfan omwille fan 'e koarteheid weilitten. foarke C++ wrappers lmdbxx, wêryn flaters wurde materialisearre as C ++ útsûnderings.

As de rapste manier om LMDB te ferbinen mei in projekt foar iOS of macOS, stel ik myn CocoaPod foar POSLMDB.

4.1. Basic Abstraksjes

Miljeu

struktuer MDB_env is it depot fan 'e ynterne steat fan' e LMDB. Prefixed funksje famylje mdb_env kinne jo guon fan syn eigenskippen konfigurearje. Yn it ienfâldichste gefal, motor inisjalisaasje sjocht der sa út.

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

Yn 'e Mail.ru Cloud-applikaasje hawwe wy de standertwearden fan mar twa parameters feroare.

De earste is de grutte fan 'e firtuele adresromte wêrop it opslachbestân is yn kaart brocht. Spitigernôch, sels op itselde apparaat, kin de spesifike wearde signifikant ferskille fan run nei run. Om dizze funksje fan iOS te rekkenjen, wurdt it maksimale opslachvolume dynamysk selektearre. Begjinnend fan in bepaalde wearde, wurdt it sequentieel halve oant de funksje mdb_env_open sil net werom in resultaat oars as ENOMEM. Yn teory is d'r ek de tsjinoerstelde manier - tawize earst in minimum oan ûnthâld oan 'e motor, en dan, as flaters wurde ûntfongen, MDB_MAP_FULL, fergrutsje it. It is lykwols folle stikeliger. De reden is dat de proseduere foar re-allocating ûnthâld (remap) mei help fan de funksje mdb_env_set_map_size ûnjildich alle entiteiten (cursors, transaksjes, kaaien en wearden) earder ûntfongen fan de motor. It rekkenjen fan dizze beurt fan eveneminten yn 'e koade sil liede ta syn wichtige komplikaasje. As firtuele ûnthâld lykwols heul wichtich foar jo is, dan kin dit in reden wêze om de gabel dy't fier foarút gien is, yn tichterby te sjen MDBX, Wêr't ûnder de oankundige funksjes "automatyske on-the-fly databankgrutte oanpassing is".

De twadde parameter, wêrfan de standertwearde ús net paste, regelet de meganika fan garandearjen fan threadfeiligens. Spitigernôch hat op syn minst iOS 10 problemen mei stipe foar lokale opslach fan thread. Om dizze reden wurdt yn it hjirboppe foarbyld it repository iepene mei de flagge MDB_NOTLS. Neist dit wie it ek nedich foarke C++ wrapper lmdbxxfariabelen út te snijen mei dit attribút en dêryn.

Databases

De databank is in aparte B-beam-eksimplaar, dy't wy hjirboppe besprutsen. De iepening dêrfan fynt plak yn in transaksje, dy't earst in bytsje frjemd kin lykje.

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

Yndied, in transaksje yn LMDB is in opslach entiteit, net in spesifike databank entiteit. Dit konsept lit jo atomêre operaasjes útfiere op entiteiten dy't yn ferskate databases lizze. Yn teory iepenet dit de mooglikheid om tabellen te modellearjen yn 'e foarm fan ferskate databases, mar op ien kear naam ik in oar paad, hjirûnder yn detail beskreaun.

Keys en wearden

struktuer MDB_val modelleart it konsept fan sawol kaai as wearde. It repository hat gjin idee oer har semantyk. Foar har is wat oars gewoan in array fan bytes fan in opjûne grutte. De maksimale kaaigrutte is 512 bytes.

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

Mei help fan in komparator sortearret de winkel de kaaien yn oprinnende folchoarder. As jo ​​it net ferfange troch jo eigen, dan sil de standert brûkt wurde, dy't se byte-foar-byte yn leksikografyske folchoarder sortearret.

Transaksjes

De transaksjestruktuer wurdt yn detail beskreaun yn foarige haadstik, dus hjir sil ik koart har haadeigenskippen werhelje:

  1. Unterstützt alle basis eigenskippen acid: atomiteit, konsistinsje, isolaasje en betrouberens. Ik kin it net helpe mar te notearjen dat d'r in brek is yn termen fan duorsumens op macOS en iOS dy't waard reparearre yn MDBX. Jo kinne mear lêze yn har README.
  2. De oanpak fan multithreading wurdt beskreaun troch it skema "ienige skriuwer / meardere lêzers". Skriuwers blokkearje inoar, mar blokkearje de lêzers net. Lêzers blokkearje skriuwers of inoar net.
  3. Stipe foar geneste transaksjes.
  4. Multiversion stipe.

Multiversion yn LMDB is sa goed dat ik it yn aksje wol demonstrearje. Ut de koade hjirûnder kinne jo sjen dat elke transaksje wurket mei krekt de ferzje fan 'e databank dy't aktueel wie op it momint dat it waard iepene, folslein isolearre fan alle folgjende feroarings. It inisjalisearjen fan de opslach en it tafoegjen fan in testrekord deroan fertsjintwurdiget neat ynteressant, dus dizze rituelen wurde ûnder de spoiler litten.

It tafoegjen fan in test yngong

MDB_env *env;
MDB_dbi dbi;
MDB_txn *txn;

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

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

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

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

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

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

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

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

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

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

Ik riede oan dat jo besykje deselde trúk mei SQLite en sjen wat der bart.

Multiversion bringt heul moaie foardielen oan it libben fan in iOS-ûntwikkelder. Mei help fan dit pân kinne jo maklik en natuerlik oanpasse de fernijing rate fan de gegevens boarne foar skerm formulieren, basearre op brûkersûnderfining oerwagings. Litte wy bygelyks in funksje nimme fan 'e Mail.ru Cloud-applikaasje, lykas it automatysk laden fan ynhâld fan' e systeemmediagalery. Mei in goede ferbining kin de kliïnt ferskate foto's per sekonde tafoegje oan 'e server. As jo ​​bywurkje nei eltse download UICollectionView mei media-ynhâld yn 'e wolk fan' e brûker, kinne jo 60 fps ferjitte en soepel rôlje tidens dit proses. Om foarkommende skermupdates te foarkommen, moatte jo op ien of oare manier de taryf beheine wêrmei't gegevens feroarje yn 'e ûnderlizzende UICollectionViewDataSource.

As de databank gjin multiversion stipet en jo allinich mei de aktuele aktuele steat kinne wurkje, dan moatte jo om in tiidstabyl momintopname fan gegevens te meitsjen, it kopiearje nei ien of oare gegevensstruktuer yn it ûnthâld of nei in tydlike tabel. Elk fan dizze oanpak is heul djoer. Yn it gefal fan opslach yn it ûnthâld krije wy kosten sawol yn it ûnthâld, feroarsake troch it opslaan fan konstruearre objekten, en yn 'e tiid, ferbûn mei oerstallige ORM-transformaasjes. Wat de tydlike tafel oanbelanget, is dit in noch djoerder wille, allinich yn net-triviale gefallen sin.

De multiversion-oplossing fan LMDB lost it probleem op fan it behâld fan in stabile gegevensboarne op in heul elegante manier. It is genôch om gewoan in transaksje te iepenjen en voila - oant wy it foltôgje, wurdt de gegevensset garandearre fêst te stellen. De logika foar syn fernijingssnelheid is no folslein yn 'e hannen fan' e presintaasjelaach, sûnder overhead fan wichtige boarnen.

Cursors

Cursors jouwe in meganisme foar oarderlik iterearjen oer kaai-wearde-pearen fia B-beam-traversal. Sûnder harren soe it ûnmooglik wêze om de tabellen yn 'e databank effektyf te modellearjen, wêr't wy no nei gean.

4.2. Tabel Modeling

It eigendom fan kaaibestelling lit jo in abstraksje op heech nivo konstruearje, lykas in tabel boppe op basisabstraksjes. Litte wy dit proses beskôgje mei it foarbyld fan 'e haadtabel fan in wolkklient, dy't ynformaasje oer alle bestannen en mappen fan' e brûker cache.

Tabel skema

Ien fan de meast foarkommende senario's dêr't in tabelstruktuer mei in mapbeam foar oanpast wurde moat, is de seleksje fan alle eleminten dy't binnen in opjûne map sitte. In goed dataorganisaasjemodel foar sa effisjinte fragen is Adjacency List. Om it boppe op 'e kaai-wearde opslach út te fieren, is it nedich om de kaaien fan bestannen en mappen op sa'n manier te sortearjen dat se wurde groepeare op basis fan har lidmaatskip yn 'e âldermap. Derneist, om de ynhâld fan 'e map te werjaan yn' e foarm dy't bekend is foar in Windows-brûker (earst mappen, dan bestannen, beide alfabetysk sortearre), is it nedich om de oerienkommende ekstra fjilden yn 'e kaai op te nimmen.

De ôfbylding hjirûnder lit sjen hoe't, basearre op de taak by de hân, in fertsjintwurdiging fan kaaien yn 'e foarm fan in byte-array der útsjen kin. De bytes mei de identifier fan 'e âldermap (read) wurde earst pleatst, dan mei it type (grien) en yn 'e sturt mei de namme (blau). Troch de standert LMDB-fergeliker yn leksikografyske folchoarder te sortearjen, wurde se oardere yn 'e fereaske wize. Opfolgjende trochrinnende kaaien mei itselde reade foarheaksel jouwe ús har assosjearre wearden yn 'e folchoarder dy't se moatte wurde werjûn yn' e brûkersynterface (rjochts), sûnder dat ekstra neiferwurking nedich is.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Serialisearjen fan kaaien en wearden

In protte metoaden foar it serialisearjen fan objekten binne yn 'e wrâld útfûn. Om't wy gjin oare eask hiene oars as snelheid, hawwe wy foar ússels de fluchst mooglik keazen - in dump fan it ûnthâld beset troch in eksimplaar fan 'e taalstruktuer C. Sa kin de kaai fan in mapelemint mei de folgjende struktuer modelearre wurde NodeKey.

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

Bewarje NodeKey yn opslach nedich yn objekt MDB_val pleats de gegevensoanwizer nei it adres fan it begjin fan 'e struktuer, en berekkenje har grutte mei de funksje sizeof.

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

Yn it earste haadstik oer databankseleksjekritearia neamde ik it minimalisearjen fan dynamyske allocaasjes binnen CRUD-operaasjes as in wichtige seleksjefaktor. Funksje koade serialize lit sjen hoe't se yn it gefal fan LMDB folslein foarkommen wurde kinne by it ynfoegjen fan nije records yn 'e databank. De ynkommende byte-array fan 'e tsjinner wurdt earst omfoarme ta stackstruktueren, en dan wurde se triviaal yn opslach dumpt. Yn betinken nommen dat d'r ek gjin dynamyske allocaasjes binnen LMDB binne, kinne jo in fantastyske situaasje krije troch iOS-standerts - brûk allinich stapelûnthâld om te wurkjen mei gegevens oer it heule paad fan it netwurk nei de skiif!

Bestelle kaaien mei in binêre comparator

De kaai oarder relaasje wurdt oantsjutte troch in spesjale funksje neamd in comparator. Om't de motor neat wit oer de semantyk fan 'e bytes dy't se befetsje, hat de standertfergeliker gjin oare kar as om de kaaien yn leksikografyske folchoarder te regeljen, mei in byte-by-byte-fergeliking. It brûken fan it om struktueren te organisearjen is fergelykber mei it skeeren mei in hakkende bile. Yn ienfâldige gefallen fyn ik dizze metoade lykwols akseptabel. It alternatyf wurdt hjirûnder beskreaun, mar hjir sil ik in pear harken notearje dy't op dit paad ferspraat binne.

It earste ding om te ûnthâlden is de ûnthâldfertsjintwurdiging fan primitive gegevenstypen. Sa wurde op alle Apple-apparaten ynteger fariabelen opslein yn it formaat Lytse Endian. Dit betsjut dat de minst wichtige byte sil wêze oan de linkerkant, en it sil net mooglik te sortearjen hiele getallen mei help fan in byte-by-byte ferliking. Bygelyks, besykje dit te dwaan mei in set nûmers fan 0 oant 511 sil it folgjende resultaat produsearje.

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

Om dit probleem op te lossen, moatte de heule getallen yn 'e kaai opslein wurde yn in formaat dat geskikt is foar de byte-byte-komparator. Funksjes fan 'e famylje sille jo helpe om de nedige transformaasje út te fieren hton* (yn't bysonder htons foar dûbele-byte nûmers út it foarbyld).

It formaat foar it fertsjintwurdigjen fan snaren yn programmearring is, lykas jo witte, in gehiel skiednis. As de semantyk fan snaren, lykas de kodearring dy't brûkt wurdt om se yn it ûnthâld te fertsjintwurdigjen, suggerearret dat der mear as ien byte per karakter wêze kin, dan is it better om it idee fan it brûken fan in standertfergeliker daliks te ferlitten.

It twadde ding om yn gedachten te hâlden is alignment prinsipes struktuer fjild gearstaller. Fanwegen harren, bytes mei garbage wearden kinne wurde foarme yn it ûnthâld tusken fjilden, dy't, fansels, brekt byte-byte sortearring. Om ôffal te eliminearjen, moatte jo fjilden yn in strikt definieare folchoarder ferklearje, mei ôfstimmingsregels yn gedachten hâlde, of it attribút brûke yn 'e struktuerferklearring packed.

Bestelle kaaien mei in eksterne comparator

De logika foar kaaifergeliking kin te kompleks wêze foar in binêre komparator. Ien fan 'e protte redenen is de oanwêzigens fan technyske fjilden binnen struktueren. Ik sil yllustrearje harren foarkommen mei help fan it foarbyld fan in kaai foar in triemtafel elemint dat is al bekend foar ús.

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

Nettsjinsteande syn ienfâld, ferbrûkt it yn 'e grutte mearderheid fan' e gefallen tefolle ûnthâld. De buffer foar de namme nimt 256 bytes op, hoewol yn trochsneed triem- en mapnammen selden mear as 20-30 tekens binne.

Ien fan 'e standerttechniken foar it optimalisearjen fan de grutte fan in record is it "trimmen" nei de werklike grutte. De essinsje dêrfan is dat de ynhâld fan alle fjilden mei fariabele lingte wurdt opslein yn in buffer oan 'e ein fan' e struktuer, en har lingten wurde opslein yn aparte fariabelen.​ Neffens dizze oanpak is de kaai NodeKey wurdt omfoarme as folget.

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

Fierder, by serialisearjen, wurdt de gegevensgrutte net oantsjutte sizeof de hiele struktuer, en de grutte fan alle fjilden is in fêste lingte plus de grutte fan 'e eins brûkte diel fan' e buffer.

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

As gefolch fan 'e refactoring krigen wy wichtige besparrings yn' e romte beset troch kaaien. Lykwols, fanwege it technyske fjild nameLength, de standert binêre fergeliker is net langer geskikt foar kaaifergeliking. As wy it net ferfange troch ús eigen, dan sil de lingte fan 'e namme in hegere prioriteitsfaktor wêze by it sortearjen as de namme sels.

LMDB lit elke databank in eigen kaaifergelikingsfunksje hawwe. Dit wurdt dien mei de funksje mdb_set_compare strikt foar iepening. Om foar de hân lizzende redenen kin it net feroare wurde yn 'e hiele libben fan' e databank. De komparator ûntfangt twa kaaien yn binêre opmaak as ynfier, en by de útfier jout it it ferlikingsresultaat werom: minder as (-1), grutter as (1) of lyk oan (0). Pseudokoade foar NodeKey liket dat.

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

Salang't alle kaaien yn 'e databank binne fan itselde type, sûnder betingst casting harren byte fertsjintwurdiging nei it type fan de applikaasje kaai struktuer is legaal. D'r is hjir ien nuânse, mar it sil hjirûnder besprutsen wurde yn 'e subseksje "Reading Records".

Serialisearjen fan wearden

LMDB wurket ekstreem yntinsyf mei de kaaien fan opsleine records. Harren ferliking mei elkoar komt foar yn it ramt fan elke tapaste operaasje, en de prestaasjes fan 'e folsleine oplossing hinget ôf fan' e snelheid fan 'e komparator. Yn in ideale wrâld moat de standert binêre fergeliker genôch wêze om toetsen te fergelykjen, mar as jo jo eigen moatte brûke, dan moat de proseduere foar it deserialisearjen fan toetsen dêryn sa rap mooglik wêze.

De databank is net benammen ynteressearre yn it weardediel fan it rekôr (wearde). De konverzje dêrfan fan in byte-representaasje nei in objekt bart allinich as it al ferplicht is troch de applikaasjekoade, bygelyks om it op it skerm te werjaan. Om't dit relatyf komselden bart, binne de snelheidseasken foar dizze proseduere net sa kritysk, en yn 'e útfiering binne wy ​​folle frijer om te fokusjen op gemak. Bygelyks, om metadata te serialisearjen oer bestannen dy't noch net binne ynladen, brûke wy NSKeyedArchiver.

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

D'r binne lykwols tiden dat prestaasjes noch fan belang binne. Bygelyks, by it bewarjen fan meta-ynformaasje oer de triemstruktuer fan in brûkerswolk, brûke wy deselde ûnthâlddump fan objekten. It hichtepunt fan 'e taak om in serialisearre fertsjintwurdiging fan har te generearjen is it feit dat de eleminten fan in map wurde modeleare troch in hierargy fan klassen.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Om it yn 'e C-taal út te fieren, wurde spesifike fjilden fan' e erfgenamten yn aparte struktueren pleatst, en har ferbining mei de basis wurdt spesifisearre troch in fjild fan typeferiening. De eigentlike ynhâld fan de uny wurdt oantsjutte fia de technyske attribút type.

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

Records tafoegje en bywurkje

De serialisearre kaai en wearde kinne wurde tafoege oan 'e winkel. Om dit te dwaan, brûk de funksje mdb_put.

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

By de konfiguraasje poadium, de opslach kin tastien of ferbean wurde opslaan meardere records mei deselde kaai. As duplikaasje fan kaaien is ferbean, dan by it ynfoegjen fan in record, kinne jo bepale oft it bywurkjen fan in besteande record is tastien of net. As fraying allinich kin foarkomme as gefolch fan in flater yn 'e koade, dan kinne jo josels derfan beskermje troch de flagge op te jaan NOOVERWRITE.

Ynstjoerings lêze

Om records yn LMDB te lêzen, brûk de funksje mdb_get. As it kaai-wearde-pear wurdt fertsjintwurdige troch earder dumpte struktueren, dan sjocht dizze proseduere der sa út.

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

De presintearre list lit sjen hoe't serialisaasje fia struktuerdump jo dynamyske allocaasjes kinne kwytreitsje net allinich by it skriuwen, mar by it lêzen fan gegevens. Oflaat fan funksje mdb_get de oanwizer sjocht krekt op it firtuele ûnthâld adres dêr't de databank bewarret de byte fertsjintwurdiging fan it objekt. Yn feite krije wy in soarte fan ORM dy't heul hege gegevenslêssnelheid hast fergees leveret. Nettsjinsteande alle skientme fan 'e oanpak, is it nedich om te ûnthâlden ferskate funksjes ferbûn mei it.

  1. Foar in allinich lêzen transaksje wurdt de oanwizer nei de weardestruktuer garandearre allinich jildich te bliuwen oant de transaksje is sletten. Lykas earder opmurken, bliuwe de B-beam-siden dêr't in objekt op leit, tanksij it copy-on-write-prinsipe, net feroare, salang't se wurde ferwiisd troch op syn minst ien transaksje. Tagelyk, sa gau as de lêste transaksje ferbûn mei harren foltôging, de siden kinne wurde opnij brûkt foar nije gegevens. As it nedich is foar objekten om de transaksje te oerlibjen dy't se genereare, dan moatte se noch kopieare wurde.
  2. Foar in readwrite-transaksje sil de oanwizer nei de resultearjende weardestruktuer allinich jildich wêze oant de earste wizigingsproseduere (gegevens skriuwe of wiskje).
  3. Hoewol't de struktuer NodeValue net folweardich, mar ôfsnien (sjoch subseksje "Bestelle kaaien mei help fan in eksterne comparator"), kinne jo feilich tagong ta syn fjilden fia de oanwizer. It wichtichste is om it net te ûnderskieden!
  4. Under gjin omstannichheden moat de struktuer wizige wurde troch de ûntfongen oanwizer. Alle wizigingen moatte allinich makke wurde fia de metoade mdb_put. Lykwols, nettsjinsteande hoe hurd jo dit dwaan wolle, sil it net mooglik wêze, om't it ûnthâldgebiet wêr't dizze struktuer leit is yn kaart brocht yn allinich lêsmodus.
  5. Remap in triem oan de proses adres romte foar it doel fan, bygelyks, it fergrutsjen fan de maksimale opslach grutte mei help fan de funksje mdb_env_set_map_size folslein ûnjildich alle transaksjes en besibbe entiteiten yn it algemien en oanwizings foar bepaalde objekten yn it bysûnder.

Uteinlik is in oare funksje sa ferrifeljend dat it iepenbierjen fan syn essinsje net yn in oare paragraaf past. Yn it haadstik oer de B-beam haw ik in diagram jûn fan hoe't de siden yn it ûnthâld gearstald binne. Dêrút folget dat it adres fan it begjin fan 'e buffer mei serialisearre gegevens absolút willekeurich kin wêze. Fanwegen dit, de oanwizer foar harren ûntfongen yn 'e struktuer MDB_val en redusearre ta in oanwizer nei in struktuer, it blykt te wêzen unaligned yn it algemien gefal. Tagelyk fereaskje de arsjitektueren fan guon chips (yn it gefal fan iOS is dit armv7) dat it adres fan alle gegevens in mearfâld is fan 'e grutte fan it masinewurd of, mei oare wurden, de bitgrutte fan it systeem ( foar armv7 is it 32 bits). Mei oare wurden, in operaasje lykas *(int *foo)0x800002 op harren is lykweardich oan ûntsnapping en liedt ta eksekúsje mei in oardiel EXC_ARM_DA_ALIGN. D'r binne twa manieren om sa'n tryst lot te foarkommen.

De earste komt del op foarriedich kopiearjen fan gegevens yn in fansels ôfstimd struktuer. Bygelyks, op in oanpaste komparator sil dit as folget wurde wjerspegele.

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

In alternative manier is om de gearstaller fan tefoaren te melden dat kaaiweardestruktueren miskien net attribuut-aligned wurde aligned(1). Op ARM kinne jo itselde effekt hawwe berikke en mei help fan it packed attribút. Yn betinken nommen dat it ek helpt om de romte te optimalisearjen dy't beset wurdt troch de struktuer, liket my dizze metoade de foarkar, hoewol приводит ta in tanimming fan de kosten fan gegevens tagong operaasjes.

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

Range queries

Om oer in groep records te iterearjen, leveret LMDB in rinnerkeabstraksje. Litte wy sjen hoe't jo dermei kinne wurkje mei it foarbyld fan in tabel mei metadata fan brûkerswolken dy't ús al bekend binne.

As ûnderdiel fan it werjaan fan in list mei bestannen yn in map, is it nedich om alle kaaien te finen wêrmei't syn bernebestannen en mappen binne assosjearre. Yn 'e foarige subseksjes sorteare wy de kaaien NodeKey sa dat se primêr besteld wurde troch de ID fan 'e âldermap. Sa, technysk, komt de taak fan it opheljen fan de ynhâld fan in map del op it pleatsen fan de rinnerke op de boppegrins fan de groep kaaien mei in opjûn foarheaksel en dan iterearjen nei de ûndergrins.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

De boppegrins kin direkt fûn wurde troch sekwinsjele sykopdracht. Om dit te dwaan, wurdt de rinnerke oan it begjin fan 'e folsleine list fan kaaien yn' e databank pleatst en fierder ferhege oant in kaai mei de identifier fan 'e âldermap derûnder ferskynt. Dizze oanpak hat 2 dúdlike neidielen:

  1. Lineêre sykkompleksiteit, hoewol, lykas bekend, yn beammen yn it algemien en yn in B-beam yn it bysûnder kin it yn logaritmyske tiid útfierd wurde.
  2. Omdôch wurde alle siden foarôfgeand oan de sochte fan it bestân opheft nei it haadûnthâld, wat ekstreem djoer is.

Gelokkich biedt de LMDB API in effektive manier om it rinnerke yn earste ynstânsje te pleatsen. Om dit te dwaan moatte jo in kaai generearje wêrfan de wearde fansels minder is as of gelyk is oan de kaai dy't oan 'e boppegrins fan it ynterval leit. Bygelyks, yn relaasje ta de list yn 'e boppesteande figuer, kinne wy ​​meitsje in kaai wêryn it fjild parentId sil wêze gelyk oan 2, en al de rest binne fol mei nullen. Sa'n foar in part ynfolle kaai wurdt levere oan de funksje ynfier mdb_cursor_get oanjout fan de operaasje MDB_SET_RANGE.

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

As de boppegrins fan in groep kaaien fûn wurdt, dan iterearje wy der oer oant wy treffe of de kaai in oare parentId, of de toetsen sille hielendal net oprinne

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

Wat moai is, is dat wy as ûnderdiel fan 'e iteraasje mei mdb_cursor_get net allinich de kaai krije, mar ek de wearde. As jo, om oan de samplingbetingsten te foldwaan, ûnder oare de fjilden moatte kontrolearje fan it weardediel fan it rekord, dan binne se frij tagonklik sûnder ekstra stjoerings.

4.3. Modeling relaasjes tusken tabellen

Tsjintwurdich binne wy ​​it slagge om alle aspekten fan it ûntwerpen en wurkjen mei in single-table database te beskôgjen. Wy kinne sizze dat in tabel is in set fan sortearre records besteande út deselde soarte fan kaai-wearde pearen. As jo ​​in kaai werjaan as in rjochthoek en de assosjearre wearde as in parallelepiped, krije jo in fisuele diagram fan 'e databank.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Yn it echte libben is it lykwols selden mooglik om mei sa'n bytsje bloedfergieten te kommen. Faak is it yn in databank fereaske, yn it foarste plak, om ferskate tabellen te hawwen, en twadde, om seleksjes yn te meitsjen yn in oare folchoarder as de primêre kaai. Dizze lêste paragraaf is wijd oan 'e problemen fan har skepping en ferbining.

Index tabellen

De wolkapplikaasje hat in seksje "Galerij". It toant mediaynhâld fan 'e heule wolk, sorteare op datum. Om sa'n seleksje optimaal út te fieren, moatte jo njonken de haadtabel in oare meitsje mei in nij soart toetsen. Se sille in fjild befetsje mei de datum dat it bestân makke is, dat sil fungearje as it primêre sortearkritearium. Omdat de nije kaaien ferwize nei deselde gegevens as de kaaien yn 'e wichtichste tabel, se wurde neamd yndeks kaaien. Op de foto hjirûnder binne se yn oranje markearre.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Om de kaaien fan ferskate tabellen fan elkoar te skieden binnen deselde databank, waard in ekstra technysk fjild tableId oan allegear tafoege. Troch it de heechste prioriteit te meitsjen foar sortearjen, sille wy de groepearring fan toetsen earst troch tabellen en binnen tabellen berikke - neffens ús eigen regels.

De yndekskaai ferwiist nei deselde gegevens as de primêre kaai. In rjochtlinige ymplemintaasje fan dit pân troch dêrmei in kopy fan it weardediel fan 'e primêre kaai te assosjearjen is net optimaal út ferskate eachpunten:

  1. Yn termen fan romte opnommen, kinne de metadata frij ryk wêze.
  2. Fanút in perspektyf fan prestaasjes, om't by it bywurkjen fan de metadata fan in knooppunt, jo it moatte oerskriuwe mei twa kaaien.
  3. Ut it eachpunt fan koade-stipe, as wy ferjitte om de gegevens foar ien fan 'e kaaien te aktualisearjen, sille wy in ûngrypbere brek krije fan gegevensynkonsistinsje yn' e opslach.

Folgjende sille wy beskôgje hoe't te elimineren dizze tekoartkommingen.

Organisearje relaasjes tusken tabellen

It patroan is goed geskikt foar it keppeljen fan de yndekstafel mei de haadtafel "kaai as wearde". Lykas de namme al fermoeden docht, is it weardediel fan it yndeksrecord in kopy fan 'e primêre kaaiwearde. Dizze oanpak elimineert alle boppeneamde neidielen ferbûn mei it bewarjen fan in kopy fan de wearde diel fan de primêre record. De ienige kosten is dat om in wearde te krijen troch yndekskaai, moatte jo 2 queries meitsje nei de databank ynstee fan ien. Skematysk sjocht it resultearjende databankskema der sa út.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

In oar patroan foar it organisearjen fan relaasjes tusken tabellen is "oerstallige kaai". De essinsje dêrfan is om ekstra attributen ta te foegjen oan 'e kaai, dy't net nedich binne foar sortearjen, mar foar it opnij oanmeitsjen fan' e assosjearre kaai. Yn 'e Mail.ru Cloud-applikaasje binne d'r echte foarbylden fan it gebrûk, lykwols, om in djippe dûk yn te foarkommen de kontekst fan spesifike iOS-ramten, sil ik in fiktyf, mar in dúdliker foarbyld jaan

Cloud mobile kliïnten hawwe in side dy't alle bestannen en mappen werjaan dy't de brûker hat dield mei oare minsken. Om't d'r relatyf in pear sokke bestannen binne, en d'r in protte ferskillende soarten spesifike ynformaasje oer publisiteit dêrmei ferbûn binne (wa't tagong krije, mei hokker rjochten, ensfh.), sil it net rasjoneel wêze om it weardediel fan 'e opnimme yn 'e haadtabel mei it. As jo ​​lykwols sokke bestannen offline werjaan wolle, moatte jo it noch earne opslaan. In natuerlike oplossing is om dêr in aparte tafel foar te meitsjen. Yn it diagram hjirûnder is de kaai foarôfgeand mei "P", en de plakhâlder "propname" kin wurde ferfongen troch de mear spesifike wearde "iepenbiere ynfo".

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

Alle unike metadata, om 'e wille fan it bewarjen wêrfan de nije tabel is makke, wurdt pleatst yn it weardediel fan it record. Tagelyk wolle jo de gegevens net duplisearje oer bestannen en mappen dy't al binne opslein yn 'e haadtabel. Ynstee dêrfan wurde oerstallige gegevens tafoege oan de "P" kaai yn 'e foarm fan' e "knooppunt ID" en "timestamp" fjilden. Mei tank oan harren kinne jo in yndekskaai konstruearje, wêrfan jo in primêre kaai kinne krije, wêrfan jo úteinlik knooppuntmetadata kinne krije.

Konklúzje

Wy beoardielje de resultaten fan de ymplemintaasje fan LMDB posityf. Dêrnei is it oantal oanfragen befriezen mei 30% ôfnommen.

De glâns en earmoede fan 'e kaai-wearde databank LMDB yn iOS-applikaasjes

De resultaten fan it dien wurk resonearren bûten it iOS-team. Op it stuit is ien fan 'e haadseksjes "Bestânen" yn 'e Android-applikaasje ek oerstapt nei it brûken fan LMDB, en oare dielen binne ûnderweis. De C-taal, wêryn't de kaai-wearde-winkel is ymplementearre, wie in goede help om yn earste ynstânsje in applikaasje-kader der omhinne te meitsjen cross-platform yn C++. In koadegenerator waard brûkt om de resultearjende C++-bibleteek naadloos te ferbinen mei platfoarmkoade yn Objective-C en Kotlin Djinni fan Dropbox, mar dat is in folslein oar ferhaal.

Boarne: www.habr.com

Add a comment