Асаблівасці праектавання мадэлі даных для NoSQL

Увядзенне

Асаблівасці праектавання мадэлі даных для NoSQL «Трэба бегчы з усіх ног, каб толькі заставацца на месцы,
а каб кудысьці патрапіць, трэба бегчы як мінімум удвая хутчэй!»
(с) Аліса ў краіне цудаў

Некаторы час таму мяне папрасілі прачытаць лекцыю. аналітыкам нашай кампаніі на тэму праектавання мадэляў дадзеных, бо седзячы доўгі час на праектах (часам па некалькі гадоў) мы выпускаем з-пад увагі адбывалае вакол у свеце ІТ-тэхналогій. У нашай кампаніі (ужо так атрымалася) на шматлікіх праектах не выкарыстоўваюцца NoSQL-базы дадзеных (прынамсі пакуль), таму ў сваёй лекцыі я асобна надаў ім некаторую ўвагу на прыкладзе HBase і паспрабаваў арыентаваць выклад матэрыялу на тых, хто з імі ніколі не працаваў. У прыватнасці, я ілюстраваў некаторыя асаблівасці праектавання мадэлі даных на прыкладзе, які некалькі гадоў таму прачытаў у артыкуле "Introduction to HB ase Schema Design" by Amandeep Khurana. Разбіраючы прыклады, я параўноўваў паміж сабой некалькі варыянтаў рашэння адной і той жа задачы, каб лепш данесці да слухачоў асноўныя ідэі.

Нядаўна, «ад няма чаго рабіць», я задаўся пытаннем (доўгія травеньскія выходныя ў рэжыме каранціну да гэтага асабліва размяшчаюць), наколькі тэарэтычныя выкладкі будуць адпавядаць практыцы? Уласна, так і нарадзілася ідэя гэтага артыкула. Распрацоўнік, які не першы дзень працуе з NoSQL, магчыма і не запазыча з яе нешта новае (і таму можа адразу праматаць паўартыку). Але для аналітыкаў, Якія яшчэ не працавалі шчыльна з NoSQL, мяркую, яна будзе карысная для атрымання базавых уяўленняў аб асаблівасцях праектавання мадэляў дадзеных для HBase.

Разбор прыкладу

На мой погляд, перш чым пачаць выкарыстоўваць NoSQL базы дадзеных, неабходна добра падумаць і ўзважыць "за" і "супраць". Часта задачу хутчэй за ўсё можна вырашыць і на традыцыйных рэляцыйных СКБД. Таму лепш не выкарыстоўваць NoSQL без істотных на тое падстаў. Калі ўсё ж было прынятае рашэнне выкарыстоўваць NoSQL базу дадзеных, то варта ўлічыць, што падыходы да праектавання тут некалькі адрозніваюцца. Асабліва некаторыя з іх могуць быць нязвыклыя тым, хто да гэтага меў справу толькі з рэляцыйнымі СКБД (па маіх назіраннях). Так, у "рэляцыйным" свеце мы звычайна ідзем ад мадэлявання прадметнай вобласці, і ўжо потым пры неабходнасці праводзім дэнармалізацыю мадэлі. У NoSQL ж мы адразу павінны ўлічваць меркаваныя сцэнары працы з дадзенымі і першапачаткова дэнармалізоўваць дадзеныя. Акрамя таго, ёсць шэраг іншых адрозненняў, аб якіх будзе напісана ніжэй.

Разгледзім наступную "сінтэтычную" задачу, з якой і будзем далей працаваць:

Неабходна спраектаваць структуру захоўвання спісу сяброў карыстальнікаў нейкай абстрактнай сацыяльнай сеткі. Для спрашчэння будзем меркаваць, што ўсе сувязі ў нас накіраваныя (як у Інстаграме, а не ў Linkedin). Структура павінна дазваляць эфектыўна:

  • Адказваць на пытанне, ці чытае карыстальнік А карыстальніка Б (шаблон чытання)
  • Дазваляць дадаваць/выдаляць сувязі ў выпадку падпіскі/адпіскі карыстальніка А ад карыстальніка Б (шаблон змены даных)

Вядома ж, варыянтаў рашэння задачы мноства. У звычайнай рэляцыйнай БД мы б хутчэй за ўсё проста зрабілі б табліцу сувязяў (магчыма, тыпізаваную, калі, напрыклад, патрабуецца захоўваць карыстацкую групу: сям'я, праца і да т.п., у якую ўваходзіць дадзены "сябар"), а для аптымізацыі хуткасці доступу дадалі б індэксы/партыцыянаванне. Хутчэй за ўсё выніковая табліца выглядала б прыкладна вось так:

user_id
friend_id

Вася
Пецька

Вася
Оля

тут і далей для навочнасці і лепшага разумення замест ID буду паказваць імёны

У выпадку ж з HBase мы ведаем, што:

  • эфектыўны пошук, які не прыводзіць да full table scan, магчымы выключна па ключы
    • уласна, таму пісаць звыклыя шматлікім SQL-запыты да падобных баз - дрэнная ідэя; тэхнічна, вядома, вы можаце з той жа Impala адправіць SQL-запыт з Join'амі і іншай логікай у HBase, але вось наколькі гэта будзе эфектыўна…

Таму ID карыстальніка мы вымушаны выкарыстоўваць як ключ. А першай думкай на тэму "дзе і як захоўваць ID сяброў?" можа быць ідэя захоўвання іх у калонках. Гэты самы відавочны і "наіўны" варыянт будзе выглядаць прыкладна так (назавем яго Варыянт 1 (default), каб спасылацца ў далейшым):

RowKey
калонкі

Вася
1: Пеця
2: Оля
3: Даша

Пецька
1: Маша
2: Вася

Тут кожны радок адпавядае аднаму карыстачу сеткі. Калонкі маюць імёны: 1, 2, … - па колькасці сяброў, і ў калонках захоўваюцца ID сяброў. Важна заўважыць, што ў кожнага радка будзе розны лік калонак. У прыкладзе на малюнку вышэй адзін радок мае тры калонкі (1, 2 і 3), а другая - толькі дзве (1 і 2) - тут мы самі скарысталіся двума ўласцівасцямі HBase, якіх няма ў рэляцыйных БД:

  • магчымасцю дынамічна змяняць склад калонак (дадаем сябра -> дадаем калонку, выдаляны сябра -> выдаляны калонку)
  • у розных радкоў можа быць розны склад калонак

Праверым нашу структуру на адпаведнасць патрабаванням задачы:

  • Чытанне дадзеных: для таго, каб зразумець, ці падпісаны Вася на Олю, нам трэба будзе адымаць увесь радок па ключы RowKey = "Вася" і перабіраць значэння калонак, пакуль не "сустрэнем" у іх Олю. Або перабраць значэнні ўсіх калонак, "не сустрэць" Олю і вярнуць адказ False;
  • Змяненне дадзеных: даданне сябра: для падобнай задачы нам таксама спатрэбіцца адымаць увесь радок па ключы RowKey = "Вася", каб палічыць агульную колькасць яго сяброў. Гэта агульная колькасць сяброў нам неабходна, каб вызначыць нумар калонкі, у якую трэба запісаць ID новага сябра.
  • Змяненне дадзеных: выдаленне сябра:
    • Неабходна адымаць увесь радок па ключы RowKey = "Вася" і перабіраць калонкі для таго, каб знайсці тую самую, у якой запісаны які выдаляецца сябар;
    • Далей нам, пасля выдалення сябра, трэба "зрушыць" усе дадзеныя на адну калонку, каб не атрымаць "разрываў" у іх нумарацыі.

Давайце зараз ацэнім, наколькі дадзеныя алгарытмы, якія нам неабходна будзе рэалізоўваць на баку "ўмоўнага прыкладання", будуць прадукцыйныя, выкарыстоўваючы О-сімволіку. Абазначым памер нашай гіпатэтычнай сацыяльнай сеткі як n. Тады максімальная колькасць сяброў у аднаго карыстальніка можа быць (n-1). Гэтай (-1) мы можам у далейшым занядбаць для нашых мэт, бо ў рамках выкарыстання О-сімволікі яна неістотная.

  • Чытанне дадзеных: неабходна адымаць увесь радок і перабраць у мяжы ўсе яго калонкі. Значыць верхняя ацэнка выдаткаў будзе прыкладна О(n)
  • Змяненне дадзеных: даданне сябра: для вызначэння колькасці сяброў патрабуецца перабраць усе калонкі радка, пасля чаго ўставіць новую калонку => О(n)
  • Змяненне дадзеных: выдаленне сябра:
    • Аналагічна даданню - патрабуецца ў мяжы перабраць усе калонкі => О(n)
    • Пасля выдалення калонак нам трэба "зрушыць" іх. Калі рэалізоўваць гэта "у лоб", то ў мяжы спатрэбіцца яшчэ да (n-1) аперацый. Але мы тут і далей у практычнай частцы прымянім іншы падыход, які будзе рэалізоўваць "псеўда-зрух" за фіксаванае кол-ць аперацый - гэта значыць на яго будзе марнавацца канстантнае час па-за залежнасці ад n. Гэтым канстантным часам (калі быць дакладным, то О(2)) у параўнанні з О(n) можна занядбаць. Падыход праілюстраваны на малюнку ніжэй: мы проста капіюем дадзеныя з "апошняй" калонкі ў тую, з якой трэба выдаліць дадзеныя, пасля чаго выдаляем апошнюю калонку:
      Асаблівасці праектавання мадэлі даных для NoSQL

Разам ва ўсіх сцэнарах мы атрымалі асімптатычную вылічальную складанасць O(n).
Напэўна, вы ўжо заўважылі, што нам даводзіцца амаль заўсёды вычытваць з базы ўвесь радок цалкам, прычым у двух выпадках з трох толькі для таго, каб перабраць усе калонкі і палічыць агульную колькасць сяброў. Таму ў якасці спробы аптымізацыі можна дадаць калонку "count", у якой захоўваць агульны лік сяброў кожнага карыстальніка сеткі. У гэтым выпадку мы можам не вычытваць увесь радок цалкам для падліку агульнага кол-ва сяброў, а прачытаць толькі адну калонку "count". Галоўнае, не забываць абнаўляць "count" пры маніпуляцыі з дадзенымі. В.а. атрымліваем палепшаны Варыянт 2 (count):

RowKey
калонкі

Вася
1: Пеця
2: Оля
3: Даша
count: 3

Пецька
1: Маша
2: Вася

count: 2

У параўнанні з першым варыянтам:

  • Чытанне дадзеных: для атрымання адказу на пытанне «Ці чытае Вася Олю?» нічога не змянілася => О(n)
  • Змяненне дадзеных: даданне сябра: Мы спрасцілі ўстаўку новага сябра, бо зараз нам не трэба вычытваць увесь радок і перабіраць яго калонкі, а можна атрымаць толькі значэнне калонкі "count" і г.д. адразу вызначыць нумар калонкі для ўстаўкі новага сябра. Гэта прыводзіць да зніжэння вылічальнай складанасці да О(1)
  • Змяненне дадзеных: выдаленне сябра: Пры выдаленні сябра мы можам гэтак жа скарыстацца дадзенай калонкай, каб зменшыць колькасць аперацый уводу-высновы пры «зруху» дадзеных на адно вочка налева. Але неабходнасць перабору калонак для пошуку той, якую неабходна выдаліць, усё роўна застаецца, таму => O(n)
  • З іншага боку, зараз нам пры абнаўленні дадзеных неабходна кожны раз абнаўляць і калонку "count", але на гэта сыходзіць канстантны час, якім у рамках О-сімволікі можна занядбаць

У цэлым варыянт 2 бачыцца крыху больш аптымальным, але гэта хутчэй "эвалюцыя замест рэвалюцыі". Для здзяйснення "рэвалюцыі" нам спатрэбіцца Варыянт 3 (col).
Перавернем усё «з ног на галаву»: прызначым імем калонкі ідэнтыфікатар карыстальніка! Што будзе запісана ў самой калонцы - для нас ужо не іста важна, хай будзе лічба 1 (наогул, з карыснага тамака можна захоўваць, напрыклад, групу "сям'я/сябры/і да т.п."). Дадзены падыход можа здзівіць непадрыхтаванага «абывацеля», які да гэтага не меў досведу працы з NoSQL-базамі, але менавіта ён дазваляе выкарыстоўваць патэнцыял HBase у дадзенай задачы нашмат больш эфектыўна:

RowKey
калонкі

Вася
Пеця: 1
Оля: 1
Даша: 1

Пецька
Маша: 1
Вася: 1

Тут мы атрымліваем адразу некалькі пераваг. Каб іх зразумець, прааналізуем новую структуру і ацэнім вылічальную складанасць:

  • Чытанне дадзеных: для таго, каб адказаць на пытанне, ці падпісаны Вася на Олю, дастаткова прачытаць адну калонку «Оля»: калі яна ёсць, то адказ True, калі не – False => O(1)
  • Змяненне дадзеных: даданне сябра: Даданне сябра: дастаткова проста дадаць новую калонку «ID сябра» => O(1)
  • Змяненне дадзеных: выдаленне сябра: дастаткова проста выдаліць калонку «ID сябра» => O(1)

Як бачым, істотнай перавагай такой мадэлі захоўвання з'яўляецца тое, што мы ва ўсіх неабходных нам сцэнарах аперуем толькі адной калонкай, пазбягаючы вычытванні з базы ўсяго радка і тым больш, перабору ўсіх калонак гэтага радка. На гэтым можна было б спыніцца, але...

Можна збянтэжыцца і пайсці яшчэ трохі далей па шляху аптымізацыі прадукцыйнасці і памяншэнні аперацый уводу-высновы пры звароце да базы. Што калі захоўваць поўную інфармацыю аб сувязі непасрэдна ў самым ключы радка? Гэта значыць зрабіць ключ складовым выгляду userID.friendID? У гэтым выпадку нам наогул можна нават не вычытваць калонкі радка (Варыянт 4(row)):

RowKey
калонкі

Вася.Пеця
Пеця: 1

Вася.Оля
Оля: 1

Вася.Даша
Даша: 1

Пеця.Маша
Маша: 1

Пеця.Вася
Вася: 1

Відавочна, што ацэнка ўсіх сцэнарыяў маніпуляцыі з дадзенымі ў такой структуры таксама, як і ў папярэднім варыянце будзе О(1). Розніца з варыянтам 3 будзе ўжо выключна ў эфектыўнасці аперацый уводу-вываду ў БД.

Ну і апошні "банцік". Лёгка заўважыць, што ў варыянце 4 у нас ключ радка будзе мець зменную даўжыню, што, магчыма, можа паўплываць на прадукцыйнасць (тут успамінаем, што HBase захоўвае дадзеныя як набор байтаў і радкі ў табліцах адсартаваны па ключы). Плюс у нас ёсць раздзяляльнік, які ў некаторых сцэнарах можа спатрэбіцца апрацоўваць. Каб выключыць гэты ўплыў, можна выкарыстоўваць хэшы ад userID і friendID, і бо абодва хэша будуць мець сталую даўжыню, то можна проста канкатэнаваць іх, без падзельніка. Тады дадзеныя ў табліцы будуць выглядаць так (Варыянт 5(hash)):

RowKey
калонкі

dc084ef00e94aef49be885f9b01f51c01918fa783851db0dc1f72f83d33a5994
Пеця: 1

dc084ef00e94aef49be885f9b01f51c0f06b7714b5ba522c3cf51328b66fe28a
Оля: 1

dc084ef00e94aef49be885f9b01f51c00d2c2e5d69df6b238754f650d56c896a
Даша: 1

1918fa783851db0dc1f72f83d33a59949ee3309645bd2c0775899fca14f311e1
Маша: 1

1918fa783851db0dc1f72f83d33a5994dc084ef00e94aef49be885f9b01f51c0
Вася: 1

Відавочна, што алгарытмічная складанасць працы з такой структурай па разгляданым намі сцэнарамі, будзе такая ж, як у варыянту 4 - гэта значыць О(1).
Разам звядзём усе нашы адзнакі вылічальнай складанасці ў адну табліцу:

Даданне сябра
Праверка сябра
Выдаленне сябра

Варыянт 1 (default)
Аб (п)
Аб (п)
Аб (п)

Варыянт 2 (count)
O (1)
Аб (п)
Аб (п)

Варыянт 3 (column)
O (1)
O (1)
O (1)

Варыянт 4 (row)
O (1)
O (1)
O (1)

Варыянт 5 (hash)
O (1)
O (1)
O (1)

Як відаць, варыянты 3-5 выглядаюць найболей пераважным і тэарэтычна забяспечвае выкананне ўсіх неабходных сцэнараў маніпуляцыі з дадзенымі за канстантны час. Ва ўмове нашай задачы няма яўнага патрабавання па атрыманні спісу ўсіх сяброў карыстальніка, але ў рэальнай праектнай дзейнасці нам, як добрым аналітыкам, добра было б "прадбачыць", што падобная задача можа ўзнікнуць і "пасаслаць саломку". Таму мае сімпатыі на баку варыянта 3. Але цалкам верагодна, што ў рэальным праекце дадзены запыт мог быць ужо вырашана іншымі сродкамі, таму без агульнага бачання ўсёй задачы лепш не рабіць канчатковых высноў.

Падрыхтоўка эксперыменту

Вышэйпададзеныя тэарэтычныя развагі хацелася б праверыць на практыцы - гэта і было мэтай ўзнікла на доўгіх выходных задумкі. Для гэтага неабходна ацаніць хуткасць працы нашага "умоўнага дадатку" ва ўсіх апісаных сцэнарах выкарыстання базы, а таксама рост гэтага часу з ростам памеру сацыяльнай сеткі (n). Мэтавым параметрам, які нас цікавіць і які мы будзем замяраць падчас эксперыменту, з'яўляецца час, затрачаны "умоўным дадаткам", на выкананне адной "бізнес-аперацыі". Пад "бізнэс-аперацыяй" мы разумеем адну з наступных:

  • Даданне аднаго новага сябра
  • Праверка, ці з'яўляецца карыстальнік А сябрам карыстальніка Б
  • Выдаленне аднаго сябра

Такім чынам, з улікам абазначаных у першапачатковай пастаноўцы патрабаванняў, сцэнарый праверкі вымалёўваецца наступны:

  • Запіс дадзеных. Згенераваць выпадковым чынам зыходную сетку памерам n. Для большага набліжэння да "рэальнага свету" колькасць сяброў у кожнага карыстальніка - гэтак жа выпадковая велічыня. Замераць час, за які наша «ўмоўнае прыкладанне» запіша ў HBase усе згенераваныя дадзеныя. Потым атрыманы час падзяліць на агульную колькасць дададзеных сяброў - так мы атрымаем сярэдні час на адну "бізнес-аперацыю"
  • Чытанне дадзеных. Для кожнага карыстальніка скласці спіс «асоб», для якіх трэба атрымаць адказ, ці падпісаны на іх карыстальнік ці не. Даўжыня спісу = прыкладна кол-у сяброў карыстача, прычым для паловы правяраемых сяброў адказ павінен быць "Так", а для іншай паловы - "Не". Праверка праводзіцца ў такім парадку, каб адказы «Так» і «Не» чаргаваліся (гэта значыць у кожным другім выпадку нам давядзецца перабіраць усе калонкі радка для варыянтаў 1 і 2). Агульны час праверкі затым падзяліць на колькасць сяброў, якія правяраюцца для атрымання сярэдняга часу на праверку аднаго суб'екта.
  • выдаленне дадзеных. Выдаліць у карыстальніка ўсіх сяброў. Прычым парадак выдалення - выпадковы (гэта значыць "змешваем" першапачатковы спіс, які выкарыстоўваўся для запісу дадзеных). Агульны час праверкі затым падзяліць на колькасць сяброў, якія выдаляюцца для атрымання сярэдняга часу на адну праверку.

Сцэнары неабходна прагнаць для кожнага з 5 варыянтаў мадэляў дадзеных і для розных памераў сацыяльнай сеткі, каб паглядзець, як мяняецца час з яе ростам. У рамках аднаго n сувязі ў сетцы і спіс карыстальнікаў для праверкі павінны быць, натуральна, аднолькавымі для ўсіх 5 варыянтаў.
Для лепшага разумення ніжэй прыводжу прыклад згенераваных дадзеных для n = 5. Напісаны "генератар" дае на выхадзе тры слоўніка ID-шнікаў:

  • першы - для ўстаўкі
  • другі - для праверкі
  • трэці - для выдалення

{0: [1], 1: [4, 5, 3, 2, 1], 2: [1, 2], 3: [2, 4, 1, 5, 3], 4: [2, 1]} # всего 15 друзей

{0: [1, 10800], 1: [5, 10800, 2, 10801, 4, 10802], 2: [1, 10800], 3: [3, 10800, 1, 10801, 5, 10802], 4: [2, 10800]} # всего 18 проверяемых субъектов

{0: [1], 1: [1, 3, 2, 5, 4], 2: [1, 2], 3: [4, 1, 2, 3, 5], 4: [1, 2]} # всего 15 друзей

Як можна заўважыць, усе ID, вялікія 10 000 у слоўніку для праверкі - гэта як раз тыя, якія загадзя дадуць адказ False. Устаўка, праверка і выдаленне "сяброў" вырабляюцца менавіта ў паказанай у слоўніку паслядоўнасці.

Эксперымент праводзіўся на наўтбуку пад кіраваннем Windows 10, дзе ў адным докер-кантэйнеры была запушчана база HBase, а ў іншым - Python з Jupyter Notebook. Докеру было выдзелена 2 ядры CPU і 2 Гб аператыўнай памяці. Уся логіка, як і эмуляцыі працы "умоўнага прыкладання", так і "абвязка" для генерацыі тэставых дадзеных і замеру часу былі напісаны на Python. Для працы з HBase выкарыстоўвалася бібліятэка happybase, для вылічэння хэшаў (MD5) для варыянту 5 — hashlib

З улікам вылічальнай магутнасці канкрэтнага наўтбука эксперыментальна быў абраны запуск для n = 10, 30, …. 170 - калі агульны час працы поўнага цыклу тэставання (усе сцэнары для ўсіх варыянтаў для ўсіх n) было яшчэ больш-менш разумным і змяшчалася падчас аднаго чаявання (у сярэднім 15 хвілін).

Тут неабходна зрабіць рэмарку, што ў дадзеным эксперыменце мы ў першую чаргу ацэньваем не абсалютныя лічбы прадукцыйнасці. Нават адноснае параўнанне розных двух варыянтаў можа быць не зусім карэктным. Цяпер нас цікавіць менавіта характар ​​змены часу ў залежнасці ад n, бо з улікам паказанай вышэй канфігурацыі «тэставага стэнда» атрымаць часавыя адзнакі, «вычышчаныя» ад уплыву выпадковых і іншых фактараў, вельмі складана (ды і такой задачы не ставілася).

Вынік эксперыменту

Першы тэст - як змяняецца час, які затрачваецца на запаўненне спісу сяброў. Вынік - на графіцы ніжэй.
Асаблівасці праектавання мадэлі даных для NoSQL
Варыянты 3-5 чакана паказваюць практычна канстантны час "бізнэс-аперацыі", якое не залежыць ад росту памеру сеткі і неадрозную розніцу ў прадукцыйнасці.
Варыянт 2 паказвае таксама канстантную, але крыху горшую прадукцыйнасць, прычым практычна роўна ў 2 разы адносна варыянтаў 3-5. І гэта не можа не цешыць, бо суадносіцца з тэорыяй – у гэтым варыянце колькасць аперацый уводу-высновы ў/з HBase як раз у 2 разу больш. Гэта можа служыць ускосным сведчаннем, што наш тэставы стэнд у прынцыпе дае нядрэнную дакладнасць.
Варыянт 1 гэтак жа чакана апыняецца самым павольным і дэманструе лінейны ад памеру сеткі рост часу, які затрачваецца на даданне адно аднаго.
Паглядзім зараз вынікі другога тэста.
Асаблівасці праектавання мадэлі даных для NoSQL
Варыянты 3-5 ізноў жа паводзіць сябе чакана - канстантны час, не якое залежыць ад памеру сеткі. Варыянты 1 і 2 дэманструюць лінейны рост часу пры росце памеру сеткі і падобную прадукцыйнасць. Прычым варыянт 2 апыняецца ледзь павольней па ўсёй бачнасці з-за неабходнасці вычыткі і апрацоўкі дадатковай калонкі count , што пры росце n становіцца больш прыкметным. Але я ўсё ж устрымаюся ад якіх-небудзь высноваў, бо дакладнасць дадзенага параўнання адносна невысокая. Акрамя таго, дадзеныя суадносіны (які варыянт, 1 ці 2, хутчэй) змяняліся ад запуску да запуску (пры гэтым захоўваючы характар ​​залежнасці і "ідучы ноздра ў ноздру").

Ну і апошні графік - вынік тэсціравання выдалення.

Асаблівасці праектавання мадэлі даных для NoSQL

Тут зноў жа без сюрпрызаў. Варыянты 3-5 ажыццяўляюць выдаленне за канстантнае час.
Прычым, што цікава, варыянты 4 і 5, у адрозненні ад папярэдніх сцэнараў, паказваюць прыкметную ледзь горшую прадукцыйнасць, чым варыянт 3. Па ўсёй бачнасці, аперацыя выдалення радка больш выдатковая, чым аперацыя выдалення калонкі, што ў цэлым лагічна.

Варыянты 1 і 2, чакана, дэманструюць лінейны рост часу. Пры гэтым варыянт 2 стабільна павольней варыянту 1 - з-за дадатковай аперацыі ўводу-вываду па «абслугоўванню» калонкі count.

Агульныя высновы эксперыменту:

  • Варыянты 3-5 дэманструюць большую эфектыўнасць, бо яны выкарыстоўвае перавагі HBase; пры гэтым іх прадукцыйнасць адрозніваецца сябар адносна сябра на канстанту і не залежыць ад памеру сеткі.
  • Розніца паміж варыянтамі 4 і 5 не была зафіксаваная. Але гэта не значыць, што варыянт 5 не трэба выкарыстоўваць. Цалкам верагодна, што выкарыстоўваны сцэнар эксперыменту з улікам ТТХ тэставага стэнда не дазволіў яе выявіць.
  • Характар ​​росту часу, неабходнага на выкананне "бізнэс-аперацый" з дадзенымі, у цэлым пацвердзіў атрыманыя раней тэарэтычныя выкладкі для ўсіх варыянтаў.

Эпілог

Праведзеныя грубіянскія эксперыменты не варта ўспрымаць як абсалютную ісціну. Ёсць мноства фактараў, якія не былі ўлічаныя і ўносілі скажэнні ў вынікі (асабліва добра гэтыя флуктуацыі бачныя на графіках пры невялікім памеры сеткі). Напрыклад, хуткасць працы thrift, які выкарыстоўваецца happybase, аб'ём і спосаб рэалізацыі логікі, якая ў мяне была напісана на Python (не бяруся сцвярджаць, што код быў напісаны аптымальна і эфектыўна выкарыстоўваў магчымасці ўсіх кампанентаў), магчыма асаблівасці кэшавання HBase, фонавая актыўнасць Windows 10 на маім ноўтбуку і да т.п. У цэлым можна лічыць, што ўсе тэарэтычныя выкладкі эксперыментальна паказалі сваю заможнасць. Ну ці як мінімум абвергнуць іх такім вось "наскокам у лоб" не атрымалася.

У зняволенні - рэкамендацыі ўсім, хто толькі пачынае праектаваць мадэлі дадзеных у HBase: абстрагуйцеся ад папярэдняга досведу працы з рэляцыйнымі базамі і падушыце «запаведзі»:

  • Праектуючы, ідзем ад задачы і шаблонаў маніпуляцыі з дадзенымі, а не ад мадэлі прадметнай вобласці
  • Эфектыўны доступ (без full table scan) - толькі па ключы
  • Дэнармалізацыя
  • Розныя радкі магу змяшчаць розныя калонкі
  • Дынамічны склад калонак

Крыніца: habr.com

Дадаць каментар