Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Në vjeshtën e vitit 2019, një ngjarje e shumëpritur u zhvillua në ekipin e Mail.ru Cloud iOS. Baza e të dhënave kryesore për ruajtjen e vazhdueshme të gjendjes së aplikacionit është bërë mjaft ekzotike për botën celulare Baza e të dhënave e hartuar nga Lightning Memory (LMDB). Nën prerjen, vëmendja juaj është e ftuar në rishikimin e tij të detajuar në katër pjesë. Së pari, le të flasim për arsyet e një zgjedhjeje kaq jo të parëndësishme dhe të vështirë. Më pas le të kalojmë në shqyrtimin e tre balenave në zemër të arkitekturës LMDB: skedarët e hartuar me memorie, pema B +, qasja kopje-në-shkruaj për zbatimin e transaksioneve dhe multiversionit. Së fundi, për ëmbëlsirë - pjesa praktike. Në të, ne do të shikojmë se si të hartojmë dhe zbatojmë një skemë bazë me disa tabela, duke përfshirë një indeks, në krye të API-së me vlerë kyçe të nivelit të ulët.​

Përmbajtje

  1. Motivimi i Zbatimit
  2. Pozicionimi i LMDB
  3. Tre balena LMDB
    3.1. Balena numër 1. Skedarët e hartuar me memorie
    3.2. Balena numër 2. B+-pema
    3.3. Balena numër 3. kopje-në-shkruaj
  4. Dizajnimi i një skeme të dhënash në krye të API-së me vlerë kyçe
    4.1. Abstraksionet bazë
    4.2. Modelimi i tabelës
    4.3. Modelimi i marrëdhënieve ndërmjet tabelave

1. Motivimi i zbatimit

Një herë në vit, në vitin 2015, ne u kujdesëm të bënim një metrikë, sa shpesh ngec ndërfaqja e aplikacionit tonë. Ne nuk e bëmë vetëm këtë. Ne kemi gjithnjë e më shumë ankesa për faktin se nganjëherë aplikacioni ndalon t'i përgjigjet veprimeve të përdoruesit: butonat nuk shtypen, listat nuk lëvizin, etj. Rreth mekanikës së matjeve tha në AvitoTech, kështu që këtu jap vetëm rendin e numrave.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Rezultatet e matjes u bënë një dush i ftohtë për ne. Doli se problemet e shkaktuara nga ngricat janë shumë më tepër se çdo tjetër. Nëse, përpara se të realizohej ky fakt, treguesi kryesor teknik i cilësisë ishte pa përplasje, atëherë pas fokusit i zhvendosur pa ngrirje.

Duke ndërtuar pult me ​​ngrirje dhe pasi ka shpenzuar sasiore и cilësi analiza e shkaqeve të tyre, armiku kryesor u bë i qartë - logjika e rëndë e biznesit ekzekutohet në fillin kryesor të aplikacionit. Një reagim i natyrshëm ndaj këtij turpi ishte një dëshirë e zjarrtë për ta futur atë në rrjedhat e punës. Për një zgjidhje sistematike të këtij problemi, ne iu drejtuam një arkitekture me shumë fije të bazuar në aktorë të lehtë. I kushtova përshtatjet e saj për botën iOS dy fije në Twitter kolektiv dhe artikull në Habré. Si pjesë e historisë aktuale, dua të theksoj ato aspekte të vendimit që ndikuan në zgjedhjen e bazës së të dhënave.​

Modeli aktor i organizimit të sistemit supozon se multithreading bëhet thelbi i dytë i tij. Objektet e modelit në të pëlqejnë të kalojnë kufijtë e fillit. Dhe ata e bëjnë këtë jo ndonjëherë dhe në disa vende, por pothuajse vazhdimisht dhe kudo.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Baza e të dhënave është një nga komponentët themelorë në diagramin e paraqitur. Detyra e tij kryesore është të zbatojë një model makro Baza e të dhënave e përbashkët. Nëse në botën e ndërmarrjes përdoret për të organizuar sinkronizimin e të dhënave ndërmjet shërbimeve, atëherë në rastin e një arkitekture aktori, të dhënat midis thread-ve. Kështu, na duhej një bazë të dhënash e tillë, puna me të cilën në një mjedis me shumë fije nuk shkakton as vështirësi minimale. Në veçanti, kjo do të thotë që objektet që rrjedhin prej tij duhet të jenë të paktën të sigurta në fije, dhe në mënyrë ideale fare të pandryshueshme. Siç e dini, kjo e fundit mund të përdoret njëkohësisht nga disa fije pa përdorur asnjë lloj bravë, gjë që ka një efekt të dobishëm në performancën.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOSFaktori i dytë i rëndësishëm që ndikoi në zgjedhjen e bazës së të dhënave ishte API-ja jonë e cloud. Ai u frymëzua nga qasja git për sinkronizimin. Ashtu si ai synuam API-ja e parë jashtë linje, e cila duket më se e përshtatshme për klientët cloud. Supozohej se ato do të nxirrnin vetëm një herë gjendjen e plotë të resë, dhe më pas sinkronizimi në shumicën dërrmuese të rasteve do të ndodhte përmes ndryshimeve të rrotullimit. Mjerisht, kjo mundësi është ende vetëm në zonën teorike, dhe në praktikë, klientët nuk kanë mësuar se si të punojnë me arna. Ka një sërë arsyesh objektive për këtë, të cilat, për të mos vonuar prezantimin, do t'i lëmë jashtë kllapave. Tani shumë më interesante janë rezultatet udhëzuese të mësimit se çfarë ndodh kur API tha "A" dhe konsumatori i saj nuk tha "B".

Pra, nëse imagjinoni git, i cili, kur ekzekuton një komandë tërheqëse, në vend që të aplikojë arna në një fotografi lokale, krahason gjendjen e tij të plotë me atë të serverit të plotë, atëherë do të keni një ide mjaft të saktë se si sinkronizimi ndodh në klientët cloud. Është e lehtë të merret me mend se për zbatimin e tij është e nevojshme të ndahen dy pemë DOM në memorie me meta-informacion për të gjithë skedarët e serverit dhe lokal. Rezulton se nëse një përdorues ruan 500 mijë skedarë në cloud, atëherë për ta sinkronizuar atë, është e nevojshme të rikrijohen dhe shkatërrohen dy pemë me 1 milion nyje. Por çdo nyje është një agregat që përmban një grafik të nënobjekteve. Në këtë këndvështrim, rezultatet e profilizimit ishin të pritshme. Doli se edhe pa marrë parasysh algoritmin e bashkimit, vetë procedura e krijimit dhe më pas të shkatërrimit të një numri të madh objektesh të vogla kushton një qindarkë goxha. Situata rëndohet nga fakti se operacioni bazë i sinkronizimit përfshihet në një numër të madh. të skripteve të përdoruesve. Si rezultat, ne rregullojmë kriterin e dytë të rëndësishëm në zgjedhjen e një baze të dhënash - aftësinë për të zbatuar operacionet CRUD pa shpërndarje dinamike të objekteve.

Kërkesat e tjera janë më tradicionale dhe lista e plotë e tyre është si më poshtë.

  1. Siguria e fijeve.
  2. Multiprocessing. Diktohet nga dëshira për të përdorur të njëjtin shembull të bazës së të dhënave për të sinkronizuar gjendjen jo vetëm midis temave, por edhe midis aplikacionit kryesor dhe shtesave iOS.
  3. Aftësia për të paraqitur entitetet e ruajtura si objekte të pandryshueshme
  4. Mungesa e alokimeve dinamike brenda operacioneve CRUD.
  5. Mbështetja e transaksionit për pronat themelore ACIDFjalët kyçe: atomiciteti, qëndrueshmëria, izolimi dhe besueshmëria.
  6. Shpejtësia në rastet më të njohura.

Me këtë grup kërkesash, SQLite ishte dhe është ende një zgjedhje e mirë. Megjithatë, si pjesë e studimit të alternativave, hasa në një libër "Fillimi me LevelDB". Nën udhëheqjen e saj, u shkrua një pikë referimi që krahason shpejtësinë e punës me baza të të dhënave të ndryshme në skenarë realë të cloud. Rezultati i tejkaloi pritjet më të egra. Në rastet më të njohura - marrja e një kursori në një listë të renditur të të gjithë skedarëve dhe një listë e renditur e të gjithë skedarëve për një drejtori të caktuar - LMDB doli të ishte 10 herë më i shpejtë se SQLite. Zgjedhja u bë e qartë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

2. Pozicionimi LMDB

LMDB është një bibliotekë, shumë e vogël (vetëm 10K rreshta), e cila zbaton shtresën më të ulët themelore të bazave të të dhënave - ruajtjen.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Diagrami i mësipërm tregon se krahasimi i LMDB me SQLite, i cili zbaton nivele edhe më të larta, në përgjithësi nuk është më i saktë se SQLite me të dhëna Core. Do të ishte më e drejtë të citoheshin të njëjtët motorë ruajtjeje si konkurrentë të barabartë - BerkeleyDB, LevelDB, Sophia, RocksDB, etj. Madje ka zhvillime ku LMDB vepron si një komponent i motorit të ruajtjes për SQLite. Eksperimenti i parë i tillë në 2012 shpenzuar autor LMDB Howard Çu. Gjetjet doli të ishte aq intrigues sa iniciativa e tij u kap nga entuziastët e OSS dhe gjeti vazhdimin e saj përballë LumoSQL. Në janar 2020 autor i këtij projekti është Den Shearer paraqitur atë në LinuxConfAu.

Përdorimi kryesor i LMDB është si një motor për bazat e të dhënave të aplikacioneve. Biblioteka ia detyron pamjen e saj zhvilluesve OpenLDAP, të cilët ishin shumë të pakënaqur me BerkeleyDB si bazë të projektit të tyre. Duke u larguar nga biblioteka e përulur bpeme, Howard Chu ishte në gjendje të krijonte një nga alternativat më të njohura të kohës sonë. Ai ia kushtoi raportin e tij shumë të lezetshëm kësaj historie, si dhe strukturës së brendshme të LMDB "Baza e të dhënave e hartuar nga Rrufeja". Leonid Yuriev (aka yleo) nga Positive Technologies në fjalimin e tij në Highload 2015 "Motori LMDB është një kampion special". Në të, ai flet për LMDB në kontekstin e një detyre të ngjashme të zbatimit të ReOpenLDAP, dhe LevelDB tashmë i është nënshtruar kritikës krahasuese. Si rezultat i zbatimit, Positive Technologies madje mori një pirun në zhvillim aktiv MDBX me karakteristika shumë të shijshme, optimizime dhe rregullime të gabimeve.

LMDB përdoret shpesh si një ruajtje po ashtu. Për shembull, shfletuesi Mozilla Firefox zgjodhi atë për një sërë nevojash, dhe, duke filluar nga versioni 9, Xcode e preferuar SQLite e tij për të ruajtur indekset.

Motori kapi edhe në botën e zhvillimit celular. Gjurmët e përdorimit të tij mund të jenë për të gjetur në klientin iOS për Telegram. LinkedIn shkoi një hap më tej dhe zgjodhi LMDB si ruajtjen e paracaktuar për kuadrin e tij të ruajtjes së të dhënave në vend, Rocket Data, rreth të cilit tha në një artikull në 2016.

LMDB po lufton me sukses për një vend në diell në kamaren e lënë nga BerkeleyDB pas kalimit nën kontrollin e Oracle. Biblioteka është e dashur për shpejtësinë dhe besueshmërinë e saj, edhe në krahasim me llojin e saj. Siç e dini, nuk ka dreka falas dhe do të doja të theksoja kompromisin me të cilin do të duhet të përballeni kur zgjidhni midis LMDB dhe SQLite. Diagrami i mësipërm tregon qartë se si arrihet shpejtësia e rritur. Së pari, ne nuk paguajmë për shtresa shtesë të abstraksionit në krye të ruajtjes së diskut. Sigurisht, në një arkitekturë të mirë, ende nuk mund të bëni pa to, dhe ato do të shfaqen në mënyrë të pashmangshme në kodin e aplikacionit, por do të jenë shumë më të hollë. Ata nuk do të kenë veçori që nuk kërkohen nga një aplikacion specifik, për shembull, mbështetje për pyetje në gjuhën SQL. Së dyti, bëhet e mundur që në mënyrë optimale të zbatohet hartëzimi i operacioneve të aplikacionit me kërkesat për ruajtjen e diskut. Nëse SQLite në punën time vjen nga nevojat mesatare të një aplikacioni mesatar, atëherë ju, si zhvillues aplikacioni, jeni mirë të vetëdijshëm për skenarët kryesorë të ngarkesës. Për një zgjidhje më produktive, do t'ju duhet të paguani një çmim më të lartë si për zhvillimin e zgjidhjes fillestare ashtu edhe për mbështetjen e saj pasuese.

3. Tre balena LMDB

Pasi ta shikoni LMDB-në nga një këndvështrim zogu, është koha për të shkuar më thellë. Tre seksionet e ardhshme do t'i kushtohen analizës së balenave kryesore mbi të cilat mbështetet arkitektura e ruajtjes:

  1. Skedarët e hartuar me memorie si një mekanizëm për të punuar me disk dhe për sinkronizimin e strukturave të brendshme të të dhënave.
  2. B+-pema si një organizim i strukturës së të dhënave të ruajtura.
  3. Kopjo-në-shkruaj si një qasje për të ofruar veçori transaksionale ACID dhe multiversionim.

3.1. Balena numër 1. Skedarët e hartuar me memorie

Skedarët e hartuar me memorie janë një element kaq i rëndësishëm arkitekturor saqë shfaqen edhe në emrin e depove. Çështjet e memorizimit dhe sinkronizimit të aksesit në informacionin e ruajtur janë tërësisht në mëshirën e sistemit operativ. LMDB nuk përmban asnjë memorie brenda vetes. Ky është një vendim i vetëdijshëm nga autori, pasi leximi i të dhënave direkt nga skedarët e hartës ju lejon të shkurtoni shumë qoshe në zbatimin e motorit. Më poshtë është një listë jo e plotë e disa prej tyre.

  1. Ruajtja e konsistencës së të dhënave në ruajtje kur punoni me to nga disa procese bëhet përgjegjësi e sistemit operativ. Në pjesën tjetër, ky mekanik diskutohet në detaje dhe me foto.
  2. Mungesa e cache-ve e lehtëson plotësisht LMDB-në nga shpenzimet e përgjithshme të lidhura me alokimet dinamike. Leximi i të dhënave në praktikë është vendosja e treguesit në adresën e saktë në memorien virtuale dhe asgjë më shumë. Tingëllon si fantazi, por në burimin e ruajtjes, të gjitha thirrjet calloc janë të përqendruara në funksionin e konfigurimit të depove.
  3. Mungesa e cache-ve nënkupton gjithashtu mungesën e bllokimeve të lidhura me sinkronizimin për t'iu qasur atyre. Lexuesit, nga të cilët një numër arbitrar mund të ekzistojë në të njëjtën kohë, nuk hasin një mutex të vetëm në rrugën e tyre drejt të dhënave. Për shkak të kësaj, shpejtësia e leximit ka një shkallëzim ideal linear për sa i përket numrit të CPU-ve. Në LMDB, sinkronizohen vetëm operacionet modifikuese. Mund të ketë vetëm një shkrimtar në të njëjtën kohë.
  4. Një minimum i logjikës së memorizimit dhe sinkronizimit e kursen kodin nga një lloj gabimesh jashtëzakonisht komplekse që lidhen me punën në një mjedis me shumë fije. Kishte dy studime interesante të bazës së të dhënave në konferencën Usenix OSDI 2014: "Të gjitha sistemet e skedarëve nuk janë krijuar të barabartë: në kompleksitetin e krijimit të aplikacioneve të qëndrueshme në përplasje" и Torturuese e bazave të të dhënave për argëtim dhe fitim. Prej tyre mund të merrni informacione si për besueshmërinë e paprecedentë të LMDB, ashtu edhe për zbatimin pothuajse të përsosur të vetive ACID të transaksioneve, gjë që e tejkalon atë në të njëjtin SQLite.
  5. Minimalizmi i LMDB lejon që përfaqësimi i makinës i kodit të tij të vendoset plotësisht në cache L1 të procesorit me karakteristikat e shpejtësisë që rezultojnë.

Fatkeqësisht, në iOS, skedarët e hartuar me memorie nuk janë aq rozë sa do të dëshironim. Për të folur më me vetëdije për disavantazhet që lidhen me to, është e nevojshme të kujtojmë parimet e përgjithshme për zbatimin e këtij mekanizmi në sistemet operative.

Informacion i përgjithshëm në lidhje me skedarët e hartuar me memorie

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOSMe çdo aplikacion të ekzekutueshëm, sistemi operativ shoqëron një entitet të quajtur proces. Secilit proces i ndahet një gamë e vazhdueshme adresash, në të cilat vendos gjithçka që i nevojitet për të punuar. Adresat më të ulëta përmbajnë seksione me kod dhe të dhëna dhe burime të koduara. Më pas vjen blloku në rritje i hapësirës dinamike të adresave, i njohur për ne si grumbulli. Ai përmban adresat e subjekteve që shfaqen gjatë funksionimit të programit. Në krye është zona e memories së përdorur nga grupi i aplikacionit. Ai ose rritet ose zvogëlohet, me fjalë të tjera, madhësia e tij ka gjithashtu një natyrë dinamike. Në mënyrë që pirgu dhe grumbulli të mos shtyjnë dhe ndërhyjnë me njëri-tjetrin, ato ndahen në skaje të ndryshme të hapësirës së adresës.Ka një vrimë midis dy seksioneve dinamike në krye dhe në fund. Adresat në këtë seksion të mesëm përdoren nga sistemi operativ për t'u lidhur me një proces të entiteteve të ndryshme. Në veçanti, ai mund të hartojë një grup të caktuar adresash të vazhdueshme në një skedar në disk. Një skedar i tillë quhet skedar i hartuar me memorie.​

Hapësira e adresës e caktuar për një proces është e madhe. Teorikisht, numri i adresave është i kufizuar vetëm nga madhësia e treguesit, i cili përcaktohet nga biti i sistemit. Nëse memoria fizike do t'i caktohej 1-në-1, atëherë procesi i parë do të gëlltiste të gjithë RAM-in dhe nuk do të bëhej fjalë për ndonjë multitasking.

Megjithatë, ne e dimë nga përvoja se sistemet moderne operative mund të ekzekutojnë sa më shumë procese që dëshironi në të njëjtën kohë. Kjo është e mundur për shkak të faktit se ata ndajnë shumë memorie për proceset vetëm në letër, por në realitet ata ngarkojnë në memorien kryesore fizike vetëm atë pjesë që kërkohet këtu dhe tani. Prandaj, kujtesa e lidhur me procesin quhet virtuale.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Sistemi operativ organizon kujtesën virtuale dhe fizike në faqe të një madhësie të caktuar. Sapo kërkohet një faqe e caktuar e memories virtuale, sistemi operativ e ngarkon atë në kujtesën fizike dhe vendos një korrespondencë midis tyre në një tabelë të veçantë. Nëse nuk ka lojëra elektronike falas, atëherë një nga faqet e ngarkuara më parë kopjohet në disk dhe ajo e kërkuar zë vendin e saj. Kjo procedurë, së cilës do t'i kthehemi së shpejti, quhet shkëmbim. Figura më poshtë ilustron procesin e përshkruar. Në të, faqja A me adresën 0 u ngarkua dhe u vendos në faqen kryesore të memories me adresën 4. Ky fakt u pasqyrua në tabelën e korrespondencës në qelizën numër 0.​

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Me skedarët e hartuar me kujtesë, historia është saktësisht e njëjtë. Logjikisht, ato supozohet se vendosen vazhdimisht dhe tërësisht në hapësirën e adresave virtuale. Sidoqoftë, ato futen në memorien fizike faqe për faqe dhe vetëm sipas kërkesës. Modifikimi i faqeve të tilla sinkronizohet me skedarin në disk. Kështu, ju mund të kryeni skedarin I/O, thjesht duke punuar me bajt në memorie - të gjitha ndryshimet do të transferohen automatikisht nga kerneli i sistemit operativ në skedarin origjinal.​
â € <
Imazhi më poshtë tregon se si LMDB sinkronizon gjendjen e tij kur punon me bazën e të dhënave nga procese të ndryshme. Duke hartuar memorien virtuale të proceseve të ndryshme në të njëjtin skedar, ne de facto e detyrojmë sistemin operativ të sinkronizojë në mënyrë transitive blloqe të caktuara të hapësirave të adresave të tyre me njëra-tjetrën, ku duket LMDB.
â € <

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Një nuancë e rëndësishme është se LMDB modifikon skedarin e të dhënave si parazgjedhje përmes mekanizmit të thirrjes së sistemit të shkrimit dhe vetë skedari shfaqet në modalitetin vetëm për lexim. Kjo qasje ka dy implikime të rëndësishme.

Pasoja e parë është e përbashkët për të gjitha sistemet operative. Thelbi i saj është të shtojë mbrojtje kundër dëmtimit të paqëllimshëm të bazës së të dhënave nga kodi i pasaktë. Siç e dini, udhëzimet e ekzekutueshme të një procesi janë të lira për të hyrë në të dhëna nga kudo në hapësirën e tij të adresave. Në të njëjtën kohë, siç u kujtuam sapo, shfaqja e një skedari në modalitetin lexim-shkrim do të thotë që çdo udhëzim mund ta modifikojë atë gjithashtu. Nëse ajo e bën këtë gabimisht, duke u përpjekur, për shembull, të mbishkruajë një element të grupit në një indeks jo-ekzistent, atëherë në këtë mënyrë ajo mund të ndryshojë aksidentalisht skedarin e vendosur në këtë adresë, gjë që do të çojë në prishjen e bazës së të dhënave. Nëse skedari shfaqet në modalitetin vetëm për lexim, atëherë një përpjekje për të ndryshuar hapësirën e adresës që korrespondon me të do të çojë në përplasjen e programit me sinjalin SIGSEGV, dhe skedari do të mbetet i paprekur.

Pasoja e dytë është tashmë specifike për iOS. As autori dhe as ndonjë burim tjetër nuk e përmend në mënyrë eksplicite, por pa të, LMDB do të ishte i papërshtatshëm për funksionimin në këtë sistem operativ celular. Seksioni tjetër i kushtohet shqyrtimit të tij.

Specifikat e skedarëve të hartuar me kujtesë në iOS

Në vitin 2018, pati një raport të mrekullueshëm në WWDC Zhytje e thellë e kujtesës iOS. Ai tregon se në iOS të gjitha faqet e vendosura në memorien fizike i përkasin një prej 3 llojeve: të pista, të ngjeshura dhe të pastra.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Memoria e pastër është një koleksion faqesh që mund të ndërrohen në mënyrë të sigurt nga memoria fizike. Të dhënat që ato përmbajnë mund të ringarkohen nga burimet e tyre origjinale sipas nevojës. Skedarët e hartuar me memorie vetëm për lexim bëjnë pjesë në këtë kategori. iOS nuk ka frikë të shkarkojë faqet e hartuara në një skedar nga memorja në çdo kohë, pasi ato janë të garantuara të sinkronizohen me skedarin në disk.
â € <
Të gjitha faqet e modifikuara futen në memorie të ndyrë, pavarësisht se ku ndodhen fillimisht. Në veçanti, skedarët e hartuar me memorie të modifikuar duke shkruar në memorien virtuale të lidhur me to do të klasifikohen gjithashtu në këtë mënyrë. Hapja e LMDB me flamur MDB_WRITEMAP, pasi të bëni ndryshime në të, mund ta shihni vetë.

Sapo një aplikacion fillon të marrë shumë memorie fizike, iOS ngjesh faqet e tij të pista. Koleksioni i memories së zënë nga faqet e pista dhe të ngjeshura është e ashtuquajtura gjurmë memorie e aplikacionit. Kur arrin një vlerë të caktuar pragu, daemon i sistemit vrasës OOM vjen pas procesit dhe e përfundon me forcë atë. Kjo është e veçanta e iOS në krahasim me sistemet operative të desktopit. Në të kundërt, ulja e gjurmës së kujtesës duke ndërruar faqet nga memoria fizike në disk nuk ofrohet në iOS. Mund të merret vetëm me hamendje për arsyet. Ndoshta procedura për lëvizjen intensive të faqeve në disk dhe mbrapa është shumë konsumuese e energjisë për pajisjet celulare, ose iOS kursen burimin e rishkrimit të qelizave në disqet SSD, ose ndoshta projektuesit nuk ishin të kënaqur me performancën e përgjithshme të sistemit, ku gjithçka është këmbyer vazhdimisht. Sido që të jetë, fakti mbetet.

Lajmi i mirë, i përmendur tashmë më herët, është se LMDB nuk përdor mekanizmin mmap për të përditësuar skedarët si parazgjedhje. Nga kjo rrjedh se të dhënat e paraqitura klasifikohen si memorie të pastër nga iOS dhe nuk kontribuojnë në gjurmën e kujtesës. Kjo mund të verifikohet duke përdorur mjetin Xcode të quajtur VM Tracker. Pamja e mëposhtme e ekranit tregon gjendjen e memories virtuale të aplikacionit iOS Cloud gjatë funksionimit. Në fillim, 2 instanca LMDB u inicializuan në të. I pari u lejua të hartonte skedarin e tij në 1 GiB memorie virtuale, i dyti - 512 MiB. Përkundër faktit se të dy depozitat zënë një sasi të caktuar të memories rezidente, asnjëra prej tyre nuk kontribuon në madhësinë e pistë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Tani është koha për lajmet e këqija. Falë mekanizmit të shkëmbimit në sistemet operative të desktopit 64-bit, çdo proces mund të zërë aq hapësirë ​​​​për adresa virtuale sa lejon hapësira e lirë në hard disk për shkëmbimin e tij të mundshëm. Zëvendësimi i shkëmbimit me kompresim në iOS ul në mënyrë drastike maksimumin teorik. Tani të gjitha proceset e gjalla duhet të futen në memorien kryesore (lexo RAM), dhe të gjitha ato që nuk përshtaten i nënshtrohen përfundimit të detyruar. Është përmendur si më sipër raport, dhe në dokumentacion zyrtar. Si pasojë, iOS kufizon rëndë sasinë e memories së disponueshme për shpërndarje përmes mmap. Këtu këtu mund të shikoni kufijtë empirikë të sasisë së memories që mund të ndahet në pajisje të ndryshme duke përdorur këtë thirrje sistemi. Në modelet më moderne të telefonave inteligjentë, iOS është bërë bujar me 2 gigabajt, dhe në versionet më të mira të iPad - me 4. Në praktikë, natyrisht, duhet të përqendroheni në modelet më të reja të pajisjeve të mbështetura, ku gjithçka është shumë e trishtuar. Akoma më keq, duke parë gjendjen e memories së aplikacionit në VM Tracker, do të zbuloni se LMDB nuk është i vetmi që pretendon memorie të hartuar nga memoria. Pjesët e mira hahen nga alokuesit e sistemit, skedarët e burimeve, kornizat e imazhit dhe grabitqarët e tjerë më të vegjël.

Si rezultat i eksperimenteve në Cloud, ne dolëm me vlerat e mëposhtme kompromisi të memories të ndara nga LMDB: 384 megabajt për pajisjet 32-bit dhe 768 për ato 64-bit. Pasi të përdoret ky vëllim, çdo operacion modifikues fillon të përfundojë me kodin MDB_MAP_FULL. Ne vërejmë gabime të tilla në monitorimin tonë, por ato janë mjaft të vogla për t'u neglizhuar në këtë fazë.

Një arsye jo e qartë për konsumin e tepërt të memories nga ruajtja mund të jenë transaksionet jetëgjata. Për të kuptuar se si lidhen këto dy fenomene, do të na ndihmojë të marrim parasysh dy balenat e mbetura LMDB.

3.2. Balena numër 2. B+-pema

Për të imituar tabelat në krye të një dyqani me vlerë kyçe, operacionet e mëposhtme duhet të jenë të pranishme në API-në e tij:

  1. Futja e një elementi të ri.
  2. Kërkoni për një element me një çelës të caktuar.
  3. Fshirja e një elementi.
  4. Përsëriteni në intervalet kryesore sipas renditjes së tyre.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOSStruktura më e thjeshtë e të dhënave që mund të zbatojë lehtësisht të katër operacionet është një pemë kërkimi binar. Secila prej nyjeve të saj është një çelës që ndan të gjithë nëngrupin e çelësave fëmijë në dy nënpemë. Në të majtë janë ato që janë më të vogla se prindi, dhe në të djathtë - ato që janë më të mëdha. Marrja e një grupi çelësash të porositur arrihet përmes një prej kalimeve klasike të pemëve

Pemët binare kanë dy të meta themelore që i pengojnë ato të jenë efektive si një strukturë e të dhënave të diskut. Së pari, shkalla e ekuilibrit të tyre është e paparashikueshme. Ekziston një rrezik i konsiderueshëm i marrjes së pemëve në të cilat lartësia e degëve të ndryshme mund të ndryshojë shumë, gjë që përkeqëson ndjeshëm kompleksitetin algoritmik të kërkimit në krahasim me atë që pritet. Së dyti, bollëku i lidhjeve të kryqëzuara ndërmjet nyjeve i privon pemët binare nga lokaliteti në memorie.Nyjet e mbyllura (për sa i përket lidhjeve ndërmjet tyre) mund të vendosen në faqe krejtësisht të ndryshme në memorien virtuale. Si pasojë, edhe një kalim i thjeshtë i disa nyjeve fqinje në një pemë mund të kërkojë vizitën e një numri të krahasueshëm faqesh. Ky është një problem edhe kur flasim për efektivitetin e pemëve binare si një strukturë e të dhënave në memorie, pasi rrotullimi i vazhdueshëm i faqeve në cache të procesorit nuk është i lirë. Kur bëhet fjalë për ngritjen e shpeshtë të faqeve të lidhura me nyjet nga disku, gjërat bëhen shumë keq. e mjerueshme.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOSPemët B, duke qenë një evolucion i pemëve binare, zgjidhin problemet e identifikuara në paragrafin e mëparshëm. Së pari, ata janë vetë-balancues. Së dyti, secila prej nyjeve të tyre e ndan grupin e çelësave fëmijë jo në 2, por në nënbashkësi të renditura M, dhe numri M mund të jetë mjaft i madh, në rendin e disa qindra apo edhe mijërave.

Në këtë mënyrë:

  1. Çdo nyje ka një numër të madh çelësash të porositur tashmë dhe pemët janë shumë të ulëta.
  2. Pema fiton vetinë e lokalitetit në memorie, pasi çelësat që janë të afërt në vlerë ndodhen natyrshëm pranë njëri-tjetrit në një ose nyje fqinje.
  3. Zvogëlon numrin e nyjeve transitore kur zbresin nga pema gjatë një operacioni kërkimi.
  4. Zvogëlon numrin e nyjeve të synuara të lexuara për pyetjet e diapazonit, pasi secila prej tyre tashmë përmban një numër të madh çelësash të renditur.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

LMDB përdor një variant të pemës B të quajtur pema B+ për të ruajtur të dhënat. Diagrami i mësipërm tregon tre llojet e nyjeve që përmban:

  1. Në krye është rrënja. Ai nuk materializon asgjë më shumë se konceptin e një baze të dhënash brenda një depoje. Brenda një shembulli të vetëm LMDB, ju mund të krijoni baza të të dhënave të shumta që ndajnë hapësirën e adresave virtuale të hartës. Secila prej tyre fillon nga rrënja e vet.
  2. Në nivelin më të ulët janë gjethet (gjethe). Janë ata dhe vetëm ata që përmbajnë çiftet çelës-vlerë të ruajtur në bazën e të dhënave. Nga rruga, kjo është veçantia e pemëve B+. Nëse një pemë normale B ruan pjesët e vlerave në nyjet e të gjitha niveleve, atëherë variacioni B+ është vetëm në atë më të ulët. Duke e rregulluar këtë fakt, në vijim do ta quajmë nëntipin e pemës së përdorur në LMDB thjesht një pemë B.
  3. Midis rrënjës dhe gjetheve ka 0 ose më shumë nivele teknike me nyje lundrimi (degë). Detyra e tyre është të ndajnë grupin e çelësave të renditur midis gjetheve.

Fizikisht, nyjet janë blloqe kujtese me një gjatësi të paracaktuar. Madhësia e tyre është shumëfish i madhësisë së faqeve të kujtesës në sistemin operativ, për të cilin folëm më lart. Struktura e nyjeve është paraqitur më poshtë. Kreu përmban meta-informacion, më e dukshme prej të cilave, për shembull, është shuma e kontrollit. Më pas vjen informacioni për kompensimet, përgjatë të cilave ndodhen qelizat me të dhëna. Roli i të dhënave mund të jetë ose çelësat, nëse flasim për nyjet e lundrimit, ose çifte të tëra çelës-vlerë në rastin e gjetheve. Mund të lexoni më shumë rreth strukturës së faqeve në vepër "Vlerësimi i dyqaneve me vlerë kyçe me performancë të lartë".

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Pasi të kemi trajtuar përmbajtjen e brendshme të nyjeve të faqeve, ne do të paraqesim më tej pemën B LMDB në një mënyrë të thjeshtuar në formën e mëposhtme.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Faqet me nyje rregullohen në mënyrë sekuenciale në disk. Faqet me numër më të madh ndodhen në fund të skedarit. E ashtuquajtura meta faqe (faqe meta) përmban informacione për kompensimet, të cilat mund të përdoren për të gjetur rrënjët e të gjitha pemëve. Kur hapet një skedar, LMDB skanon skedarin faqe për faqe nga fundi në fillim në kërkim të një faqeje të vlefshme meta dhe gjen bazat e të dhënave ekzistuese përmes saj.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Tani, duke pasur një ide të strukturës logjike dhe fizike të organizimit të të dhënave, ne mund të vazhdojmë të shqyrtojmë balenën e tretë të LMDB. Është me ndihmën e tij që të gjitha modifikimet e ruajtjes ndodhin në mënyrë transaksionale dhe të izoluara nga njëra-tjetra, duke i dhënë bazës së të dhënave në tërësi edhe vetitë multiversionale.

3.3. Balena numër 3. kopje-në-shkruaj

Disa operacione të pemës B përfshijnë kryerjen e një serie të tërë ndryshimesh në nyjet e saj. Një shembull është shtimi i një çelësi të ri në një nyje që tashmë ka arritur kapacitetin e saj maksimal. Në këtë rast, është e nevojshme, së pari, të ndahet nyja në dysh, dhe së dyti, të shtohet një lidhje në nyjen e re të spunuar të fëmijës në prindin e saj. Kjo procedurë është potencialisht shumë e rrezikshme. Nëse për ndonjë arsye (përplasje, ndërprerje të energjisë, etj.) ndodh vetëm një pjesë e ndryshimeve nga seria, atëherë pema do të mbetet në një gjendje jokonsistente.

Një zgjidhje tradicionale për ta bërë një bazë të dhënash tolerante ndaj gabimeve është shtimi i një strukture shtesë të të dhënave të bazuar në disk, regjistri i transaksioneve, i njohur gjithashtu si regjistri i shkrimit përpara (WAL), pranë pemës B. Është një skedar, në fund të së cilës, rreptësisht para modifikimit të vetë pemës B, shkruhet operacioni i synuar. Kështu, nëse zbulohet korrupsioni i të dhënave gjatë vetë-diagnostikimit, baza e të dhënave konsultohet me regjistrin për të pastruar veten.

LMDB ka zgjedhur një metodë tjetër si mekanizmin e saj të tolerancës së gabimeve, e cila quhet copy-on-write. Thelbi i tij është që në vend që të përditësojë të dhënat në një faqe ekzistuese, ai fillimisht i kopjon ato tërësisht dhe i bën të gjitha modifikimet tashmë në kopje.​

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Më tej, në mënyrë që të dhënat e përditësuara të jenë të disponueshme, është e nevojshme të ndryshohet lidhja me nyjen që është bërë e përditësuar në nyjen mëmë në lidhje me të. Meqenëse për këtë duhet të modifikohet gjithashtu, ai është gjithashtu i kopjuar paraprakisht. Procesi vazhdon në mënyrë rekursive deri në rrënjë. Të dhënat në faqen meta janë të fundit që ndryshojnë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Nëse papritmas procesi rrëzohet gjatë procedurës së përditësimit, atëherë ose nuk do të krijohet një faqe e re meta, ose nuk do të shkruhet në disk deri në fund, dhe shuma e kontrollit të saj do të jetë e pasaktë. Në secilin prej këtyre dy rasteve, faqet e reja do të jenë të paarritshme dhe ato të vjetrat nuk do të preken. Kjo eliminon nevojën që LMDB të shkruajë regjistrin përpara për të ruajtur konsistencën e të dhënave. De fakto, struktura e ruajtjes së të dhënave në disk, e përshkruar më sipër, merr njëkohësisht funksionin e saj. Mungesa e një regjistri të qartë të transaksioneve është një nga veçoritë e LMDB, e cila siguron shpejtësi të lartë të leximit të të dhënave.​

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Konstruksioni që rezulton, i quajtur B-pema vetëm me shtojcë, siguron natyrshëm izolimin e transaksionit dhe multiversionimin. Në LMDB, çdo transaksion i hapur ka një rrënjë peme të përditësuar të lidhur me të. Për sa kohë që transaksioni nuk është përfunduar, faqet e pemës së lidhur me të nuk do të ndryshohen ose ripërdoren kurrë për versionet e reja të të dhënave. Kështu, ju mund të punoni për aq kohë sa dëshironi saktësisht me grupin e të dhënave që ishte relevant në koha kur transaksioni u hap, edhe nëse ruajtja vazhdon të përditësohet në mënyrë aktive në këtë moment. Ky është thelbi i multiversionimit, duke e bërë LMDB një burim ideal të dhënash për të dashurit tanë UICollectionView. Pasi të keni hapur një transaksion, nuk keni nevojë të rrisni gjurmën e kujtesës së aplikacionit, duke pompuar me ngut të dhënat aktuale në një strukturë në memorie, duke pasur frikë të mos mbeteni pa asgjë. Kjo veçori e dallon LMDB nga i njëjti SQLite, i cili nuk mund të mburret me një izolim të tillë total. Pas hapjes së dy transaksioneve në këtë të fundit dhe fshirjes së një regjistrimi të caktuar brenda njërit prej tyre, i njëjti rekord nuk mund të merret më brenda të dytit të mbetur.

Ana e kundërt e medaljes është konsumi potencialisht dukshëm më i lartë i memories virtuale. Sllajdi tregon se si do të duket struktura e bazës së të dhënave nëse modifikohet në të njëjtën kohë me 3 transaksione të hapura të lexuara që shikojnë versione të ndryshme të bazës së të dhënave. Meqenëse LMDB nuk mund të ripërdorë nyjet që janë të arritshme nga rrënjët e lidhura me transaksionet aktuale, ruajtja nuk ka zgjidhje tjetër veçse të ndajë një rrënjë tjetër të katërt në memorie dhe të klonojë përsëri faqet e modifikuara nën të.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Këtu nuk do të jetë e tepërt të kujtoni seksionin për skedarët e hartuar me kujtesë. Duket se konsumi shtesë i memories virtuale nuk duhet të na shqetësojë shumë, pasi nuk kontribuon në gjurmën e memories së aplikacionit. Sidoqoftë, në të njëjtën kohë, u vu re se iOS është shumë dorështrënguar në shpërndarjen e tij dhe ne nuk mund të ofrojmë një rajon LMDB 1 terabajt në një server ose desktop nga supi i zotit dhe të mos mendojmë fare për këtë veçori. Kur është e mundur, duhet të përpiqeni ta mbani sa më të shkurtër jetëgjatësinë e transaksioneve.

4. Dizajnimi i një skeme të dhënash në krye të API-së me vlerë kyçe

Le të fillojmë analizimin e API-së duke parë abstraksionet bazë të ofruara nga LMDB: mjedisi dhe bazat e të dhënave, çelësat dhe vlerat, transaksionet dhe kursorët.

Një shënim në lidhje me listat e kodeve

Të gjitha funksionet në API-në publike LMDB e kthejnë rezultatin e punës së tyre në formën e një kodi gabimi, por në të gjitha listimet e mëvonshme kontrolli i tij hiqet për hir të koncizitetit. Në praktikë, ne përdorëm kodin tonë për të bashkëvepruar me depon. pirun mbështjellës C++ lmdbxx, në të cilën gabimet materializohen si përjashtime në C++.

Si mënyra më e shpejtë për të lidhur LMDB me një projekt iOS ose macOS, unë ofroj CocoaPod-in tim POSLMDB.

4.1. Abstraksionet bazë

Mjedisi

Strukturë MDB_env është është depoja e gjendjes së brendshme të LMDB. Familja e funksioneve të paracaktuara mdb_env ju lejon të konfiguroni disa nga vetitë e tij. Në rastin më të thjeshtë, inicializimi i motorit duket kështu.

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

Në aplikacionin Mail.ru Cloud, ne ndryshuam vlerat e paracaktuara vetëm për dy parametra.

E para është madhësia e hapësirës së adresës virtuale në të cilën është hartuar skedari i ruajtjes. Fatkeqësisht, edhe në të njëjtën pajisje, vlera specifike mund të ndryshojë ndjeshëm nga ekzekutimi në ekzekutim. Për të marrë parasysh këtë veçori të iOS, ne zgjedhim sasinë maksimale të ruajtjes në mënyrë dinamike. Duke u nisur nga një vlerë e caktuar, ajo përgjysmohet radhazi deri në funksion mdb_env_open nuk do të kthejë një rezultat tjetër përveç ENOMEM. Në teori, ekziston një mënyrë e kundërt - së pari caktoni një minimum memorie në motor, dhe më pas, kur merren gabime MDB_MAP_FULL, rriteni atë. Megjithatë, është shumë më e mprehtë. Arsyeja është se procedura për rimapifikimin e kujtesës duke përdorur funksionin mdb_env_set_map_size zhvlerëson të gjitha entitetet (kursorët, transaksionet, çelësat dhe vlerat) të marra nga motori më herët. Kontabilizimi për një kthesë të tillë të ngjarjeve në kod do të çojë në ndërlikimin e tij të rëndësishëm. Nëse, megjithatë, kujtesa virtuale është shumë e dashur për ju, atëherë kjo mund të jetë një arsye për të parë pirunin që ka shkuar shumë përpara. MDBX, ku ndër veçoritë e deklaruara ka “rregullim automatik të madhësisë së bazës së të dhënave në fluturim”.

Parametri i dytë, vlera e paracaktuar e të cilit nuk na përshtatet, rregullon mekanikën e sigurimit të fijeve. Fatkeqësisht, të paktën në iOS 10, ka probleme me mbështetjen e ruajtjes lokale të fijeve. Për këtë arsye, në shembullin e mësipërm, depoja hapet me flamurin MDB_NOTLS. Përveç kësaj, kërkohej gjithashtu pirun mbështjellës C++ lmdbxxpër të prerë variablat me dhe në këtë atribut.

Bazat e të dhënave

Baza e të dhënave është një shembull i veçantë i pemës B për të cilën folëm më lart. Hapja e tij ndodh brenda një transaksioni, i cili në fillim mund të duket pak i çuditshëm.

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

Në të vërtetë, një transaksion në LMDB është një ent ruajtjeje, jo një bazë të dhënash specifike. Ky koncept ju lejon të kryeni operacione atomike në entitete të vendosura në baza të të dhënave të ndryshme. Në teori, kjo hap mundësinë e modelimit të tabelave në formën e bazave të të dhënave të ndryshme, por në një kohë unë shkova në anën tjetër, të përshkruar në detaje më poshtë.

Çelësat dhe vlerat

Strukturë MDB_val modelon konceptin si të çelësit ashtu edhe të vlerës. Depoja nuk ka asnjë ide për semantikën e tyre. Për të, diçka që është e ndryshme është vetëm një grup bajtësh të një madhësie të caktuar. Madhësia maksimale e çelësit është 512 bajt.

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

Dyqani përdor një krahasues për të renditur çelësat në rend rritës. Nëse nuk e zëvendësoni me tuajin, atëherë do të përdoret ai i paracaktuar, i cili i rendit ato sipas rendit leksikografik.

transaksionet

Pajisja e transaksionit përshkruhet në detaje në kapitulli i mëparshëm, kështu që këtu do të përsëris vetitë e tyre kryesore në një rresht të shkurtër:

  1. Mbështetje për të gjitha pronat bazë ACIDFjalët kyçe: atomiciteti, qëndrueshmëria, izolimi dhe besueshmëria. Nuk mund të mos vërej se për sa i përket qëndrueshmërisë në macOS dhe iOS ka një gabim të rregulluar në MDBX. Mund të lexoni më shumë në to README.
  2. Qasja ndaj multithreading përshkruhet nga skema "shkrimtar i vetëm / lexues të shumtë". Shkrimtarët bllokojnë njëri-tjetrin, por nuk bllokojnë lexuesit. Lexuesit nuk bllokojnë shkrimtarët apo njëri-tjetrin.
  3. Mbështetje për transaksionet e ndërlidhura.
  4. Mbështetje për shumë versione.

Multiversioning në LMDB është aq i mirë sa unë dua ta demonstroj atë në veprim. Kodi i mëposhtëm tregon se çdo transaksion funksionon saktësisht me versionin e bazës së të dhënave që ishte relevant në kohën e hapjes së tij, duke qenë plotësisht i izoluar nga të gjitha ndryshimet e mëvonshme. Inicializimi i depove dhe shtimi i një regjistrimi testimi në të nuk është me interes, kështu që këto rituale lihen nën spoiler.

Shtimi i një hyrje testimi

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

Opsionale, unë rekomandoj të provoni të njëjtin mashtrim me SQLite dhe të shihni se çfarë ndodh.

Multiversioning sjell përfitime shumë të mira në jetën e një zhvilluesi iOS. Duke përdorur këtë veçori, mund të rregulloni lehtësisht dhe natyrshëm shpejtësinë e përditësimit të burimit të të dhënave për format e ekranit bazuar në konsideratat e përvojës së përdoruesit. Për shembull, le të marrim një veçori të tillë të aplikacionit Mail.ru Cloud si ngarkimi automatik i përmbajtjes nga galeria e mediave të sistemit. Me një lidhje të mirë, klienti është në gjendje të shtojë disa foto në sekondë në server. Nëse përditësoni pas çdo shkarkimi UICollectionView me përmbajtjen e medias në renë kompjuterike të përdoruesit, mund të harroni rreth 60 fps dhe lëvizje të qetë gjatë këtij procesi. Për të parandaluar përditësimet e shpeshta të ekranit, duhet të kufizoni disi shkallën e ndryshimit të të dhënave në bazë UICollectionViewDataSource.

Nëse baza e të dhënave nuk mbështet multiversionimin dhe ju lejon të punoni vetëm me gjendjen aktuale, atëherë për të krijuar një fotografi të të dhënave të qëndrueshme në kohë, duhet ta kopjoni atë ose në ndonjë strukturë të dhënash në memorie ose në një tabelë të përkohshme. Secila prej këtyre qasjeve është shumë e shtrenjtë. Në rastin e ruajtjes në memorie, marrim si kostot e memories të shkaktuara nga ruajtja e objekteve të ndërtuara ashtu edhe kostot e kohës që lidhen me transformimet e tepërta ORM. Sa i përket tryezës së përkohshme, kjo është një kënaqësi edhe më e shtrenjtë, e cila ka kuptim vetëm në raste jo të parëndësishme.

Multiversioning LMDB zgjidh problemin e mbajtjes së një burimi të qëndrueshëm të të dhënave në një mënyrë shumë elegante. Mjafton vetëm të hapim një transaksion dhe voila - derisa ta përfundojmë, grupi i të dhënave është i garantuar të rregullohet. Logjika e shkallës së përditësimit të tij tani është tërësisht në duart e shtresës së prezantimit, pa shpenzime të larta të burimeve të rëndësishme.

Kursorët

Kursorët sigurojnë një mekanizëm për përsëritjen e rregullt mbi çiftet çelës-vlerë duke përshkuar një pemë B. Pa to, do të ishte e pamundur të modeloheshin në mënyrë efektive tabelat në bazën e të dhënave, të cilave ne i drejtohemi tani.

4.2. Modelimi i tabelës

Vetia e renditjes së çelësave ju lejon të ndërtoni një abstraksion të nivelit të lartë, si p.sh. një tabelë në krye të abstraksioneve bazë. Le ta shqyrtojmë këtë proces në shembullin e tabelës kryesore të klientit cloud, në të cilin ruhen informacionet për të gjithë skedarët dhe dosjet e përdoruesit.

Skema e tabelës

Një nga skenarët e zakonshëm për të cilin struktura e një tabele me një pemë folder duhet të mprehet është zgjedhja e të gjithë elementëve të vendosur brenda një drejtorie të caktuar.Një model i mirë organizimi i të dhënave për pyetje efikase të këtij lloji është Lista e afërsisë. Për ta zbatuar atë në krye të ruajtjes së vlerës së çelësit, është e nevojshme të renditni çelësat e skedarëve dhe dosjeve në mënyrë të tillë që ata të grupohen në bazë të përkatësisë në drejtorinë prind. Për më tepër, për të shfaqur përmbajtjen e drejtorisë në formën e njohur për një përdorues të Windows (dosjet së pari, pastaj skedarët, të dyja renditen sipas alfabetit), është e nevojshme të përfshini fushat përkatëse shtesë në çelës.

​Figura më poshtë tregon se si, bazuar në detyrën, mund të duket paraqitja e çelësave si një grup bajtësh. Fillimisht vendosen bajtet me identifikuesin e direktoriumit prind (e kuqe), me pas me tipin (jeshile) dhe ne bisht me emrin (blu), duke u renditur sipas paracaktuar krahasues LMDB sipas rendit leksikografik, renditen ne mënyrën e kërkuar. Kalimi i njëpasnjëshëm i çelësave me të njëjtin prefiks të kuq na jep vlerat e lidhura me ta në rendin në të cilin duhet të shfaqen në ndërfaqen e përdoruesit (djathtas), pa kërkuar ndonjë post-përpunim shtesë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Serializimi i çelësave dhe vlerave

Ka shumë metoda për serializimin e objekteve në mbarë botën. Meqenëse nuk kishim asnjë kërkesë tjetër përveç shpejtësisë, zgjodhëm atë më të shpejtë të mundshme për veten tonë - një grumbull memorie të zënë nga një shembull i strukturës së gjuhës C. Pra, çelësi i një elementi direktoriumi mund të modelohet nga struktura e mëposhtme NodeKey.

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

Të ruaj NodeKey në nevojë për ruajtje në objekt MDB_val poziciononi treguesin drejt të dhënave në adresën e fillimit të strukturës dhe llogaritni madhësinë e tyre me funksionin sizeof.

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

Në kapitullin e parë mbi kriteret e përzgjedhjes së bazës së të dhënave, përmenda minimizimin e alokimeve dinamike si pjesë e operacioneve CRUD si një faktor i rëndësishëm përzgjedhjeje. Kodi i funksionit serialize tregon se si, në rastin e LMDB, ato mund të shmangen plotësisht kur të dhënat e reja futen në bazën e të dhënave. Grupet hyrëse të bajteve nga serveri fillimisht transformohen në struktura stek, dhe më pas ato hidhen në mënyrë të parëndësishme në ruajtje. Duke pasur parasysh që nuk ka as alokime dinamike brenda LMDB, mund të merrni një situatë fantastike sipas standardeve të iOS - përdorni vetëm memorie stivore për të punuar me të dhënat gjatë gjithë rrugës nga rrjeti në disk!

Renditja e çelësave me një krahasues binar

Lidhja e rendit të çelësit jepet nga një funksion i veçantë i quajtur krahasues. Meqenëse motori nuk di asgjë për semantikën e bajteve që përmbajnë, krahasuesi i paracaktuar nuk ka zgjidhje tjetër veçse t'i rregullojë çelësat sipas rendit leksikografik, duke përdorur krahasimin e tyre bajt pas bajt. Përdorimi i tij për të rregulluar strukturat është i ngjashëm me rruajtjen me një sëpatë gdhendjeje. Megjithatë, në raste të thjeshta, kjo metodë më duket e pranueshme. Alternativa përshkruhet më poshtë, por këtu do të shënoj disa raketa të shpërndara gjatë rrugës.

Gjëja e parë që duhet mbajtur parasysh është paraqitja e memories së llojeve primitive të të dhënave. Pra, në të gjitha pajisjet Apple, variablat e numrave të plotë ruhen në format Endiani i Vogël. Kjo do të thotë se bajt më pak i rëndësishëm do të jetë në të majtë dhe ju nuk do të jeni në gjendje të renditni numrat e plotë duke përdorur krahasimin e tyre bajt pas bajt. Për shembull, duke u përpjekur ta bëni këtë me një grup numrash nga 0 në 511 do të rezultojë në rezultatin e mëposhtëm.

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

Për të zgjidhur këtë problem, numrat e plotë duhet të ruhen në çelës në një format të përshtatshëm për krahasuesin e bajtit. Funksionet nga familja do të ndihmojnë për të kryer transformimin e nevojshëm. hton* (veçanërisht htons për numrat me dy bajtë nga shembulli).

Formati për paraqitjen e vargjeve në programim është, siç e dini, një e tërë histori. Nëse semantika e vargjeve, si dhe kodimi i përdorur për t'i përfaqësuar ato në memorie, sugjeron që mund të ketë më shumë se një bajt për karakter, atëherë është më mirë të braktisni menjëherë idenë e përdorimit të një krahasuesi të paracaktuar.

Gjëja e dytë që duhet mbajtur parasysh është parimet e shtrirjes përpilues i fushës struct. Për shkak të tyre, bajtë me vlera mbeturinash mund të formohen në kujtesë midis fushave, të cilat, natyrisht, prishin renditjen e bajtit. Për të eliminuar mbeturinat, duhet ose të deklaroni fushat në një rend të përcaktuar rreptësisht, duke mbajtur parasysh rregullat e shtrirjes, ose të përdorni atributin në deklaratën e strukturës packed.

Renditja e çelësave nga një krahasues i jashtëm

Logjika kryesore e krahasimit mund të rezultojë të jetë shumë komplekse për një krahasues binar. Një nga arsyet e shumta është prania e fushave teknike brenda strukturave. Unë do të ilustroj shfaqjen e tyre në shembullin e një çelësi tashmë të njohur për ne për një element drejtorie.

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

Me gjithë thjeshtësinë e tij, në shumicën dërrmuese të rasteve konsumon shumë memorie. Buferi i titullit është 256 bajt, megjithëse emrat mesatarë të skedarëve dhe dosjeve rrallë kalojnë 20-30 karaktere.

Një nga teknikat standarde për optimizimin e madhësisë së një rekordi është shkurtimi i tij për t'iu përshtatur madhësisë së tij aktuale. Thelbi i saj është se përmbajtja e të gjitha fushave me gjatësi të ndryshueshme ruhet në një bufer në fund të strukturës dhe gjatësitë e tyre ruhen në variabla të veçantë.Në përputhje me këtë qasje, çelësi NodeKey transformohet në mënyrën e mëposhtme.

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

Më tej, gjatë serializimit, nuk specifikohet si madhësia e të dhënave sizeof e gjithë struktura, dhe madhësia e të gjitha fushave është gjatësia fikse plus madhësia e pjesës së përdorur aktualisht të buferit.

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

Si rezultat i rifaktorimit, ne morëm një kursim të konsiderueshëm në hapësirën e zënë nga çelësat. Megjithatë, për shkak të fushës teknike nameLength, krahasuesi binar i parazgjedhur nuk është më i përshtatshëm për krahasimin e çelësave. Nëse nuk e zëvendësojmë me tonin, atëherë gjatësia e emrit do të jetë një faktor më prioritar në renditje sesa vetë emri.

LMDB lejon çdo bazë të dhënash të ketë funksionin e vet të krahasimit të çelësave. Kjo bëhet duke përdorur funksionin mdb_set_compare rreptësisht para hapjes. Për arsye të dukshme, një bazë të dhënash nuk mund të ndryshohet gjatë gjithë jetës së saj. Në hyrje, krahasuesi merr dy çelësa në format binar, dhe në dalje kthen rezultatin e krahasimit: më pak se (-1), më i madh se (1) ose i barabartë (0). Pseudokodi për NodeKey duket kështu.

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

Për sa kohë që të gjithë çelësat në bazën e të dhënave janë të të njëjtit lloj, është e ligjshme që përfaqësimi i tyre i bajtit të transferohet pa kushte në llojin e strukturës së aplikimit të çelësit. Ekziston një nuancë këtu, por ajo do të diskutohet pak më poshtë në nënseksionin "Record Records".

Serializimi i vlerave

Me çelësat e regjistrimeve të ruajtura, LMDB punon jashtëzakonisht intensivisht. Ato krahasohen me njëri-tjetrin në kuadër të çdo operacioni aplikimi, dhe performanca e të gjithë zgjidhjes varet nga shpejtësia e krahasuesit. Në një botë ideale, krahasuesi binar i parazgjedhur duhet të jetë i mjaftueshëm për të krahasuar çelësat, por nëse vërtet duhet të përdorni tuajin, atëherë procedura për deserializimin e çelësave në të duhet të jetë sa më e shpejtë që të jetë e mundur.

Baza e të dhënave nuk është veçanërisht e interesuar për pjesën Vlera të rekordit (vlerës). Shndërrimi i tij nga një paraqitje bajt në një objekt ndodh vetëm kur kërkohet tashmë nga kodi i aplikacionit, për shembull, për ta shfaqur atë në ekran. Meqenëse kjo ndodh relativisht rrallë, kërkesat për shpejtësinë e kësaj procedure nuk janë aq kritike, dhe në zbatimin e saj jemi shumë më të lirë të fokusohemi te komoditeti. Për shembull, për të serializuar meta të dhënat rreth skedarëve që nuk janë shkarkuar ende, ne përdorim NSKeyedArchiver.

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

Sidoqoftë, ka raste kur performanca ka rëndësi. Për shembull, kur ruajmë meta-informacionet në lidhje me strukturën e skedarit të resë së përdoruesit, ne përdorim të njëjtin depozitim të memories së objektit. Pika kryesore e detyrës së gjenerimit të përfaqësimit të tyre të serializuar është fakti që elementët e një drejtorie modelohen nga një hierarki klase.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Për zbatimin e tij në gjuhën C, fushat specifike të trashëgimtarëve nxirren në struktura të veçanta dhe lidhja e tyre me bazën përcaktohet përmes një fushe të tipit bashkim. Përmbajtja aktuale e bashkimit specifikohet nëpërmjet atributit teknik tip.

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

Shtimi dhe përditësimi i të dhënave

Çelësi i serializuar dhe vlera mund të shtohen në dyqan. Për këtë përdoret funksioni mdb_put.

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

Në fazën e konfigurimit, depoja mund të lejohet ose të ndalohet të ruajë shumë regjistrime me të njëjtin çelës.​ Nëse dublikimi i çelësave është i ndaluar, atëherë kur futni një rekord, mund të përcaktoni nëse përditësimi i një regjistrimi tashmë ekzistues lejohet apo jo. Nëse prishja mund të ndodhë vetëm si rezultat i një gabimi në kod, atëherë mund të siguroheni kundër tij duke specifikuar flamurin NOOVERWRITE.

Leximi i të dhënave

Funksioni për leximin e të dhënave në LMDB është mdb_get. Nëse çifti çelës-vlerë përfaqësohet nga struktura të hedhura më parë, atëherë kjo procedurë duket kështu.

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

Lista e paraqitur tregon sesi serializimi përmes një deponie strukturash ju lejon të shpëtoni nga alokimet dinamike jo vetëm kur shkruani, por kur lexoni të dhëna. Rrjedh nga funksioni mdb_get treguesi shikon saktësisht adresën e memories virtuale ku baza e të dhënave ruan paraqitjen me bajt të objektit. Në fakt, ne marrim një lloj ORM, pothuajse falas duke siguruar një shpejtësi shumë të lartë të leximit të të dhënave. Me gjithë bukurinë e qasjes, është e nevojshme të mbani mend disa veçori që lidhen me të.

  1. Për një transaksion vetëm për lexim, një tregues në një strukturë vlere është i garantuar të mbetet i vlefshëm vetëm derisa transaksioni të mbyllet. Siç u përmend më herët, faqet e pemës B në të cilën ndodhet objekti, falë parimit copy-on-write, mbeten të pandryshuara për sa kohë që të paktën një transaksion u referohet atyre. Në të njëjtën kohë, sapo të kryhet transaksioni i fundit i lidhur me to, faqet mund të ripërdoren për të dhëna të reja. Nëse është e nevojshme që objektet t'i mbijetojnë transaksionit që i krijoi ato, atëherë ato ende duhet të kopjohen.
  2. Për një transaksion të leximit, treguesi i vlerës së strukturës që rezulton do të jetë i vlefshëm vetëm deri në procedurën e parë të modifikimit (shkrimi ose fshirja e të dhënave).
  3. Edhe pse struktura NodeValue jo të plota, por të shkurtuara (shih nënseksionin "Renditja e çelësave nga një krahasues i jashtëm"), përmes treguesit, mund të përdorni lehtësisht fushat e tij. Gjëja kryesore është të mos e anuloni atë!
  4. Në asnjë rast nuk mund ta modifikoni strukturën përmes treguesit të marrë. Të gjitha ndryshimet duhet të bëhen vetëm përmes metodës mdb_put. Sidoqoftë, me gjithë dëshirën për ta bërë këtë, nuk do të funksionojë, pasi zona e kujtesës ku ndodhet kjo strukturë është hartuar në modalitetin vetëm për lexim.
  5. Rimarrë një skedar në hapësirën e adresave të një procesi në mënyrë që, për shembull, të rritet madhësia maksimale e ruajtjes duke përdorur funksionin mdb_env_set_map_size zhvleftëson plotësisht të gjitha transaksionet dhe entitetet e lidhura në përgjithësi dhe treguesit për të lexuar objekte në veçanti.

Së fundi, një veçori tjetër është aq tinëzare sa zbulimi i thelbit të tij nuk përshtatet vetëm në një pikë më shumë. Në kapitullin e pemës B, dhashë një diagram të organizimit të faqeve të saj në kujtesë. Nga kjo rrjedh se adresa e fillimit të tamponit me të dhëna të serializuara mund të jetë absolutisht arbitrare. Për shkak të kësaj, treguesi për to, i marrë në strukturë MDB_val dhe të hedhura në një tregues në një strukturë është përgjithësisht i padrejtuar. Në të njëjtën kohë, arkitekturat e disa çipave (në rastin e iOS, ky është armv7) kërkojnë që adresa e çdo të dhënë të jetë shumëfish i madhësisë së një fjale makinerie, ose, me fjalë të tjera, bitness e sistemit. (për armv7, kjo është 32 bit). Me fjalë të tjera, një operacion si *(int *foo)0x800002 mbi to barazohet me arratisje dhe çon në ekzekutim me aktgjykim EXC_ARM_DA_ALIGN. Ka dy mënyra për të shmangur një fat kaq të trishtuar.

E para është kopjimi i të dhënave në një strukturë të njohur më parë. Për shembull, në një krahasues me porosi, kjo do të pasqyrohet si më poshtë.

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

Një mënyrë alternative është të njoftoni paraprakisht përpiluesin se strukturat me një çelës dhe vlerë mund të mos përafrohet duke përdorur një atribut aligned(1). Në ARM mund të jetë i njëjti efekt arritur dhe duke përdorur atributin e paketuar. Duke marrë parasysh që kontribuon edhe në optimizimin e hapësirës së zënë nga struktura, kjo metodë më duket e preferueshme, megjithëse приводит për të rritur koston e operacioneve të aksesit të të dhënave.

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

Pyetjet e gamës

Për të përsëritur mbi një grup regjistrimesh, LMDB ofron një abstraksion të kursorit. Le të shohim se si të punojmë me të duke përdorur shembullin e një tabele me meta të dhënat e cloud të përdoruesve tashmë të njohura për ne.

Si pjesë e shfaqjes së një liste skedarësh në një drejtori, ju duhet të gjeni të gjithë çelësat me të cilët lidhen skedarët dhe dosjet e tij fëmijë. Në nënseksionet e mëparshme, ne renditëm çelësat NodeKey në mënyrë që ato së pari të renditen sipas ID-së së drejtorisë së tyre mëmë. Kështu, teknikisht, detyra e marrjes së përmbajtjes së një dosjeje reduktohet në vendosjen e kursorit në kufirin e sipërm të një grupi çelësash me një parashtesë të caktuar, e ndjekur nga përsëritja në kufirin e poshtëm.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Ju mund ta gjeni kufirin e sipërm "në ballë" me kërkim të njëpasnjëshëm. Për ta bërë këtë, kursori vendoset në fillim të të gjithë listës së çelësave në bazën e të dhënave dhe më pas rritet derisa tasti me identifikuesin e drejtorisë mëmë të shfaqet poshtë tij. Kjo qasje ka 2 disavantazhe të dukshme:

  1. Kompleksiteti linear i kërkimit, megjithëse, siç e dini, në pemë në përgjithësi dhe në një pemë B në veçanti, ai mund të bëhet në kohën logaritmike.
  2. Më kot, të gjitha faqet që i paraprijnë asaj të dëshiruar ngrihen nga skedari në memorien kryesore, e cila është jashtëzakonisht e shtrenjtë.

Për fat të mirë, API LMDB ofron një mënyrë efikase për të pozicionuar fillimisht kursorin. Për ta bërë këtë, ju duhet të formoni një çelës, vlera e të cilit dihet se është më e vogël ose e barabartë me çelësin e vendosur në kufirin e sipërm të intervalit. Për shembull, në lidhje me listën në figurën e mësipërme, ne mund të bëjmë një çelës në të cilin fusha parentId do të jetë e barabartë me 2, dhe të gjitha të tjerat janë të mbushura me zero. Një çelës i tillë i mbushur pjesërisht futet në hyrjen e funksionit mdb_cursor_get duke treguar funksionimin 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);

Nëse gjendet kufiri i sipërm i grupit të çelësave, atëherë përsërisim mbi të derisa të takohemi ose çelësi me një tjetër parentId, ose çelësat nuk do të mbarojnë fare.

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​​

Ajo që është e bukur, si pjesë e përsëritjes duke përdorur mdb_cursor_get, marrim jo vetëm çelësin, por edhe vlerën. Nëse, për të përmbushur kushtet e përzgjedhjes, është e nevojshme të kontrollohen, ndër të tjera, edhe fushat nga pjesa e vlerës së regjistrimit, atëherë ato janë mjaft të aksesueshme për veten e tyre pa gjeste shtesë.

4.3. Modelimi i marrëdhënieve ndërmjet tabelave

Deri më sot, ne kemi arritur të marrim parasysh të gjitha aspektet e projektimit dhe punës me një bazë të dhënash me një tabelë të vetme. Mund të themi se një tabelë është një grup regjistrimesh të renditura që përbëhet nga çifte çelës-vlerë të të njëjtit lloj. Nëse e shfaqni një çelës si një drejtkëndësh dhe vlerën e lidhur me të si një kuti, ju merrni një diagram vizual të bazës së të dhënave.

â € <

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Megjithatë, në jetën reale, rrallë është e mundur të ia dalësh me kaq pak gjak. Shpesh në një bazë të dhënash kërkohet, së pari, të ketë disa tabela, dhe së dyti, të kryhen zgjedhje në to në një rend të ndryshëm nga çelësi kryesor. Ky seksion i fundit i kushtohet çështjeve të krijimit dhe ndërlidhjes së tyre.

Tabelat e indeksit

Aplikacioni cloud ka një seksion "Galeria". Ai shfaq përmbajtjen e medias nga e gjithë cloud, të renditur sipas datës. Për zbatimin optimal të një përzgjedhjeje të tillë, pranë tabelës kryesore, duhet të krijoni një tjetër me një lloj të ri çelësash. Ato do të përmbajnë një fushë me datën e krijimit të skedarit, e cila do të veprojë si kriteri kryesor i renditjes. Për shkak se çelësat e rinj u referohen të njëjtave të dhëna si çelësat në tabelën bazë, ata quhen çelësat e indeksit. Ato janë të theksuara në ngjyrë portokalli në foton më poshtë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Për të ndarë çelësat e tabelave të ndryshme nga njëri-tjetri brenda të njëjtës bazë të dhënash, të gjithëve u është shtuar një tabelë shtesë teknike e fushës. Duke e bërë atë prioritetin më të lartë për renditjen, ne do t'i grupojmë çelësat fillimisht sipas tabelave dhe tashmë brenda tabelave - sipas rregullave tona.

Çelësi i indeksit i referohet të njëjtave të dhëna si çelësi primar. Zbatimi i drejtpërdrejtë i kësaj veçorie duke e lidhur me të një kopje të pjesës së vlerës së çelësit primar është jooptimal nga disa këndvështrime njëherësh:​

  1. Nga pikëpamja e zënë hapësirë, meta të dhënat mund të jenë mjaft të pasura.
  2. Nga pikëpamja e performancës, meqenëse kur përditësoni metadatat e nyjeve, do t'ju duhet të mbishkruani dy çelësa.
  3. Nga pikëpamja e mbështetjes së kodit, në fund të fundit, nëse harrojmë të përditësojmë të dhënat për një nga çelësat, do të kemi një gabim delikate të mospërputhjes së të dhënave në ruajtje.

Më tej, ne do të shqyrtojmë se si t'i eliminojmë këto mangësi.

Organizimi i marrëdhënieve ndërmjet tabelave

Modeli është i përshtatshëm për lidhjen e tabelës së indeksit me atë kryesore "Çelësi si vlerë". Siç nënkupton edhe emri i saj, pjesa e vlerës së rekordit të indeksit është një kopje e vlerës së çelësit primar. Kjo qasje eliminon të gjitha disavantazhet e listuara më sipër që lidhen me ruajtjen e një kopjeje të pjesës së vlerës së rekordit primar. Tarifa e vetme është që për të marrë vlerën me çelësin e indeksit, duhet të bëni 2 pyetje në bazën e të dhënave në vend të një. Skematikisht, skema e bazës së të dhënave që rezulton është si më poshtë.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Një model tjetër për organizimin e marrëdhënieve ndërmjet tabelave është "çelës i tepërt". Thelbi i tij është t'i shtojë çelësit atribute shtesë, të cilat nevojiten jo për renditjen, por për rikrijimin e çelësit të lidhur. Megjithatë, ekzistojnë shembuj realë të përdorimit të tij në aplikacionin Mail.ru Cloud, për të shmangur zhytjen e thellë në kontekstin e kornizave specifike të iOS, do të jap një shembull fiktiv, por më të kuptueshëm.

Klientët celularë në renë kompjuterike kanë një faqe që shfaq të gjithë skedarët dhe dosjet që përdoruesi ka ndarë me njerëzit e tjerë. Meqenëse ka relativisht pak skedarë të tillë dhe ka shumë informacione specifike në lidhje me publicitetin që lidhen me to (kujt i jepet akses, me çfarë të drejtash etj.), nuk do të ishte racionale ta ngarkojmë atë me pjesën e vlerës së rekordin në tabelën kryesore. Sidoqoftë, nëse dëshironi të shfaqni skedarë të tillë jashtë linje, atëherë duhet t'i ruani ato diku. Një zgjidhje e natyrshme është krijimi i një tabele të veçantë për të. Në diagramin e mëposhtëm, çelësi i tij është i parashtesuar me "P" dhe mbajtësi i vendndodhjes "propname" mund të zëvendësohet me vlerën më specifike "informacion publik".​

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Të gjitha meta të dhënat unike, për hir të të cilave u krijua tabela e re, zhvendosen në pjesën e vlerës së rekordit. Në të njëjtën kohë, nuk dua të kopjoj të dhënat për skedarët dhe dosjet që janë ruajtur tashmë në tabelën kryesore. Në vend të kësaj, të dhënat e tepërta i shtohen çelësit "P" në formën e fushave "ID-ja e nyjes" dhe "vula kohore". Falë tyre, ju mund të ndërtoni një çelës indeks, me të cilin mund të merrni çelësin primar, me të cilin, më në fund, mund të merrni metadatat e nyjes.

konkluzioni

Ne i vlerësojmë pozitivisht rezultatet e zbatimit të LMDB. Pas tij, numri i ngrirjeve të aplikacioneve u ul me 30%.

Shkëlqimi dhe varfëria e bazës së të dhënave me vlerë kyçe LMDB në aplikacionet iOS

Rezultatet e punës së bërë kanë gjetur një përgjigje jashtë ekipit të iOS. Aktualisht, një nga seksionet kryesore "Files" në aplikacionin Android ka kaluar gjithashtu në përdorimin e LMDB dhe pjesë të tjera janë në rrugë e sipër. Gjuha C, në të cilën zbatohet ruajtja e vlerës së çelësit, ishte një ndihmë e mirë për ta bërë fillimisht aplikacionin të detyrueshëm rreth tij ndër-platformë në gjuhën C ++. Për një lidhje pa probleme të bibliotekës C ++ që rezulton me kodin e platformës në Objective-C dhe Kotlin, u përdor një gjenerator kodi Xhini nga Dropbox, por kjo është një histori tjetër.

Burimi: www.habr.com

Shto një koment