iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

2019 оны намар Mail.ru Cloud iOS-ийн багт удаан хүлээсэн үйл явдал болсон. Програмын төлөвийг байнга хадгалах үндсэн мэдээллийн сан нь гар утасны ертөнцөд маш чамин болсон Lightning Memory-Mapped Database (LMDB). Зүсэлтийн доор бид танд дөрвөн хэсгээс бүрдсэн дэлгэрэнгүй тоймыг санал болгож байна. Эхлээд ийм өчүүхэн бус, хэцүү сонголт хийх болсон шалтгаануудын талаар ярилцъя. Дараа нь бид LMDB архитектурын гол гурван тулгуурыг авч үзэх болно: санах ойд суурилсан файлууд, B+-мод, гүйлгээний болон олон хувилбарыг хэрэгжүүлэхэд хуулбарлах дээр бичих арга. Эцэст нь, амттангаар - практик хэсэг. Үүн дээр бид доод түвшний түлхүүр-утга API дээр индексжүүлсэн хэд хэдэн хүснэгт бүхий мэдээллийн сангийн схемийг хэрхэн боловсруулж, хэрэгжүүлэх талаар авч үзэх болно.

Агуулга

  1. Хэрэгжүүлэх сэдэл
  2. LMDB Байршил
  3. LMDB-ийн гурван тулгуур
    3.1. Халим №1. Санах ойн зураглалтай файлууд
    3.2. Халим №2. B+-мод
    3.3. Халим №3. Бичих дээр хуулбарлах
  4. Түлхүүр-утга API дээр өгөгдлийн схемийг зохион бүтээх
    4.1. Үндсэн хийсвэрлэлүүд
    4.2. Хүснэгтийн загварчлал
    4.3. Хүснэгт хоорондын харилцааг загварчлах

1. Хэрэгжүүлэх сэдэл

2015 онд нэг жилийн хугацаанд бид програмынхаа интерфэйс хэр олон удаа хоцорч байгааг хэмжихэд бэрхшээлтэй тулгарсан. Бид ямар нэг шалтгаанаар үүнийг хийсэн. Заримдаа програм нь хэрэглэгчийн үйлдэлд хариу өгөхөө больдог: товчлуурыг дарах боломжгүй, жагсаалт гүйлгэдэггүй гэх мэт гомдол бидэнд илүү олон удаа ирдэг. Хэмжилтийн механикийн тухай гэж хэлэв AvitoTech дээр, тиймээс би энд зөвхөн тоонуудын дарааллыг өгдөг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Хэмжилтийн үр дүн нь бидний хувьд хүйтэн шүршүүр болсон. Хөлдөлтөөс үүдэлтэй асуудал бусадтай харьцуулахад олон байдаг нь тогтоогдсон. Хэрэв энэ баримтыг ойлгохоос өмнө чанарын гол техникийн үзүүлэлт нь гэмтэлгүй байсан бол анхаарлаа төвлөрүүлсний дараа шилжсэн хөлдөхгүй.

Бариулсан хөлдсөн хяналтын самбар мөн зарцуулсны дараа тоон и чанар Тэдний шалтгааныг задлан шинжилж үзэхэд гол дайсан нь тодорхой болсон - програмын үндсэн хэсэгт гүйцэтгэсэн бизнесийн хүнд логик. Энэхүү гутамшигт байдалд үзүүлэх байгалийн хариу үйлдэл нь түүнийг ажлын урсгал руу түлхэх гэсэн шатаж буй хүсэл байв. Энэ асуудлыг системтэйгээр шийдвэрлэхийн тулд бид хөнгөн жинтэй жүжигчид дээр суурилсан олон урсгалтай архитектурыг ашигласан. Би үүнийг iOS ертөнцөд дасан зохицоход нь зориулав хоёр утас хамтын Twitter болон Хабрегийн тухай нийтлэл. Одоогийн өгүүллийн нэг хэсэг болгон би мэдээллийн санг сонгоход нөлөөлсөн шийдвэрийн талуудыг онцлон тэмдэглэхийг хүсч байна.

Системийн зохион байгуулалтын жүжигчний загвар нь multithreading нь түүний хоёр дахь мөн чанар болно гэж үздэг. Үүн доторх загвар объектууд урсгалын хилийг давах дуртай. Тэд үүнийг хааяа, энд тэндгүй, бараг байнга, хаа сайгүй хийдэг

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Өгөгдлийн сан нь танилцуулсан диаграммын тулгын чулууны бүрэлдэхүүн хэсгүүдийн нэг юм. Үүний гол ажил бол макро загварыг хэрэгжүүлэх явдал юм Хуваалцсан мэдээллийн сан. Хэрэв аж ахуйн нэгжийн ертөнцөд энэ нь үйлчилгээнүүдийн хооронд өгөгдлийн синхрончлолыг зохион байгуулахад ашиглагддаг бол жүжигчний архитектурын хувьд урсгал хоорондын өгөгдөл юм. Тиймээс бидэнд олон урсгалтай орчинд ажиллахад хамгийн бага хүндрэл учруулахгүй мэдээллийн сан хэрэгтэй байсан. Ялангуяа энэ нь үүнээс олж авсан объектууд нь хамгийн багадаа урсгалтай байх ёстой бөгөөд хамгийн тохиромжтой нь өөрчлөгддөггүй байх ёстой гэсэн үг юм. Та бүхний мэдэж байгаагаар сүүлийнх нь ямар ч түгжээгүйгээр хэд хэдэн утаснаас нэгэн зэрэг ашиглагдаж болох бөгөөд энэ нь гүйцэтгэлд сайнаар нөлөөлдөг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядууралӨгөгдлийн сангийн сонголтод нөлөөлсөн хоёр дахь чухал хүчин зүйл бол манай үүлэн API юм. Энэ нь git-ийн баталсан синхрончлолын арга барилаас санаа авсан. Түүн шиг бид зорилго тавьсан офлайн-анхны API, энэ нь үүлэн үйлчлүүлэгчдэд илүү тохиромжтой харагдаж байна. Тэд үүлний бүрэн төлөвийг зөвхөн нэг удаа шахаж, дараа нь дийлэнх тохиолдолд синхрончлол нь өөрчлөлт оруулах замаар явагдана гэж таамаглаж байсан. Харамсалтай нь, энэ боломж зөвхөн онолын бүсэд байгаа бөгөөд үйлчлүүлэгчид практик дээр засваруудтай хэрхэн ажиллах талаар сураагүй байна. Үүнд хэд хэдэн объектив шалтгаан байгаа бөгөөд танилцуулгыг хойшлуулахгүйн тулд бид хаалт үлдээх болно. Одоо илүү сонирхолтой зүйл бол API нь "A" гэж хэлэх бөгөөд хэрэглэгч нь "B" гэж хэлэхгүй байх үед юу болох тухай хичээлийн сургамжтай дүгнэлтүүд юм.

Тиймээс, хэрэв та татах командыг гүйцэтгэхдээ локал хормын хувилбарт засвар оруулахын оронд түүний бүрэн төлөвийг серверийн бүрэн төлөвтэй харьцуулдаг git-ийг төсөөлвөл үүлэн дотор синхрончлол хэрхэн явагддаг талаар нэлээд үнэн зөв ойлголттой болно. үйлчлүүлэгчид. Үүнийг хэрэгжүүлэхийн тулд та санах ойд бүх сервер болон локал файлуудын талаархи мета мэдээлэл бүхий хоёр DOM модыг хуваарилах хэрэгтэй гэдгийг таахад хялбар байдаг. Хэрэв хэрэглэгч үүлэн дотор 500 мянган файл хадгалдаг бол синхрончлохын тулд 1 сая зангилаа бүхий хоёр модыг дахин үүсгэж устгах шаардлагатай болж байна. Гэхдээ зангилаа бүр нь дэд объектуудын графикийг агуулсан агрегат юм. Энэ үүднээс профайлын үр дүнг хүлээж байсан. Нэгтгэх алгоритмыг тооцоолоогүй ч гэсэн маш олон тооны жижиг объектуудыг үүсгэх, дараа нь устгах процедур нь нэлээдгүй пенни үнэтэй байдаг нь тогтоогдсон.Үндсэн синхрончлолын үйлдлийг олон тоонд багтаасан нь нөхцөл байдлыг улам хүндрүүлж байна. хэрэглэгчийн скриптүүдийн. Үүний үр дүнд бид мэдээллийн баазыг сонгох хоёр дахь чухал шалгуурыг - объектын динамик хуваарилалтгүйгээр CRUD үйлдлүүдийг хэрэгжүүлэх чадварыг зассан.

Бусад шаардлагууд нь илүү уламжлалт бөгөөд тэдгээрийн бүх жагсаалт дараах байдалтай байна.

  1. Утасны аюулгүй байдал.
  2. Олон боловсруулалт. Зөвхөн урсгалуудын хооронд төдийгүй үндсэн програм болон iOS өргөтгөлүүдийн хооронд төлөвийг синхрончлохын тулд ижил мэдээллийн сангийн жишээг ашиглах хүслээс үүдэлтэй.
  3. Хадгалагдсан объектуудыг хувиршгүй объект болгон төлөөлөх чадвар.​
  4. CRUD үйлдлийн дотор динамик хуваарилалт байхгүй.
  5. Үндсэн шинж чанаруудын гүйлгээний дэмжлэг ХҮЧИЛ: атом, тууштай байдал, тусгаарлалт, найдвартай байдал.
  6. Хамгийн алдартай тохиолдлуудад хурд.

Энэхүү багц шаардлагын дагуу SQLite нь сайн сонголт байсан бөгөөд хэвээр байна. Гэсэн хэдий ч хувилбаруудыг судлах ажлын хүрээнд би нэг ном олж авлаа "LevelDB-г эхлүүлэх". Түүний удирдлаган дор бодит үүлэн хувилбарт янз бүрийн мэдээллийн сантай ажиллах хурдыг харьцуулсан жишиг судалгааг бичсэн. Үр дүн нь бидний хүлээлтээс давсан. Хамгийн түгээмэл тохиолдлуудад - бүх файлуудын эрэмбэлэгдсэн жагсаалтад курсорыг авах, тухайн директорийн бүх файлуудын эрэмбэлэгдсэн жагсаалт - LMDB нь SQLite-ээс 10 дахин хурдан болсон. Сонголт нь тодорхой болсон.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

2. LMDB Байршил

LMDB нь маш жижиг номын сан (зөвхөн 10К мөр) бөгөөд мэдээллийн сангийн хамгийн доод давхарга болох хадгалах санг хэрэгжүүлдэг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Дээрх диаграммаас харахад LMDB-ийг илүү өндөр түвшинд хэрэгжүүлдэг SQLite-тэй харьцуулах нь үндсэн өгөгдөлтэй SQLite-ээс илүү зөв биш болохыг харуулж байна. BerkeleyDB, LevelDB, Sophia, RocksDB гэх мэт ижил төстэй хадгалах хөдөлгүүрүүдийг нэрлэх нь илүү шударга байх болно. LMDB нь SQLite-д хадгалах хөдөлгүүрийн бүрэлдэхүүн хэсэг болж ажилладаг хөгжүүлэлтүүд ч бий. Эхний ийм туршилтыг 2012 онд хийсэн зарцуулсан LMDB Ховард Чу. Результаты Энэ нь маш сонирхолтой болсон тул түүний санаачлагыг НЦҮ-ийн сонирхогчид дэмжиж, үргэлжлэлийг нь олж авсан. LumoSQL. 2020 оны XNUMX-р сард энэхүү төслийн зохиогч нь Ден Ширер байв танилцуулсан LinuxConfAu дээр.

LMDB нь ихэвчлэн хэрэглээний мэдээллийн сангийн хөдөлгүүр болгон ашиглагддаг. Номын сан нь гадаад төрхөөрөө хөгжүүлэгчдэд өртэй Нээлттэй ОНДЛОГ, тэд өөрсдийн төслийн үндэс болсон BerkeleyDB-д маш их сэтгэл дундуур байсан. Даруухан номын сангаас эхэлнэ btree, Ховард Чу бидний цаг үеийн хамгийн алдартай хувилбаруудын нэгийг бүтээж чадсан. Тэрээр маш сайхан илтгэлээ энэ түүхээс гадна LMDB-ийн дотоод бүтцэд зориулав. "Аянгын санах ойд суурилсан мэдээллийн сан". Хадгалах байгууламжийг эзлэх сайн жишээг Леонид Юрьев (на yleo) Positive Technologies-ээс Highload 2015 дээр хийсэн тайландаа "LMDB хөдөлгүүр бол онцгой аварга". Үүнд тэрээр ReOpenLDAP-ийг хэрэгжүүлэх ижил төстэй ажлын хүрээнд LMDB-ийн талаар ярьдаг бөгөөд LevelDB нь харьцуулсан шүүмжлэлд өртөж байсан. Хэрэгжүүлсний үр дүнд Positive Technologies идэвхтэй хөгжиж буй сэрээтэй болсон MDBX маш амттай онцлог, оновчлол болон алдааны засварлалтууд.

LMDB нь ихэвчлэн хадгалдаг шиг ашиглагддаг. Жишээлбэл, Mozilla Firefox хөтөч сонгосон Энэ нь хэд хэдэн хэрэгцээнд зориулагдсан бөгөөд 9-р хувилбараас эхлэн Xcode илүүд үздэг индекс хадгалахад зориулагдсан SQLite.

Хөдөлгүүр нь мөн гар утасны хөгжлийн ертөнцөд өөрийн гэсэн тэмдэг тавьсан. Түүний хэрэглээний ул мөр байж болно хайх Telegram-д зориулсан iOS клиент дээр. LinkedIn улам бүр ахиж, LMDB-г өөрийн эх орондоо үйлдвэрлэсэн өгөгдлийн кэш хийх Rocket Data-ын үндсэн хадгалах газар болгон сонгосон. хэлсэн 2016 онд бичсэн нийтлэлдээ.

LMDB нь Oracle-ийн мэдэлд орсныхоо дараа BerkeleyDB-ийн үлдээсэн үүрэнд наранд байрлахын төлөө амжилттай тэмцэж байна. Номын сан нь үе тэнгийнхэнтэйгээ харьцуулахад хурдтай, найдвартайгаараа алдартай. Таны мэдэж байгаагаар, үнэ төлбөргүй үдийн хоол байдаггүй бөгөөд LMDB болон SQLite хоёрын хооронд сонголт хийхдээ тулгарах ёстой гэдгийг би онцлон хэлмээр байна. Дээрх диаграм нь хурдыг хэрхэн нэмэгдүүлэхийг тодорхой харуулж байна. Нэгдүгээрт, бид дискний санах ойн дээр нэмэлт хийсвэр давхаргын төлбөр төлөхгүй. Тэдгээргүйгээр сайн архитектурыг хийж чадахгүй нь ойлгомжтой бөгөөд тэдгээр нь програмын кодонд зайлшгүй гарч ирэх боловч илүү нарийн байх болно. Эдгээр нь тусгай программд шаардлагагүй функцуудыг агуулаагүй болно, жишээлбэл, SQL хэл дээрх асуулгад дэмжлэг үзүүлэх. Хоёрдугаарт, дискний санах ойд зориулсан хүсэлтийн дагуу програмын үйл ажиллагааны зураглалыг оновчтой хэрэгжүүлэх боломжтой болно. Хэрэв SQLite миний ажилд нь дундаж хэрэглээний статистикийн дундаж хэрэгцээнд тулгуурладаг бол та програм хөгжүүлэгчийн хувьд ажлын ачааллын үндсэн хувилбаруудыг сайн мэддэг. Илүү бүтээмжтэй шийдэл гаргахын тулд та анхны шийдлийг боловсруулж, дараагийн дэмжлэг үзүүлэхийн тулд нэмэгдсэн үнийн шошго төлөх шаардлагатай болно.

3. LMDB-ийн гурван тулгуур

LMDB-г шувууны нүдээр харсны дараа илүү гүнзгийрүүлэх цаг болжээ. Дараагийн гурван хэсгийг хадгалах байгууламжид тулгуурласан гол тулгууруудын дүн шинжилгээнд зориулах болно.

  1. Санах ойн зураглалтай файлууд нь дисктэй ажиллах, дотоод өгөгдлийн бүтцийг синхрончлох механизм юм.
  2. B+-tree нь хадгалагдсан өгөгдлийн бүтцийн зохион байгуулалт юм.
  3. ACID гүйлгээний шинж чанар, олон хувилбарыг хангах арга болгон бичихэд хуулбарлах.

3.1. Халим №1. Санах ойн зураглалтай файлууд

Санах ойн зураглалтай файлууд нь архивын нэр дээр ч гарч ирдэг маш чухал архитектурын элемент юм. Хадгалсан мэдээлэлд хандах хандалтыг кэш хийх, синхрончлох асуудал бүхэлдээ үйлдлийн системд үлддэг. LMDB нь дотроо ямар ч кэш агуулаагүй. Газрын зураг дээрх файлуудаас өгөгдлийг шууд унших нь хөдөлгүүрийг хэрэгжүүлэхэд маш олон булангуудыг багасгах боломжийг олгодог тул энэ нь зохиогчийн ухамсартай шийдвэр юм. Доорх нь тэдний заримын бүрэн жагсаалтаас хол байна.

  1. Хэд хэдэн процессоор ажиллахдаа хадгалалтын өгөгдлийн тууштай байдлыг хадгалах нь үйлдлийн системийн үүрэг юм. Дараагийн хэсэгт энэ механикийг дэлгэрэнгүй, зурагтай авч үзэх болно.
  2. Кэш байхгүй байгаа нь LMDB-ийг динамик хуваарилалттай холбоотой нэмэлт зардлаас бүрэн хасдаг. Практикт өгөгдөл унших нь виртуал санах ойд зөв хаяг руу заагч тохируулах гэсэн үг бөгөөд үүнээс өөр зүйл байхгүй. Энэ нь шинжлэх ухааны уран зөгнөлт мэт сонсогдож байгаа ч хадгалах сангийн эх кодод calloc руу хийсэн бүх дуудлага нь хадгалах тохиргооны функцэд төвлөрдөг.
  3. Кэш байхгүй байгаа нь тэдгээрийн хандалтыг синхрончлохтой холбоотой түгжээ байхгүй гэсэн үг юм. Нэгэн зэрэг дурын тооны уншигч байж болох уншигчид өгөгдөл рүү явах замдаа ганц ч мутекстэй тааралддаггүй. Үүнээс үүдэн унших хурд нь CPU-ийн тоон дээр тулгуурлан хамгийн тохиромжтой шугаман масштабтай байдаг. LMDB-д зөвхөн өөрчлөх үйлдлүүд синхрончлогддог. Нэг удаад зөвхөн нэг зохиолч байж болно.
  4. Хамгийн бага кэш ба синхрончлолын логик нь олон урсгалтай орчинд ажиллахтай холбоотой маш нарийн төвөгтэй төрлийн алдааг арилгадаг. Usenix OSDI 2014 бага хуралд хоёр сонирхолтой мэдээллийн сангийн судалгаа хийгдсэн: "Бүх файлын системүүд тэгш байдлаар бүтээгддэггүй: Гэмтэлтэй нийцтэй програмуудыг бүтээх нарийн төвөгтэй байдлын тухай" и "Хөгжил ба ашгийн төлөө мэдээллийн санг эрүүдэн шүүх". Тэдгээрээс та LMDB-ийн урьд өмнө хэзээ ч байгаагүй найдвартай байдал болон SQLite-ээс давуу ACID гүйлгээний шинж чанаруудын бараг өөгүй хэрэгжилтийн талаар мэдээлэл авах боломжтой.
  5. LMDB-ийн минимализм нь түүний кодын машины дүрслэлийг процессорын L1 кэшэд бүрэн байршуулах боломжийг олгодог.

Харамсалтай нь санах ойн зураглал бүхий iOS үйлдлийн системд бүх зүйл бидний хүссэн шиг үүлгүй байдаггүй. Тэдэнтэй холбоотой дутагдлуудын талаар илүү ухамсартайгаар ярихын тулд үйлдлийн системд энэ механизмыг хэрэгжүүлэх ерөнхий зарчмуудыг санах хэрэгтэй.

Санах ойн карттай файлуудын талаархи ерөнхий мэдээлэл

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядууралАжиллаж буй програм болгонд үйлдлийн систем нь процесс гэж нэрлэгддэг нэгжийг холбодог. Процесс бүрд ажиллахад шаардлагатай бүх зүйлээ байршуулах хаягийн хүрээг хуваарилдаг. Хамгийн доод хаягуудад код, хатуу кодлогдсон өгөгдөл, нөөц бүхий хэсгүүд байдаг. Дараа нь овоо нэрээр бидний сайн мэддэг динамик хаягийн орон зайн өсөн нэмэгдэж буй блок ирдэг. Энэ нь програмыг ажиллуулах явцад гарч ирэх байгууллагуудын хаягийг агуулдаг. Дээд талд нь програмын стек ашигладаг санах ойн хэсэг байна. Энэ нь өсөх эсвэл агших, өөрөөр хэлбэл хэмжээ нь динамик шинж чанартай байдаг. Стек болон овоо бие биенээ түлхэж, саад учруулахаас сэргийлэхийн тулд тэдгээр нь хаягийн зайны өөр өөр төгсгөлд байрладаг.​ Дээд ба доод хэсэгт хоёр динамик хэсгийн хооронд нүх байна. Үйлдлийн систем нь энэ дунд хэсэгт байгаа хаягуудыг ашиглан төрөл бүрийн байгууллагуудыг процесстой холбодог. Ялангуяа энэ нь тодорхой тасралтгүй хаягийн багцыг дискэн дээрх файлтай холбож чаддаг. Ийм файлыг санах ойн зураглал гэж нэрлэдэг

Процесст хуваарилагдсан хаягийн зай асар их байна. Онолын хувьд хаягийн тоо нь зөвхөн системийн битийн багтаамжаар тодорхойлогддог заагчийн хэмжээгээр хязгаарлагддаг. Хэрэв физик санах ойг 1-ээс 1-ээр буулгасан бол эхний процесс нь бүхэл бүтэн RAM-г шингээж авах бөгөөд олон үйлдэл хийх тухай ярихгүй байх болно.

Гэсэн хэдий ч орчин үеийн үйлдлийн системүүд хүссэн олон процессыг нэгэн зэрэг гүйцэтгэх боломжтой гэдгийг бидний туршлагаас бид мэднэ. Энэ нь зөвхөн цаасан дээрх процессуудад маш их санах ойг хуваарилдагтай холбоотой боловч бодит байдал дээр тэд үндсэн физик санах ойд зөвхөн энд, одоо эрэлт хэрэгцээтэй байгаа хэсгийг ачаалдагтай холбоотой юм. Тиймээс процесстой холбоотой санах ойг виртуал гэж нэрлэдэг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Үйлдлийн систем нь виртуал болон физик санах ойг тодорхой хэмжээтэй хуудас болгон зохион байгуулдаг. Виртуал санах ойн тодорхой хуудас эрэлттэй болмогц үйлдлийн систем нь түүнийг физик санах ойд ачаалж, тусгай хүснэгтэд тохируулдаг. Хэрэв чөлөөт слот байхгүй бол өмнө нь ачаалагдсан хуудсуудын аль нэгийг диск рүү хуулж, эрэлт хэрэгцээтэй нь байраа эзэлнэ. Удахгүй эргэн харах энэ процедурыг солилцоо гэж нэрлэдэг. Доорх зураг нь тайлбарласан үйл явцыг харуулж байна. Үүн дээр 0 хаягтай А хуудсыг ачаалж, үндсэн санах ойн 4-р хаягтай хуудсан дээр байрлуулсан. Энэ баримтыг 0-р нүдний захидал харилцааны хүснэгтэд тусгасан болно.​

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Энэ түүх нь санах ойд буулгасан файлуудтай яг адилхан юм. Логикийн хувьд тэдгээр нь виртуал хаягийн орон зайд тасралтгүй, бүхэлдээ байрладаг гэж үздэг. Гэсэн хэдий ч тэдгээр нь бие махбодийн санах ойг хуудас бүрээр, зөвхөн хүсэлтээр оруулдаг. Ийм хуудасны өөрчлөлтийг диск дээрх файлтай синхрончилдог. Ийм байдлаар та санах ойн байттай ажиллах замаар файлын I/O-г гүйцэтгэх боломжтой - бүх өөрчлөлтийг үйлдлийн системийн цөм автоматаар эх файл руу шилжүүлэх болно.
Gg
Доорх зураг нь LMDB нь янз бүрийн процессуудын мэдээллийн сантай ажиллахдаа төлөвөө хэрхэн синхрончлохыг харуулж байна. Өөр өөр процессуудын виртуал санах ойг нэг файлд буулгаснаар бид үйлдлийн системд LMDB-ийн хардаг хаягийн зайны тодорхой блокуудыг бие биетэйгээ шилжин синхрончлохыг үүрэг болгож байна.
Gg

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Нэг чухал нюанс бол LMDB нь анхдагчаар бичих системийн дуудлагын механизмаар өгөгдлийн файлыг өөрчилдөг бөгөөд файлыг зөвхөн унших горимд харуулдаг. Энэ арга нь хоёр чухал үр дагавартай.

Эхний үр дагавар нь бүх үйлдлийн системд нийтлэг байдаг. Үүний мөн чанар нь буруу кодоор мэдээллийн санд санамсаргүй гэмтэл учруулахаас хамгаалах хамгаалалт юм. Таны мэдэж байгаагаар процессын гүйцэтгэх заавар нь хаягийн зайн хаанаас ч өгөгдөлд хандахад чөлөөтэй байдаг. Үүний зэрэгцээ, бидний сая санаж байгаагаар файлыг унших-бичих горимд харуулах нь аливаа зааварчилгааг өөрчлөх боломжтой гэсэн үг юм. Хэрэв тэр үүнийг андуурч, жишээлбэл, байхгүй индекс дээр массивын элементийг дарж бичих гэж оролдвол энэ хаяг руу буулгасан файлыг санамсаргүйгээр өөрчлөх боломжтой бөгөөд энэ нь мэдээллийн сангийн эвдрэлд хүргэх болно. Хэрэв файлыг зөвхөн унших горимд харуулсан бол холбогдох хаягийн зайг өөрчлөх оролдлого нь дохиогоор програмыг яаралтай зогсооход хүргэнэ. SIGSEGV, мөн файл бүрэн бүтэн хэвээр байх болно.

Хоёрдахь үр дагавар нь iOS-д аль хэдийн тодорхой болсон. Зохиогч ч, бусад эх сурвалж ч үүнийг тодорхой дурдаагүй боловч үүнгүйгээр LMDB нь энэ гар утасны үйлдлийн систем дээр ажиллахад тохиромжгүй байх болно. Дараагийн хэсэг нь түүнийг авч үзэхэд зориулагдсан болно.

iOS дээрх санах ойн зураглалтай файлуудын онцлог

2018 онд WWDC дээр гайхалтай тайлан гарсан "iOS санах ойн гүнд шумбах". Энэ нь iOS-д физик санах ойд байрлах бүх хуудас нь бохир, шахсан, цэвэр гэсэн 3 төрлийн нэг юм.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Цэвэр санах ой нь физик санах ойноос өвдөлтгүй буулгаж болох хуудасны цуглуулга юм. Тэдгээрт агуулагдах өгөгдлийг шаардлагатай бол эх сурвалжаас нь дахин ачаалж болно. Зөвхөн уншихад зориулагдсан санах ойн зураглалтай файлууд энэ ангилалд багтдаг. iOS нь дискэн дээрх файлтай синхрончлох баталгаатай тул файлд байрлуулсан хуудсуудыг санах ойноос хэзээ ч буулгахаас айдаггүй.
Gg
Өөрчлөгдсөн бүх хуудас нь хаана ч байсан бохир санах ойд дуусдаг. Ялангуяа тэдгээртэй холбоотой виртуал санах ойд бичиж өөрчилсөн санах ойн зураглалтай файлуудыг ийм байдлаар ангилах болно. LMDB-г тугтай нээж байна MDB_WRITEMAP, үүнд өөрчлөлт оруулсны дараа та үүнийг биечлэн шалгах боломжтой.​

Аппликешн хэт их физик санах ойг эзэлж эхэлбэл iOS нь түүнийг бохир хуудасны шахалтад оруулдаг. Бохир, шахсан хуудсуудын эзэлдэг нийт санах ой нь програмын санах ойн ул мөр гэж нэрлэгддэг. Энэ нь тодорхой босго утгад хүрмэгц OOM алуурчин системийн демон процессын дараа ирж, түүнийг хүчээр зогсооно. Энэ бол ширээний үйлдлийн системтэй харьцуулахад iOS-ийн онцлог юм. Үүний эсрэгээр, хуудсуудыг физик санах ойноос диск рүү солих замаар санах ойн ул мөрийг багасгах нь iOS дээр байдаггүй. Шалтгааныг зөвхөн таах боломжтой. Хуудсуудыг диск рүү эрчимтэй шилжүүлэх процедур нь хөдөлгөөнт төхөөрөмжүүдэд хэт их эрчим хүч зарцуулдаг, эсвэл iOS нь SSD хөтчүүд дээрх эсүүдийг дахин бичих нөөцийг хэмнэдэг, эсвэл дизайнерууд системийн ерөнхий гүйцэтгэлд сэтгэл хангалуун бус байсан байж магадгүй юм. байнга сольдог. Гэсэн хэдий ч баримт нь баримт хэвээр байна.

Өмнө дурьдсан сайн мэдээ бол LMDB анхдагчаар файлуудыг шинэчлэхийн тулд mmap механизм ашигладаггүй явдал юм. Энэ нь харуулсан өгөгдлийг iOS-ээр цэвэр санах ой гэж ангилж, санах ойн ул мөрийг нэмэгдүүлэхгүй гэсэн үг юм. Та үүнийг VM Tracker нэртэй Xcode хэрэглүүрийг ашиглан шалгаж болно. Доорх дэлгэцийн зураг нь Cloud програмын үйлдлийн үеийн iOS виртуал санах ойн төлөвийг харуулж байна. Эхэндээ 2 LMDB инстанцыг үүн дээр эхлүүлсэн. Эхнийх нь файлаа 1ГиБ виртуал санах ойд харуулахыг зөвшөөрсөн, хоёр дахь нь 512МБ. Хэдийгээр хоёр хадгалалт нь тодорхой хэмжээний суурин санах ойг эзэлдэг ч тэдгээрийн аль нь ч бохир хэмжээтэй байдаггүй.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Тэгээд одоо муу мэдээ дуулгах цаг болжээ. 64 битийн ширээний үйлдлийн систем дэх своп механизмын ачаар процесс бүр өөрийн боломжит солилцооны хатуу дискний сул зайны хэмжээгээр виртуал хаягийн зайг эзлэх боломжтой. IOS дахь свопыг шахалтаар солих нь онолын дээд хэмжээг эрс бууруулдаг. Одоо бүх амьд процессууд үндсэн (уншсан RAM) санах ойд багтах ёстой бөгөөд тохирохгүй байгаа бүх үйл явцыг зогсоох ёстой. Үүнийг дээр дурьдсанчлан тайлбарласан болно тайлан, ба in албан ёсны баримт бичиг. Үүний үр дүнд iOS нь mmap-аар хуваарилах боломжтой санах ойн хэмжээг эрс хязгаарладаг. Энд энд Та энэ системийн дуудлагыг ашиглан өөр өөр төхөөрөмжид хуваарилж болох санах ойн хэмжээнүүдийн эмпирик хязгаарыг харж болно. Хамгийн орчин үеийн ухаалаг гар утасны загварууд дээр iOS нь 2 гигабайтаар өгөөмөр болсон бөгөөд iPad-ийн дээд хувилбарууд дээр - 4. Практик дээр мэдээжийн хэрэг та хамгийн бага дэмжигдсэн төхөөрөмжийн загварт анхаарлаа хандуулах хэрэгтэй бөгөөд энд бүх зүйл маш гунигтай байдаг. Хамгийн муу нь, VM Tracker дээрх програмын санах ойн төлөвийг харвал LMDB нь санах ойн зураглалтай гэж мэдэгддэг цорын ганц хувилбараас хол байгааг олж мэдэх болно. Сайн хэсгүүдийг системийн хуваарилагч, нөөц файл, зургийн хүрээ болон бусад жижиг махчин амьтад иднэ.

Cloud дээр хийсэн туршилтын үр дүнд үндэслэн бид LMDB-ээс хуваарилсан санах ойн хувьд дараахь буулт хийх утгуудад хүрсэн: 384 битийн төхөөрөмжид 32 мегабайт, 768 битийн төхөөрөмжид 64 мегабайт. Энэ хэмжээ дууссаны дараа аливаа өөрчлөх үйлдлүүд кодоор дуусч эхэлдэг MDB_MAP_FULL. Бид мониторинг хийхдээ ийм алдааг ажиглаж байгаа боловч энэ үе шатанд тэдгээрийг үл тоомсорлож болохуйц бага байна.

Хадгалалтаас хэт их санах ойн зарцуулалтын тодорхой бус шалтгаан нь урт хугацааны гүйлгээ байж болно. Эдгээр хоёр үзэгдэл хэрхэн холбогдож байгааг ойлгохын тулд LMDB-ийн үлдсэн хоёр баганыг авч үзэх нь бидэнд туслах болно.

3.2. Халим №2. B+-мод

Түлхүүр-утга хадгалах сангийн дээд талд байгаа хүснэгтүүдийг дуурайхын тулд API-д дараах үйлдлүүд байх ёстой:

  1. Шинэ элемент оруулж байна.
  2. Өгөгдсөн түлхүүрээр элемент хайх.
  3. Элементийг устгаж байна.
  4. Түлхүүрүүдийг эрэмбэлсэн дарааллаар нь интервалаар давт.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядууралБүх дөрвөн үйлдлийг хялбархан хэрэгжүүлэх боломжтой өгөгдлийн хамгийн энгийн бүтэц бол хоёртын хайлтын мод юм. Түүний зангилаа бүр нь хүүхдийн түлхүүрүүдийн бүх дэд хэсгийг хоёр дэд мод болгон хуваах түлхүүрийг төлөөлдөг. Зүүн талд нь эцэг эхээсээ бага, баруун талд том хэмжээтэй байгаа. Захиалгат түлхүүрүүдийг олж авах нь сонгодог модоор дамжин өнгөрөх аргуудын аль нэгээр дамждаг

Хоёртын мод нь дискэнд суурилсан өгөгдлийн бүтэц болгон үр дүнтэй ажиллахад саад болох хоёр үндсэн дутагдалтай байдаг. Нэгдүгээрт, тэдгээрийн тэнцвэрийн түвшинг урьдчилан таамаглах аргагүй юм. Янз бүрийн мөчрүүдийн өндөр нь маш их ялгаатай байж болох модыг олж авах ихээхэн эрсдэлтэй бөгөөд энэ нь хүлээгдэж буйтай харьцуулахад хайлтын алгоритмын нарийн төвөгтэй байдлыг эрс дордуулдаг. Хоёрдугаарт, зангилаа хоорондын олон тооны хөндлөн холбоосууд нь санах ой дахь хоёртын модыг нутагшуулах чадваргүй болгодог.Хаалтын зангилаа (тэдгээрийн хоорондох холболтын хувьд) виртуал санах ойн огт өөр хуудсан дээр байрлаж болно. Үүний үр дүнд, модны хэд хэдэн зэргэлдээх зангилааг энгийнээр гатлахад ч гэсэн харьцуулж болохуйц тооны хуудсыг үзэх шаардлагатай болдог. Процессорын кэш дэх хуудсуудыг байнга эргүүлэх нь хямд таашаал биш тул бид санах ойн өгөгдлийн бүтэц болгон хоёртын модыг үр дүнтэй ашиглах талаар ярихад ч энэ нь асуудал юм. Дискнээс зангилаатай холбоотой хуудсыг байнга татаж авах үед нөхцөл байдал бүрмөсөн болно өрөвдмөөрБайна.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядууралB-мод нь хоёртын модны хувьсал болох нь өмнөх догол мөрөнд тодорхойлсон асуудлуудыг шийддэг. Нэгдүгээрт, тэд өөрсдийгөө тэнцвэржүүлдэг. Хоёрдугаарт, тэдгээрийн зангилаа бүр хүүхдийн түлхүүрүүдийн багцыг 2 биш, харин M эрэмбэлэгдсэн дэд олонлогт хуваадаг бөгөөд M тоо нь нэлээд том, хэдэн зуу, бүр мянгаараа байж болно.

Ингэснээр:

  1. Зангилаа бүр нь олон тооны аль хэдийн захиалсан түлхүүрүүдийг агуулдаг бөгөөд моднууд нь маш богино байдаг.
  2. Ойролцоох түлхүүрүүд нь ижил эсвэл зэргэлдээ зангилаанууд дээр бие биенийхээ хажууд байрладаг тул мод нь санах ойд орон нутгийн шинж чанарыг олж авдаг.
  3. Эрлийн ажиллагааны явцад мод уруудах үед дамжин өнгөрөх зангилааны тоо багасдаг.
  4. Хүрээний асуулгад уншсан зорилтот зангилааны тоо багассан, учир нь тус бүр нь олон тооны захиалгат түлхүүрүүдийг агуулж байдаг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

LMDB нь өгөгдөл хадгалахын тулд B+ мод гэж нэрлэгддэг B модны хувилбарыг ашигладаг. Дээрх диаграмм нь түүнд байгаа гурван төрлийн зангилааг харуулав.

  1. Дээд талд нь үндэс байна. Энэ нь агуулах доторх мэдээллийн сан гэсэн ойлголтоос өөр зүйл биш юм. Нэг LMDB жишээн дотор та дүрслэгдсэн виртуал хаягийн зайг хуваалцдаг хэд хэдэн мэдээллийн санг үүсгэж болно. Тэд тус бүр өөрийн үндэсээс эхэлдэг.
  2. Хамгийн доод түвшинд навчнууд байдаг. Зөвхөн тэдгээр нь мэдээллийн санд хадгалагдсан түлхүүр-утга хосуудыг агуулдаг. Дашрамд хэлэхэд, энэ нь B+-модны онцлог юм. Хэрэв ердийн В мод нь бүх түвшний зангилаанд үнэ цэнийн хэсгүүдийг хадгалдаг бол B + өөрчлөлт нь зөвхөн хамгийн бага байна. Энэ баримтыг зассаны дараа бид LMDB-д ашигласан модны дэд төрлийг зүгээр л B мод гэж нэрлэх болно.
  3. Үндэс ба навчны хооронд навигацийн (салбар) зангилаа бүхий 0 ба түүнээс дээш техникийн түвшин байдаг. Тэдний даалгавар бол эрэмбэлсэн багц товчлууруудыг навчны хооронд хуваах явдал юм.

Физикийн хувьд зангилаа нь урьдчилан тодорхойлсон урттай санах ойн блокууд юм. Тэдгээрийн хэмжээ нь үйлдлийн систем дэх санах ойн хуудсуудын хэмжээнээс хэд дахин их бөгөөд бидний дээр дурдсан. Зангилааны бүтцийг доор үзүүлэв. Толгой хэсэгт мета мэдээлэл агуулагддаг бөгөөд тэдгээрийн хамгийн тод нь жишээлбэл шалгах нийлбэр юм. Дараа нь өгөгдөл бүхий нүднүүдийн байрласан офсетуудын тухай мэдээлэл ирнэ. Хэрэв бид навигацийн зангилааны тухай ярьж байгаа бол өгөгдөл нь түлхүүр эсвэл навчны хувьд бүхэл утгын хос байж болно.​ Та ажлын хуудасны бүтцийн талаар дэлгэрэнгүй унших боломжтой. "Өндөр гүйцэтгэлтэй гол үнэ цэнэтэй дэлгүүрүүдийн үнэлгээ".

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Хуудасны зангилааны дотоод агуулгыг авч үзсэний дараа бид LMDB B модыг дараах хэлбэрээр хялбаршуулсан хэлбэрээр харуулах болно.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Зангилаатай хуудсууд нь дискэн дээр дараалан байрлана. Илүү өндөр дугаарласан хуудаснууд нь файлын төгсгөлд байрладаг. Мета хуудас гэж нэрлэгддэг хуудас нь бүх модны үндсийг олж болох нөхцлийн талаархи мэдээллийг агуулдаг. Файлыг нээхдээ LMDB нь файлыг хуудаснаас нь эхнээс нь хуудсаараа сканнердаж, хүчинтэй мета хуудас хайж, түүгээр дамжуулан одоо байгаа мэдээллийн санг олдог.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Өгөгдлийн зохион байгуулалтын логик болон физик бүтцийн талаархи ойлголттой бол бид LMDB-ийн гурав дахь тулгуурыг авч үзэх болно. Үүний тусламжтайгаар бүх хадгалалтын өөрчлөлтүүд нь гүйлгээний дагуу, бие биенээсээ тусгаарлагдсан байдлаар хийгддэг бөгөөд мэдээллийн санд бүхэлдээ олон хувилбарын шинж чанарыг өгдөг.

3.3. Халим №3. Бичих дээр хуулбарлах

В модны зарим үйлдлүүд нь түүний зангилаанд хэд хэдэн өөрчлөлт хийх шаардлагатай байдаг. Үүний нэг жишээ бол аль хэдийн дээд хүчин чадалдаа хүрсэн зангилаанд шинэ түлхүүр нэмэх явдал юм. Энэ тохиолдолд, нэгдүгээрт, зангилааг хоёр хуваах, хоёрдугаарт, түүний эцэг эх дэх шинэ нахиалах хүүхдийн зангилаа руу холбоос нэмэх шаардлагатай. Энэ процедур нь маш аюултай байж магадгүй юм. Хэрэв ямар нэг шалтгааны улмаас (гацах, цахилгаан тасрах гэх мэт) цувралын өөрчлөлтүүдийн зөвхөн нэг хэсэг нь тохиолдвол мод нь тогтворгүй байдалд үлдэнэ.

Өгөгдлийн санг алдаа гаргахад тэсвэртэй болгох нэг уламжлалт шийдэл бол B модны хажууд дискэн дээрх нэмэлт өгөгдлийн бүтцийг нэмэх явдал юм - гүйлгээний бүртгэлийг урьдчилан бичих бүртгэл (WAL) гэж нэрлэдэг. Энэ нь B модыг өөрчлөхөөс өмнө төлөвлөсөн үйлдлийг төгсгөлд нь бичсэн файл юм. Тиймээс, өөрийгөө оношлох явцад мэдээллийн эвдрэл илэрсэн бол мэдээллийн сан нь бүртгэлээс зөвлөгөө авч, дарааллаар нь оруулдаг.

LMDB нь алдааг тэсвэрлэх механизмын хувьд хуулбар дээр бичих гэж нэрлэгддэг өөр аргыг сонгосон. Үүний мөн чанар нь одоо байгаа хуудсан дээрх өгөгдлийг шинэчлэхийн оронд эхлээд бүхэлд нь хуулж, хуулбар дахь бүх өөрчлөлтийг хийдэг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Дараа нь, шинэчлэгдсэн өгөгдлийг ашиглах боломжтой байхын тулд түүний эх зангилаанд одоогийн болсон зангилааны холбоосыг өөрчлөх шаардлагатай. Үүнийг бас өөрчлөх шаардлагатай тул урьдчилж хуулж авдаг. Процесс нь үндэс хүртэл рекурсив байдлаар үргэлжилнэ. Хамгийн сүүлд өөрчлөх зүйл бол мета хуудасны өгөгдөл юм.​

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Хэрэв шинэчлэлтийн явцад гэнэт процесс гацвал шинэ мета хуудас үүсгэхгүй, эсвэл дискэнд бүрэн бичигдээгүй, шалгах нийлбэр буруу байх болно. Эдгээр хоёр тохиолдлын аль нэгэнд шинэ хуудсууд нэвтрэх боломжгүй, харин хуучин хуудаснууд нөлөөлөхгүй. Энэ нь өгөгдлийн тууштай байдлыг хадгалахын тулд LMDB-д урьдчилан бүртгэл бичих шаардлагагүй болно. Үнэн хэрэгтээ дээр дурдсан диск дээрх өгөгдөл хадгалах бүтэц нь нэгэн зэрэг үүргээ гүйцэтгэдэг. Тодорхой гүйлгээний бүртгэл байхгүй байгаа нь LMDB-ийн нэг онцлог шинж чанар нь өгөгдөл унших өндөр хурдыг өгдөг.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Зөвхөн хавсаргах боломжтой B мод гэж нэрлэгддэг загвар нь гүйлгээний тусгаарлалт, олон хувилбарыг бий болгодог. LMDB дээр нээлттэй гүйлгээ бүр нь одоогийн холбогдох модны үндэстэй холбоотой байдаг. Гүйлгээ дуусах хүртэл үүнтэй холбоотой модны хуудсууд хэзээ ч өөрчлөгдөхгүй, өгөгдлийн шинэ хувилбаруудад дахин ашиглагдахгүй. Тиймээс та тухайн үед хамааралтай байсан өгөгдлийн багцтай хүссэнээрээ ажиллах боломжтой. Энэ үед хадгалах сан идэвхтэй шинэчлэгдэж байсан ч гүйлгээ нээгдсэн. Энэ бол олон хувилбарын мөн чанар бөгөөд LMDB-ийг бидний хайртуудын хувьд хамгийн тохиромжтой мэдээллийн эх сурвалж болгодог UICollectionView. Гүйлгээг нээсний дараа юу ч үлдээхгүй байх вий гэсэн болгоомжлолын үүднээс одоогийн өгөгдлийг санах ойн бүтцэд яаралтай шахах замаар програмын санах ойн хэмжээг нэмэгдүүлэх шаардлагагүй болно. Энэ онцлог нь LMDB-ийг ижил SQLite-ээс ялгаж өгдөг бөгөөд энэ нь ийм бүрэн тусгаарлалтаараа сайрхаж чадахгүй. Сүүлд нь хоёр гүйлгээ нээж, тэдгээрийн аль нэгнийх нь тодорхой бүртгэлийг устгасан тохиолдолд үлдсэн хоёр дахь гүйлгээнд ижил бүртгэлийг авах боломжгүй болно.

Зоосны эсрэг тал нь виртуал санах ойн хэрэглээг ихээхэн хэмжээгээр нэмэгдүүлдэг. Слайд нь өгөгдлийн сангийн янз бүрийн хувилбаруудыг 3 задгай унших гүйлгээтэй зэрэг өөрчилсөн тохиолдолд мэдээллийн сангийн бүтэц ямар харагдахыг харуулж байна. LMDB нь одоогийн гүйлгээнүүдтэй холбоотой үндэснээс холбогдох зангилаануудыг дахин ашиглах боломжгүй тул дэлгүүрт санах ойд өөр дөрөв дэх үндэсийг хуваарилж, доор нь өөрчилсөн хуудсуудыг дахин хувилахаас өөр сонголт байхгүй.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Энд санах ойн зураглалтай файлуудын хэсгийг эргэн санах нь ашигтай байх болно. Виртуал санах ойн нэмэлт хэрэглээ нь програмын санах ойд хувь нэмэр оруулдаггүй тул биднийг нэг их санаа зовохгүй байх шиг байна. Гэсэн хэдий ч iOS нь үүнийг хуваарилахдаа маш харамч байдаг бөгөөд бид сервер эсвэл ширээний компьютер дээр 1 терабайтын LMDB мужийг өгөх боломжгүй бөгөөд энэ функцийн талаар огт бодохгүй байна. Боломжтой бол гүйлгээний хугацааг аль болох богино болгохыг хичээх хэрэгтэй.

4. Түлхүүр-утга API дээр өгөгдлийн схемийг зохион бүтээх

API шинжилгээгээ LMDB-ийн өгсөн үндсэн хийсвэрлэлүүд болох орчин ба мэдээллийн сан, түлхүүр ба утгууд, гүйлгээ ба курсоруудыг авч үзье.

Кодын жагсаалтын тухай тэмдэглэл

Нийтийн LMDB API-н бүх функцууд ажлын үр дүнг алдааны код хэлбэрээр буцаадаг боловч дараагийн бүх жагсаалтад товчхон байх үүднээс түүний баталгаажуулалтыг орхигдуулсан болно. сэрээ C++ боодол lmdbxx, алдаа нь C++ үл хамаарах зүйл болгон материаллагдмал байдаг.

LMDB-г iOS эсвэл macOS-д зориулсан төсөлд холбох хамгийн хурдан арга бол би CocoaPod-оо санал болгож байна. POSLMDB.

4.1. Үндсэн хийсвэрлэлүүд

Байгаль орчин

бүтэц MDB_env LMDB-ийн дотоод төлөвийн агуулах юм. Угтвар функцийн гэр бүл mdb_env түүний зарим шинж чанарыг тохируулах боломжийг танд олгоно. Хамгийн энгийн тохиолдолд хөдөлгүүрийг эхлүүлэх нь иймэрхүү харагдаж байна.

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

Mail.ru Cloud програм дээр бид зөвхөн хоёр параметрийн үндсэн утгыг өөрчилсөн.

Эхнийх нь хадгалах файлыг дүрсэлсэн виртуал хаягийн зайны хэмжээ юм. Харамсалтай нь нэг төхөөрөмж дээр ч гэсэн тодорхой утга нь гүйлтээс гүйх хооронд ихээхэн ялгаатай байж болно. iOS-ийн энэ онцлогийг харгалзан үзэхийн тулд хамгийн их хадгалах хэмжээг динамикаар сонгосон. Тодорхой утгаас эхлэн функцийг гүйцэтгэх хүртэл дараалан хоёр дахин бууруулна mdb_env_open -аас өөр үр дүн гарахгүй ENOMEM. Онолын хувьд эсрэгээр нь бас байдаг - эхлээд хөдөлгүүрт хамгийн бага санах ойг хуваарилж, дараа нь алдаа гарах үед, MDB_MAP_FULL, нэмэгдүүлэх. Гэсэн хэдий ч энэ нь илүү өргөстэй байдаг. Шалтгаан нь функцийг ашиглан санах ойг (remap) дахин хуваарилах журам юм mdb_env_set_map_size хөдөлгүүрээс өмнө нь хүлээн авсан бүх байгууллагуудыг (курсор, гүйлгээ, түлхүүр, утгыг) хүчингүй болгоно. Үйл явдлын энэ эргэлтийг кодонд харгалзан үзэх нь түүнийг ихээхэн хүндрэлд хүргэх болно. Хэрэв виртуал санах ой таны хувьд маш чухал бол энэ нь хол давсан салаагаа сайтар судлах шалтгаан байж магадгүй юм. MDBX, зарласан функцуудын дунд "автоматаар мэдээллийн сангийн хэмжээг тохируулах" байдаг.

Анхдагч утга нь бидэнд тохирохгүй байсан хоёр дахь параметр нь утаснуудын аюулгүй байдлыг хангах механикийг зохицуулдаг. Харамсалтай нь ядаж iOS 10-д урсгалын дотоод санах ойг дэмжихэд асуудал гардаг. Ийм учраас дээрх жишээн дээр репозиторыг туг далбаагаар нээдэг MDB_NOTLS. Үүнээс гадна энэ нь бас шаардлагатай байсан сэрээ C++ боодол lmdbxxэнэ шинж чанар бүхий хувьсагчдыг хасах.

Мэдээллийн сан

Өгөгдлийн сан нь тусдаа B модны жишээ бөгөөд бидний дээр дурдсан. Түүний нээлт нь гүйлгээний дотор тохиолддог бөгөөд энэ нь эхлээд бага зэрэг хачирхалтай санагдаж магадгүй юм.

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

Үнэн хэрэгтээ, LMDB дахь гүйлгээ нь тодорхой мэдээллийн сангийн нэгж биш харин хадгалалтын нэгж юм. Энэхүү үзэл баримтлал нь янз бүрийн мэдээллийн санд байрладаг аж ахуйн нэгжүүдэд атомын үйл ажиллагаа явуулах боломжийг олгодог. Онолын хувьд энэ нь хүснэгтийг өөр өөр мэдээллийн сан хэлбэрээр загварчлах боломжийг нээж өгдөг, гэхдээ нэг удаа би өөр замаар явсан бөгөөд үүнийг доор дэлгэрэнгүй тайлбарласан болно.

Түлхүүр ба үнэт зүйлс

бүтэц MDB_val түлхүүр ба үнэ цэнийн үзэл баримтлалыг загварчилдаг. Хадгалах газар нь тэдний семантикийн талаар ямар ч ойлголтгүй байдаг. Түүний хувьд өөр зүйл бол өгөгдсөн хэмжээтэй байт массив юм. Түлхүүрийн дээд хэмжээ нь 512 байт.

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

Харьцуулагч ашиглан дэлгүүр нь түлхүүрүүдийг өсөх дарааллаар эрэмбэлдэг. Хэрэв та үүнийг өөрийнхөөрөө орлуулахгүй бол өгөгдмөл нь ашиглагдах бөгөөд энэ нь тэдгээрийг толь бичгийн дарааллаар байтаар эрэмбэлдэг.

Ажил гүйлгээ

Гүйлгээний бүтцийг дэлгэрэнгүй тайлбарласан болно өмнөх бүлэг, тиймээс би тэдний үндсэн шинж чанаруудыг товчхон давтах болно.

  1. Бүх үндсэн шинж чанаруудыг дэмждэг ХҮЧИЛ: атом, тууштай байдал, тусгаарлалт, найдвартай байдал. MDBX дээр зассан macOS болон iOS-ийн бат бөх байдлын хувьд алдаа байгааг анхаарахгүй байхын аргагүй. Та тэдгээрийн дэлгэрэнгүйг уншиж болно README.
  2. Multithreading-д хандах хандлагыг "нэг зохиолч / олон уншигч" схемээр тайлбарласан болно. Зохиолчид бие биенээ хаадаг ч уншигчдыг хаадаггүй. Уншигчид зохиолчдыг болон бие биенээ хаадаггүй.
  3. Оруулсан гүйлгээг дэмжих.
  4. Олон хувилбарын дэмжлэг.

LMDB дахь олон хувилбар нь маш сайн тул би үүнийг үйлдлээр харуулахыг хүсч байна. Доорх кодноос та гүйлгээ бүр нь дараагийн бүх өөрчлөлтөөс бүрэн тусгаарлагдсан мэдээллийн баазыг нээх үед яг одоо байсан хувилбартай ажиллаж байгааг харж болно. Хадгалах ажлыг эхлүүлж, түүнд туршилтын бичлэг нэмэх нь сонирхолтой зүйл биш тул эдгээр зан үйлийг хорлон сүйтгэгч дор үлдээдэг.

Туршилтын бичилт нэмж байна

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

Би танд ижил трикийг SQLite ашиглан туршиж үзээд юу болсныг харахыг зөвлөж байна.

Multiversion нь iOS хөгжүүлэгчийн амьдралд маш сайхан давуу талыг авчирдаг. Энэ өмчийг ашигласнаар та хэрэглэгчийн туршлагад үндэслэн дэлгэцийн маягтуудын өгөгдлийн эх сурвалжийн шинэчлэлтийн хурдыг хялбар бөгөөд байгалийн жамаар тохируулах боломжтой. Жишээлбэл, Mail.ru Cloud програмын системийн медиа галерейгаас контентыг автоматаар ачаалах гэх мэт функцийг авч үзье. Сайн холболттой бол үйлчлүүлэгч серверт секундэд хэд хэдэн зураг нэмэх боломжтой. Хэрэв та татаж авах бүрийн дараа шинэчлээрэй UICollectionView Хэрэглэгчийн үүлэн доторх медиа контенттой бол та энэ процессын явцад 60 fps-ийг мартаж, жигд гүйлгэх боломжтой. Дэлгэц байнга шинэчлэгдэхээс сэргийлэхийн тулд далд мэдээлэл өөрчлөгдөх хурдыг ямар нэгэн байдлаар хязгаарлах хэрэгтэй UICollectionViewDataSource.

Хэрэв мэдээллийн сан нь олон хувилбарыг дэмждэггүй бөгөөд зөвхөн одоогийн төлөвтэй ажиллах боломжийг олгодог бол өгөгдлийн цаг хугацааны тогтвортой агшин зургийг үүсгэхийн тулд та үүнийг санах ойн өгөгдлийн бүтэц эсвэл түр зуурын хүснэгтэд хуулах хэрэгтэй. Эдгээр аргуудын аль нэг нь маш үнэтэй байдаг. Санах ойн санах ойд хадгалалтын хувьд бид бүтээгдсэн объектыг хадгалахаас үүдэлтэй санах ойд зардал болон нэмэлт ORM хувиргалттай холбоотой зардлыг авдаг. Түр зуурын ширээний хувьд энэ нь илүү үнэтэй таашаал бөгөөд зөвхөн өчүүхэн бус тохиолдолд л утга учиртай байдаг.

LMDB-ийн олон хувилбарт шийдэл нь өгөгдлийн эх сурвалжийг тогтвортой байлгах асуудлыг маш гоёмсог байдлаар шийддэг. Гүйлгээг нээхэд л хангалттай - бид үүнийг дуусгах хүртэл өгөгдлийн багцыг засах баталгаатай болно. Шинэчлэх хурдны логик нь одоо бүхэлдээ танилцуулгын давхаргын гарт байгаа бөгөөд томоохон нөөцийн зардал шаардагддаггүй.

Курсорууд

Курсорууд нь B модоор дамжих замаар түлхүүр-утга хосыг эмх цэгцтэй давтах механизмыг хангадаг. Тэдгээргүйгээр өгөгдлийн санд байгаа хүснэгтүүдийг үр дүнтэй загварчлах боломжгүй байсан бөгөөд бидний одоо хандаж байна.

4.2. Хүснэгтийн загварчлал

Түлхүүр дарааллын шинж чанар нь үндсэн хийсвэрлэлийн дээр хүснэгт гэх мэт өндөр түвшний хийсвэрлэлийг бүтээх боломжийг олгодог. Хэрэглэгчийн бүх файл, фолдеруудын мэдээллийг хадгалдаг үүлэн клиентийн үндсэн хүснэгтийн жишээг ашиглан энэ үйл явцыг авч үзье.

Хүснэгтийн схем

Хавтасны мод бүхий хүснэгтийн бүтцийг тохируулах нийтлэг хувилбаруудын нэг бол тухайн директор дотор байрлах бүх элементүүдийг сонгох явдал юм.Ийм төрлийн үр ашигтай асуулгад зориулсан мэдээллийн зохион байгуулалтын сайн загвар нь. Хажуугийн жагсаалт. Түлхүүр-утга хадгалах сан дээр үүнийг хэрэгжүүлэхийн тулд файл, фолдеруудын түлхүүрүүдийг үндсэн лавлах дахь гишүүнчлэлээр нь бүлэглэх байдлаар ангилах шаардлагатай. Нэмж дурдахад, лавлахын агуулгыг Windows хэрэглэгчдэд танил хэлбэрээр (эхний хавтас, дараа нь цагаан толгойн дарааллаар эрэмблэгдсэн файлууд) харуулахын тулд түлхүүрт харгалзах нэмэлт талбаруудыг оруулах шаардлагатай.

Доорх зураг нь даалгавар дээр үндэслэн байт массив хэлбэрийн товчлууруудын дүрслэл хэрхэн харагдахыг харуулж байна. Үндсэн лавлах (улаан) таних тэмдэг бүхий байтуудыг эхлээд, дараа нь төрлийг (ногоон), сүүл хэсэгт нь нэрээр (цэнхэр) байрлуулна. Анхдагч LMDB харьцуулагчаар толь бичгийн дарааллаар эрэмбэлсэн байна. шаардлагатай арга. Ижил улаан угтвартай товчлууруудыг дараалан гүйлгэх нь бидэнд холбогдох утгыг хэрэглэгчийн интерфэйс дээр (баруун талд) харуулах дарааллаар өгдөг бөгөөд нэмэлт боловсруулалт хийх шаардлагагүй.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Түлхүүрүүд болон утгуудыг цуврал болгох

Дэлхий дээр объектуудыг цуваа болгох олон аргыг зохион бүтээсэн. Бидэнд хурдаас өөр шаардлага байгаагүй тул бид өөрсдөдөө аль болох хурданг нь сонгосон буюу Си хэлний бүтцийн жишээн дээр эзэлсэн санах ойн овоолгыг сонгосон.Тиймээс лавлах элементийн түлхүүрийг дараах бүтцээр загварчилж болно. NodeKey.

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

Хадгалах NodeKey объектод шаардлагатай агуулахад MDB_val өгөгдлийн заагчийг бүтцийн эхлэлийн хаяг руу байрлуулж, тэдгээрийн хэмжээг функцээр тооцоолно sizeof.

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

Өгөгдлийн санг сонгох шалгуурын эхний бүлэгт би CRUD үйлдлийн доторх динамик хуваарилалтыг багасгахыг сонгох чухал хүчин зүйл гэж дурдсан. Функцийн код serialize LMDB-ийн хувьд мэдээллийн санд шинэ бүртгэл оруулахдаа тэдгээрээс хэрхэн бүрэн зайлсхийх боломжтойг харуулж байна. Серверээс ирж буй байт массивыг эхлээд стекийн бүтэц болгон хувиргаж, дараа нь тэдгээрийг хадгалах сан руу өчүүхэн хэмжээгээр хаядаг. LMDB дотор динамик хуваарилалт байхгүй гэдгийг харгалзан үзвэл iOS стандартын дагуу та гайхалтай нөхцөл байдлыг олж авах боломжтой - сүлжээнээс диск хүртэлх бүх замын дагуу өгөгдөлтэй ажиллахын тулд зөвхөн стек санах ойг ашиглана уу!

Хоёртын харьцуулагчтай түлхүүр захиалах

Түлхүүр дарааллын хамаарлыг харьцуулагч гэж нэрлэгддэг тусгай функцээр тодорхойлно. Хөдөлгүүр нь агуулж буй байтуудын утгын талаар юу ч мэдэхгүй тул анхдагч харьцуулагч нь түлхүүрүүдийг үг зүйн дарааллаар нь цэгцэлж, байт байт харьцуулалт хийхээс өөр аргагүй юм. Үүнийг бүтэц зохион байгуулалтад ашиглах нь цавчих сүхээр хусахтай адил юм. Гэсэн хэдий ч энгийн тохиолдолд би энэ аргыг хүлээн зөвшөөрч байна. Альтернатив хувилбарыг доор тайлбарласан байгаа, гэхдээ энд би энэ замд тархсан хэд хэдэн тармуурыг тэмдэглэх болно.

Анхаарах зүйл бол анхдагч өгөгдлийн төрлүүдийн санах ойн дүрслэл юм. Тиймээс Apple-ийн бүх төхөөрөмж дээр бүхэл тоон хувьсагчдыг форматаар хадгалдаг Бяцхан Эндиан. Энэ нь хамгийн бага ач холбогдол бүхий байт зүүн талд байх бөгөөд байт байт харьцуулалтыг ашиглан бүхэл тоог эрэмбэлэх боломжгүй болно гэсэн үг юм. Жишээлбэл, 0-ээс 511 хүртэлх тооны олонлогоор үүнийг хийхийг оролдвол дараах үр дүн гарна.

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

Энэ асуудлыг шийдэхийн тулд бүхэл тоонуудыг байт-байт харьцуулагчийн хувьд тохирох форматаар түлхүүрт хадгалах ёстой. Гэр бүлийн чиг үүрэг нь танд шаардлагатай өөрчлөлтийг хийхэд тусална hton* (Тухайлбал htons жишээнээс хоёр байт тоонуудын хувьд).

Програмчлалд мөрүүдийг дүрслэх формат нь та бүхний мэдэж байгаагаар бүхэл бүтэн юм түүх. Хэрэв мөрүүдийн семантик, мөн тэдгээрийг санах ойд илэрхийлэх кодчилол нь нэг тэмдэгтэд нэгээс илүү байт байж болохыг харуулж байгаа бол анхдагч харьцуулагчийг ашиглах санааг даруй орхих нь дээр.

Анхаарах ёстой хоёр дахь зүйл бол уялдуулах зарчим бүтцийн талбар хөрвүүлэгч. Тэдгээрийн улмаас талбаруудын хооронд санах ойд хог хаягдал бүхий байтууд үүсч болох бөгөөд энэ нь мэдээжийн хэрэг байт-байтын эрэмбэлэлтийг эвддэг. Хог хаягдлыг арилгахын тулд та талбаруудыг нарийн тодорхой дарааллаар зарлаж, тэгшлэх дүрмийг санаж байх эсвэл бүтцийн мэдэгдэлд шинж чанарыг ашиглах хэрэгтэй. packed.

Гадаад харьцуулагчтай түлхүүр захиалах

Гол харьцуулах логик нь хоёртын харьцуулагчийн хувьд хэтэрхий төвөгтэй байж болно. Олон шалтгааны нэг нь бүтэц доторх техникийн талбарууд байдаг. Бидэнд аль хэдийн танил болсон лавлах элементийн түлхүүрийн жишээн дээр би тэдгээрийн илрэлийг харуулах болно.

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

Хэдийгээр энгийн боловч ихэнх тохиолдолд энэ нь хэт их санах ой зарцуулдаг. Нэрийн буфер нь 256 байт эзэлдэг ч файл, хавтасны нэр дунджаар 20-30 тэмдэгтээс хэтрэх нь ховор.

​Бичлэгийн хэмжээг оновчтой болгох стандарт аргуудын нэг бол түүнийг бодит хэмжээгээр нь "тайрах" явдал юм. Үүний мөн чанар нь бүх хувьсах урттай талбаруудын агуулгыг бүтцийн төгсгөлд байрлах буферт, тэдгээрийн уртыг тусдаа хувьсагчдад хадгалдаг.​ Энэ аргын дагуу түлхүүр нь NodeKey дараах байдлаар өөрчлөгдөнө.

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

Цаашилбал, цуваа болгох үед өгөгдлийн хэмжээг заагаагүй болно sizeof бүх бүтэц, бүх талбаруудын хэмжээ нь тогтмол урттай дээр нь буферын бодит ашигласан хэсгийн хэмжээ юм.

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

Рефакторинг хийсний үр дүнд бид түлхүүрүүдийн эзэлдэг зайг ихээхэн хэмнэж чадсан. Гэхдээ техникийн талбараас шалтгаалж nameLength, анхдагч хоёртын харьцуулагч нь түлхүүр харьцуулалт хийхэд тохиромжгүй болсон. Хэрэв бид үүнийг өөрийнхөөрөө орлуулахгүй бол нэрний урт нь нэрнээс нь илүү эрэмбэлэх хүчин зүйл болно.

LMDB нь мэдээллийн сан бүр өөрийн гэсэн үндсэн харьцуулах функцтэй байхыг зөвшөөрдөг. Энэ нь функцийг ашиглан хийгддэг mdb_set_compare нээхээс өмнө. Тодорхой шалтгааны улмаас үүнийг мэдээллийн сангийн ашиглалтын туршид өөрчлөх боломжгүй. Харьцуулагч нь хоёртын форматаар хоёр түлхүүрийг оролт болгон хүлээн авах ба гаралт дээр харьцуулалтын үр дүнг буцаана: (-1-ээс бага), (1)-ээс их эсвэл (0) тэнцүү. Псевдокод NodeKey ийм харагдаж байна.

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

Өгөгдлийн сангийн бүх түлхүүрүүд ижил төрлийн байвал тэдгээрийн байт дүрслэлийг програмын түлхүүрийн бүтцийн төрөлд ямар ч болзолгүйгээр шилжүүлэх нь хууль ёсны юм. Энд нэг нюанс бий, гэхдээ үүнийг доор "Унших бичлэг" дэд хэсэгт авч үзэх болно.

Утга цуваа

LMDB нь хадгалагдсан бичлэгүүдийн түлхүүрүүдтэй маш эрчимтэй ажилладаг. Тэдгээрийг бие биетэйгээ харьцуулах нь ашигласан аливаа үйлдлийн хүрээнд явагддаг бөгөөд бүх шийдлийн гүйцэтгэл нь харьцуулагчийн хурдаас хамаарна. Тохиромжтой ертөнцөд анхдагч хоёртын харьцуулагч нь түлхүүрүүдийг харьцуулахад хангалттай байх ёстой, гэхдээ хэрэв та өөрөө ашиглах шаардлагатай байсан бол түүн дэх түлхүүрүүдийг цувралаас хасах процедурыг аль болох хурдан хийх ёстой.

Мэдээллийн сан нь бичлэгийн үнэ цэнийн хэсгийг (утга) тийм ч их сонирхдоггүй. Үүнийг байт дүрслэлээс объект руу хөрвүүлэх нь зөвхөн програмын кодоор, жишээлбэл, дэлгэцэн дээр харуулах шаардлагатай үед л тохиолддог. Энэ нь харьцангуй ховор тохиолддог тул энэ процедурын хурдны шаардлагууд тийм ч чухал биш бөгөөд үүнийг хэрэгжүүлэхдээ бид тав тухтай байдалд анхаарлаа төвлөрүүлэхэд илүү чөлөөтэй байдаг.Жишээ нь, татаж аваагүй файлуудын мета өгөгдлийг цуваа болгохын тулд бид ашигладаг. NSKeyedArchiver.

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

Гэсэн хэдий ч гүйцэтгэл чухал хэвээр байх үе байдаг. Жишээлбэл, хэрэглэгчийн үүлний файлын бүтцийн талаархи мета мэдээллийг хадгалахдаа бид ижил санах ойн объектуудыг ашигладаг. Тэдгээрийн цуваа дүрслэлийг бий болгох ажлын онцлох зүйл бол лавлахын элементүүдийг ангиллын шатлалаар загварчилсан явдал юм.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Үүнийг Си хэл дээр хэрэгжүүлэхийн тулд өв залгамжлагчдын тодорхой талбаруудыг тусдаа бүтцэд байрлуулж, үндсэн нэгтэй нь холболтыг төрлийн нэгдлийн талбараар зааж өгдөг. Холбооны бодит агуулгыг техникийн шинж чанарын төрлөөр тодорхойлдог.

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

Бичлэг нэмэх, шинэчлэх

Цуваачилсан түлхүүр болон утгыг дэлгүүрт нэмж болно. Үүнийг хийхийн тулд функцийг ашиглана уу mdb_put.

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

Тохиргооны үе шатанд хадгалалтад нэг түлхүүрээр олон бичлэг хадгалахыг зөвшөөрч эсвэл хориглож болно.Хэрэв товчлууруудыг давхардуулахыг хориглосон бол бичлэг оруулахдаа одоо байгаа бичлэгийг шинэчлэхийг зөвшөөрөх эсэхийг тодорхойлж болно. Хэрэв эвдрэл нь зөвхөн кодын алдааны үр дүнд гарч болзошгүй бол туг зааж өгснөөр өөрийгөө үүнээс хамгаалж болно. NOOVERWRITEБайна.

Бичлэгүүдийг уншиж байна

LMDB дахь бичлэгүүдийг уншихын тулд функцийг ашиглана уу mdb_get. Хэрэв түлхүүр-утга хосыг өмнө нь хаясан бүтцээр төлөөлүүлсэн бол энэ процедур иймэрхүү харагдана.

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

Үзүүлсэн жагсаалт нь бүтцийн дампаар дамжуулан цуваа болгох нь зөвхөн бичих үед төдийгүй өгөгдлийг унших үед динамик хуваарилалтаас хэрхэн ангижрах боломжийг олгодог болохыг харуулж байна. Функцээс үүсэлтэй mdb_get заагч нь өгөгдлийн сан объектын байт дүрслэлийг хадгалдаг виртуал санах ойн хаягийг яг хардаг. Үнэн хэрэгтээ бид маш өндөр өгөгдөл унших хурдыг бараг үнэ төлбөргүй өгдөг нэг төрлийн ORM авдаг. Арга барилын бүх гоо үзэсгэлэнг үл харгалзан үүнтэй холбоотой хэд хэдэн шинж чанарыг санах нь зүйтэй.

  1. Зөвхөн унших гүйлгээний хувьд утгын бүтцийн заагч нь зөвхөн гүйлгээ хаагдах хүртэл хүчинтэй байх баталгаатай. Өмнө дурьдсанчлан, бичих дээр хуулбарлах зарчмын ачаар объект байрлах В модны хуудаснууд нь дор хаяж нэг гүйлгээгээр иш татсан тохиолдолд өөрчлөгдөхгүй хэвээр байна. Үүний зэрэгцээ, тэдгээртэй холбоотой сүүлийн гүйлгээ дууссаны дараа хуудсыг шинэ өгөгдөлд дахин ашиглах боломжтой. Хэрэв объектууд тэдгээрийг үүсгэсэн гүйлгээнд амьд үлдэх шаардлагатай бол тэдгээрийг хуулбарлах шаардлагатай хэвээр байна.
  2. Дахин бичих гүйлгээний хувьд үүссэн утгын бүтцийн заагч нь зөвхөн эхний өөрчлөх процедур (өгөгдөл бичих эсвэл устгах) хүртэл хүчинтэй байх болно.
  3. Хэдийгээр бүтэц нь NodeValue бүрэн хэмжээний биш, харин тайрсан ("Гадаад харьцуулагч ашиглан түлхүүрүүдийг захиалах" хэсгийг үзнэ үү) та заагчаар дамжуулан түүний талбаруудад аюулгүй нэвтрэх боломжтой. Хамгийн гол нь үүнийг үл тоомсорлож болохгүй!
  4. Ямар ч тохиолдолд хүлээн авсан заагчаар бүтцийг өөрчлөх ёсгүй. Бүх өөрчлөлтийг зөвхөн аргаар хийх ёстой mdb_put. Гэсэн хэдий ч, та үүнийг хийхийг хичнээн их хүсч байсан ч энэ нь боломжгүй, учир нь энэ бүтэц байрлах санах ойн хэсэг нь зөвхөн уншигдах горимд зурагдсан байдаг.
  5. Жишээлбэл, функцийг ашиглан хадгалах сангийн дээд хэмжээг нэмэгдүүлэх зорилгоор файлыг процессын хаягийн зайд дахин буулгана уу mdb_env_set_map_size бүх ажил гүйлгээ болон холбогдох байгууллагуудыг бүхэлд нь хүчингүй болгож, ялангуяа тодорхой объектуудыг заадаг.

Эцэст нь, өөр нэг онцлог шинж чанар нь маш нууцлаг бөгөөд түүний мөн чанарыг илчлэх нь зөвхөн өөр догол мөрөнд багтахгүй юм. В модны тухай бүлэгт би түүний хуудаснууд санах ойд хэрхэн байрласан тухай диаграммыг өгсөн. Үүнээс үзэхэд цуваа өгөгдөл бүхий буферийн эхлэлийн хаяг нь туйлын дур зоргоороо байж болно. Үүнээс болж тэдгээрт заагчийг бүтцэд хүлээн авсан MDB_val ба бүтцийн заагч болгон бууруулсан нь ерөнхий тохиолдолд тэгш бус болж хувирдаг. Үүний зэрэгцээ, зарим чипүүдийн архитектур (iOS-ийн хувьд энэ нь armv7) нь аливаа өгөгдлийн хаяг нь машины үгийн хэмжээ эсвэл өөрөөр хэлбэл системийн битийн хэмжээ ( armv7-ийн хувьд энэ нь 32 бит). Өөрөөр хэлбэл, гэх мэт үйл ажиллагаа *(int *foo)0x800002 Тэдний дээр оргон зайлахтай тэнцэх бөгөөд шүүхийн шийдвэрээр цаазаар авахуулахад хүргэдэг EXC_ARM_DA_ALIGN. Ийм гунигтай хувь тавилангаас зайлсхийх хоёр арга бий.

Эхнийх нь өгөгдлийг тодорхой уялдуулсан бүтэц болгон урьдчилан хуулах явдал юм. Жишээлбэл, захиалгат харьцуулагч дээр үүнийг дараах байдлаар тусгана.

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

Түлхүүр утгын бүтцийг шинж чанарт нийцүүлэхгүй байж болохыг хөрвүүлэгчид урьдчилан мэдэгдэх өөр арга зам юм. aligned(1). ARM дээр та ижил нөлөө үзүүлэх боломжтой хүрэх болон савласан шинж чанарыг ашиглан. Энэ нь бүтцийн эзэлдэг орон зайг оновчтой болгоход тусалдаг гэдгийг харгалзан үзвэл энэ аргыг надад илүүд үзэж байна. приводит өгөгдөлд хандах үйл ажиллагааны өртөг нэмэгдэхэд хүргэдэг.

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

Хүрээний асуулга

Бүлэг бичлэгийг давтахын тулд LMDB нь курсорын хийсвэрлэлийг өгдөг. Бидэнд аль хэдийн танил болсон хэрэглэгчийн үүл мета өгөгдөл бүхий хүснэгтийн жишээг ашиглан түүнтэй хэрхэн ажиллахыг харцгаая.

Лавлах дахь файлуудын жагсаалтыг харуулахын тулд түүний хүүхдийн файл, хавтастай холбоотой бүх түлхүүрүүдийг олох шаардлагатай. Өмнөх дэд хэсгүүдэд бид түлхүүрүүдийг эрэмбэлсэн NodeKey Ингэснээр тэдгээрийг үндсэн лавлахын ID-аар эрэмбэлдэг. Тиймээс техникийн хувьд хавтасны агуулгыг сэргээх ажил нь өгөгдсөн угтвар бүхий товчлуурын бүлгийн дээд хил дээр курсорыг байрлуулж, дараа нь доод хил рүү давтах явдал юм.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Дээд хязгаарыг дараалсан хайлтаар шууд олж болно. Үүнийг хийхийн тулд курсорыг өгөгдлийн сангийн бүх товчлуурын жагсаалтын эхэнд байрлуулж, доор нь үндсэн лавлах танигчтай түлхүүр гарч ирэх хүртэл нэмэгдүүлнэ. Энэ арга нь 2 илэрхий сул талтай:

  1. Шугаман хайлтын нарийн төвөгтэй байдал нь мэдэгдэж байгаачлан мод, ялангуяа В хэлбэрийн модыг логарифмын хугацаанд хийж болно.
  2. Дэмий хоосон, хайж буй хуудасны өмнөх бүх хуудсыг файлаас үндсэн санах ой руу аваачдаг бөгөөд энэ нь маш үнэтэй юм.

Аз болоход LMDB API нь курсорыг анхлан байршуулах үр дүнтэй аргыг өгдөг.Үүний тулд та интервалын дээд хязгаарт байрлах түлхүүрээс бага буюу тэнцүү утгатай түлхүүр үүсгэх хэрэгтэй. Жишээлбэл, дээрх зураг дээрх жагсаалттай холбоотойгоор бид аль талбарт түлхүүр хийж болно parentId 2-той тэнцүү байх ба бусад нь тэгээр дүүрсэн байна. Ийм хэсэгчлэн дүүргэсэн түлхүүрийг функцийн оролтод нийлүүлдэг mdb_cursor_get үйл ажиллагааг харуулж байна 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);

Хэрэв бүлгийн товчлуурын дээд хил олдвол бид уулзах эсвэл өөр товчлууртай таарах хүртэл бид үүнийг давтана. parentId, эсвэл түлхүүрүүд нь огт дуусахгүй

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

Хамгийн сайхан нь mdb_cursor_get ашиглан давталтын нэг хэсэг болгон бид зөвхөн түлхүүр төдийгүй утгыг авдаг. Хэрэв түүврийн нөхцлийг биелүүлэхийн тулд та бусад зүйлсийн дотор бичлэгийн утгын хэсгийн талбаруудыг шалгах шаардлагатай бол нэмэлт дохио зангаагүйгээр тэдгээрт хандах боломжтой.

4.3. Хүснэгт хоорондын харилцааг загварчлах

Одоогийн байдлаар бид нэг хүснэгтийн мэдээллийн сантай дизайн хийх, ажиллах бүх талыг авч үзсэн. Хүснэгт нь ижил төрлийн түлхүүр-утга хосуудаас бүрдэх эрэмбэлэгдсэн бичлэгүүдийн багц гэж бид хэлж болно. Хэрэв та түлхүүрийг тэгш өнцөгт хэлбэрээр, холбогдох утгыг параллелепипед хэлбэрээр харуулбал мэдээллийн сангийн харааны диаграммыг авах болно.

Gg

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Гэсэн хэдий ч бодит амьдрал дээр ийм бага цус урсгах нь ховор байдаг. Ихэнхдээ мэдээллийн санд, нэгдүгээрт, хэд хэдэн хүснэгттэй байх, хоёрдугаарт, үндсэн түлхүүрээс өөр дарааллаар сонголт хийх шаардлагатай байдаг. Энэ сүүлчийн хэсэг нь тэдгээрийг бий болгох, харилцан уялдаатай холбоотой асуудлуудад зориулагдсан болно.

Индекс хүснэгтүүд

Клоуд програм нь "Галерей" хэсэгтэй. Энэ нь бүхэл бүтэн үүл дэх медиа контентыг огноогоор нь эрэмбэлдэг. Ийм сонголтыг оновчтой хэрэгжүүлэхийн тулд үндсэн хүснэгтийн хажууд шинэ төрлийн товчлуур бүхий өөр нэгийг үүсгэх хэрэгтэй. Тэдгээр нь файлыг үүсгэсэн огноо бүхий талбарыг агуулж байх бөгөөд энэ нь эрэмбэлэх үндсэн шалгуур болно. Шинэ түлхүүрүүд нь үндсэн хүснэгтийн түлхүүрүүдтэй ижил өгөгдөлд хамаарах тул тэдгээрийг индекс түлхүүр гэж нэрлэдэг. Доорх зурган дээр тэдгээрийг улбар шараар тодруулсан.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Нэг мэдээллийн сан дотор өөр өөр хүснэгтүүдийн түлхүүрүүдийг бие биенээсээ салгахын тулд бүгдэд нь нэмэлт техникийн талбар tableId нэмсэн. Үүнийг эрэмбэлэх хамгийн чухал зүйл болгосноор бид түлхүүрүүдийг эхлээд хүснэгтээр, мөн хүснэгтийн дотор өөрийн дүрмийн дагуу бүлэглэх болно.

Индекс түлхүүр нь үндсэн түлхүүртэй ижил өгөгдлийг иш татдаг. Үндсэн түлхүүрийн үнэ цэнийн хуулбарыг түүнтэй холбох замаар энэ өмчийг шууд хэрэгжүүлэх нь хэд хэдэн үүднээс оновчтой биш юм.

  1. Авсан зайны хувьд мета өгөгдөл нь нэлээд баялаг байж болно.
  2. Гүйцэтгэлийн үүднээс авч үзвэл зангилааны мета өгөгдлийг шинэчлэх үед хоёр товчлуур ашиглан дахин бичих шаардлагатай болно.
  3. Кодын дэмжлэгийн үүднээс авч үзвэл, хэрэв бид аль нэг товчлуурын өгөгдлийг шинэчлэхээ мартвал хадгалах сан дахь өгөгдлийн үл нийцэх алдаа гарах болно.

Дараа нь бид эдгээр дутагдлыг хэрхэн арилгах талаар авч үзэх болно.

Хүснэгт хоорондын харилцааг зохион байгуулах

Загвар нь индексийн хүснэгтийг үндсэн хүснэгттэй холбоход тохиромжтой "үнэ цэнэ болгон түлхүүр". Нэрнээс нь харахад индексийн бичлэгийн утга хэсэг нь үндсэн түлхүүр утгын хуулбар юм. Энэ арга нь үндсэн бичлэгийн утгын хэсгийн хуулбарыг хадгалахтай холбоотой дээр дурдсан бүх сул талуудыг арилгадаг. Цорын ганц зардал нь индексийн түлхүүрээр утгыг олж авахын тулд мэдээллийн санд нэг биш 2 асуулга хийх шаардлагатай болно. Схемийн хувьд мэдээллийн сангийн схем нь иймэрхүү харагдаж байна.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Хүснэгтүүдийн хоорондын харилцааг зохион байгуулах өөр нэг загвар нь "Нэмэлт түлхүүр". Үүний мөн чанар нь түлхүүрийг эрэмбэлэхийн тулд биш, харин холбогдох түлхүүрийг дахин үүсгэхэд шаардлагатай нэмэлт шинж чанаруудыг нэмэх явдал юм.Mail.ru Cloud програмд ​​​​хэрэглээний бодит жишээнүүд байдаг, гэхдээ гүнзгий шумбахаас зайлсхийхийн тулд iOS-ийн тодорхой хүрээнүүдийн контекстийн хувьд би зохиомол, гэхдээ илүү тодорхой жишээ өгөх болно.​

Клоуд гар утасны үйлчлүүлэгчид нь хэрэглэгчийн бусад хүмүүстэй хуваалцсан бүх файл, хавтаснуудыг харуулдаг хуудастай байдаг. Ийм файлууд харьцангуй цөөхөн байдаг бөгөөд тэдгээртэй холбоотой олон нийтийн мэдээллийн талаархи олон төрлийн тодорхой мэдээлэл байдаг (хэнд хандах эрхтэй, ямар эрхтэй гэх мэт) тул файлын үнэ цэнийг дарамтлах нь оновчтой биш юм. үндсэн хүснэгтэд бичнэ үү. Гэсэн хэдий ч, хэрэв та ийм файлуудыг офлайнаар харуулахыг хүсвэл хаа нэгтээ хадгалах хэрэгтэй. Байгалийн шийдэл бол түүнд зориулж тусдаа хүснэгт үүсгэх явдал юм. Доорх диаграммд түүний түлхүүр нь "P"-ийн угтвартай бөгөөд "propname" орлуулагчийг "нийтийн мэдээлэл" гэсэн илүү тодорхой утгаар сольж болно.​

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Шинэ хүснэгтийг хадгалахын тулд бүх өвөрмөц мета өгөгдлийг бичлэгийн утга хэсэгт байрлуулна. Үүний зэрэгцээ та үндсэн хүснэгтэд аль хэдийн хадгалагдсан файл, фолдеруудын мэдээллийг хуулбарлахыг хүсэхгүй байна. Үүний оронд "P" товчлуур дээр "зангилааны ID" болон "цаг хугацааны тэмдэг" талбар хэлбэрээр нэмэлт өгөгдлийг нэмнэ. Тэдгээрийн ачаар та индексийн түлхүүр үүсгэж, үүнээс үндсэн түлхүүрийг авч, эцэст нь зангилааны мета өгөгдлийг олж авах боломжтой.

Дүгнэлт

LMDB-ийн хэрэгжилтийн үр дүнг бид эерэгээр үнэлж байна. Үүний дараа програм хөлдөх тоо 30% -иар буурсан байна.

iOS програмууд дахь LMDB түлхүүр-утга мэдээллийн сангийн гялалзсан байдал, ядуурал

Хийсэн ажлын үр дүн нь iOS-ийн багаас давж гарсан. Одоогийн байдлаар Android програмын үндсэн "Файл" хэсгүүдийн нэг нь LMDB ашиглахад шилжсэн бөгөөд бусад хэсгүүд нь замдаа явж байна. Түлхүүр үнэ цэнийн хадгалалт хэрэгждэг Си хэл нь C++ хэл дээрх кросс платформыг тойруулан хэрэглээний хүрээ үүсгэхэд сайн тусалсан. Үүссэн C++ номын санг Objective-C болон Kotlin дээрх платформ кодтой холбоход код үүсгэгчийг ашигласан. Жинни Dropbox-оос, гэхдээ энэ нь огт өөр түүх юм.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх