NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Да нядаўняга часу ў Аднакласніках каля 50 ТБ дадзеных, апрацоўваных у рэальным часе, захоўвалася ў SQL Server. Для такога аб'ёму забяспечыць хуткі і надзейны, ды яшчэ і ўстойлівы да адмовы ЦАД доступ, выкарыстаючы SQL СКБД, практычна немагчыма. Звычайна ў такіх выпадках выкарыстоўваюць адно з NoSQL-сховішчаў, але не ўсё можна перанесці ў NoSQL: некаторыя сутнасці патрабуюць гарантый ACID-транзакцый.

Гэта падвяло нас да выкарыстання NewSQL-сховішчы, гэта значыць СКБД, якая прадстаўляе адмоваўстойлівасць, маштабаванасць і хуткадзейнасць NoSQL-сістэм, але пры гэтым захоўвае звыклыя для класічных сістэм ACID-гарантыі. Якія працуюць прамысловых сістэм гэтага новага класа няшмат, таму мы рэалізавалі такую ​​сістэму самі і запусцілі яе ў прамысловую эксплуатацыю.

Як гэта працуе і што атрымалася - чытай пад катом.

Сёння штомесячная аўдыторыя "Аднакласнікаў" складае больш за 70 млн унікальных наведвальнікаў. Мы уваходзім у пяцёрку найбуйнейшых сацсетак свету, і ў дваццатку сайтаў, на якіх карыстачы праводзяць больш за ўсё часу. Інфраструктура «ОК» апрацоўвае вельмі высокія нагрузкі: больш за мільён HTTP-запытаў/сек на франты. Часткі парку сервераў у колькасці больш за 8000 штук размешчаны блізка адзін ад аднаго – у чатырох маскоўскіх дата-цэнтрах, што дазваляе забяспечваць сеткавую затрымку менш за 1 мс паміж імі.

Мы выкарыстоўваем Cassandra з 2010 года, пачынаючы з версіі 0.6. Сёння ў эксплуатацыі некалькі дзясяткаў кластараў. Самы хуткі кластар апрацоўвае больш за 4 млн аперацый у секунду, а найбуйнейшы захоўвае 260 Тб.

Аднак усё гэта звычайныя NoSQL-кластары, якія выкарыстоўваюцца для захоўвання слаба ўзгодненых дадзеных. Нам жа жадалася замяніць асноўнае кансістэнтнае сховішча, Microsoft SQL Server, якое выкарыстоўвалася з моманту падставы "Аднакласнікаў". Сховішча складалася з больш за 300 SQL Server Standard Edition машын, на якіх утрымоўвалася 50 Тб дадзеных – бізнэс-сутнасцяў. Гэтыя дадзеныя мадыфікуюцца ў рамках ACID-транзакцый і патрабуюць высокай узгодненасці.

Для размеркавання дадзеных па нодах SQL Server мы выкарыстоўвалі як вертыкальнае, так і гарызантальнае. партыцыянаванне (шардзіраванне). Гістарычна мы выкарыстоўвалі простую схему шардзіравання дадзеных: кожнай сутнасці супастаўляўся токен – функцыя ад ID сутнасці. Сутнасці з аднолькавым токенам змяшчаліся на адзін SQL-сервер. Стаўленне тыпу master-detail рэалізоўвалася так, каб токены асноўнага і спароджанага запісу заўсёды супадалі і знаходзіліся на адным серверы. У сацыяльнай сетцы амаль усе запісы спараджаюцца ад імя карыстальніка - значыць, усе дадзеныя карыстальніка ў межах адной функцыянальнай падсістэмы захоўваюцца на адным серверы. Гэта значыць, у бізнес-транзакцыі амаль заўсёды ўдзельнічалі табліцы аднаго SQL-сервера, што дазваляла забяспечваць узгодненасць дадзеных з дапамогай лакальных ACID-транзакцый, без неабходнасці выкарыстання. павольных і ненадзейных размеркаваных ACID-транзакцый.

Дзякуючы шардынгу і для паскарэння працы SQL:

  • Не выкарыстоўваем Foreign key constraints, бо пры шаліраванні ID сутнасці можа знаходзіцца на іншым серверы.
  • Не выкарыстоўваем захоўваемыя працэдуры і трыгеры з-за дадатковай нагрузкі на ЦПУ СКБД.
  • Не выкарыстоўваем JOINs з-за ўсяго вышэйпералічанага і мноства выпадковых чытанняў з дыска.
  • Па-за транзакцыяй для памяншэння ўзаемаблакіровак выкарыстоўваем узровень ізаляцыі Read Uncommitted.
  • Выконваем толькі кароткія транзакцыі (у сярэднім карацей 100 мс).
  • Не выкарыстоўваем шматрадныя UPDATE і DELETE з-за вялікай колькасці ўзаемаблакіровак - абнаўляем толькі па адным запісе.
  • Запыты заўсёды выконваем толькі па індэксах - запыт з планам поўнага прагляду табліцы для нас азначае перагрузку БД і яе адмову.

Гэтыя крокі дазволілі выціснуць з SQL-сервераў амаль максімум прадукцыйнасці. Аднак праблемаў рабілася ўсё больш і больш. Давайце іх разгледзім.

Праблемы з SQL

  • Паколькі мы выкарыстоўвалі самапісны шардынг, даданне новых шардаў выконвалася адміністратарамі ўручную. Увесь гэты час рэплікі дадзеных, якія маштабуюцца, не абслугоўвалі запыты.
  • Па меры росту колькасці запісаў у табліцы зніжаецца хуткасць устаўкі і мадыфікацыі, пры даданні індэксаў да існуючай табліцы хуткасць падае кратна, стварэнне і перастварэнне індэксаў ідзе з даунтаймам.
  • Наяўнасць у production невялікай колькасці Windows для SQL Server абцяжарвае кіраванне інфраструктурай

Але галоўная праблема -

адмоваўстойлівасць

У класічнага SQL-сервера дрэнная адмоваўстойлівасць. Дапушчальны, у вас усяго адзін сервер базы дадзеных, і ён адмаўляе раз у тры гады. У гэты час сайт не працуе 20 хвілін, гэта прымальна. Калі ў вас 64 серверы, то сайт не працуе ўжо раз на тры тыдні. А калі ў вас 200 сервераў, то сайт не працуе кожны тыдзень. Гэта праблема.

Што можна зрабіць для падвышэння адмоваўстойлівасці SQL-сервера? Вікіпедыя прапануе нам пабудаваць высокадаступны кластар: дзе ў выпадку адмовы любога з кампанентаў ёсць дублюючы.

Гэта патрабуе парку дарагога абсталявання: шматлікае дубліраванне, оптавалакно, сховішчы агульнага доступу, ды і ўключэнне рэзерву працуе ненадзейна: каля 10% уключэнняў заканчваюцца адмовай рэзервовай ноды паравозікам за асноўнай нодай.

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

Для гэтага можна было б прымяніць Multi-Master рэплікацыю, убудаваную ў SQL Server. Гэтае рашэнне моцна даражэй за кошт кошту софту і пакутуе ад добра вядомых праблем з рэплікацыяй - непрадказальных затрымак транзакцый пры сінхроннай рэплікацыі і затрымак ва ўжыванні рэплікацый (і, як следства, страчаных мадыфікацый) пры асінхроннай. Разумеецца ж ручное вырашэнне канфліктаў робіць гэты варыянт цалкам непрыдатным для нас.

Усе гэтыя праблемы патрабавалі кардынальнага рашэння і мы прыступілі да іх дэталёвага аналізу. Тут нам трэба пазнаёміцца ​​з тым, што ў асноўным робіць SQL Server - транзакцыямі.

Простая транзакцыя

Разгледзім найпростую, з пункта гледжання прыкладнага SQL-праграміста, транзакцыю: даданне фатаграфіі ў альбом. Альбомы і фатаграфіі захоўваюцца ў розных шыльдах. У альбома ёсць лічыльнік публічных фатаграфій. Тады такая транзакцыя разбіваецца на наступныя крокі:

  1. Блакуем альбом па ключы.
  2. Ствараем запіс у табліцы фатаграфій.
  3. Калі ў фатаграфіі публічны статус, то накручваем у альбоме лічыльнік публічных фатаграфій, абнаўляем запіс і каміцім транзакцыю.

Або ў выглядзе псеўдакода:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

Мы бачым, што самы распаўсюджаны сцэнар бізнэс транзакцыі - прачытаць дадзеныя з БД у памяць сервера прыкладанняў, нешта змяніць і захаваць новыя значэнні назад у БД. Звычайна ў такой транзакцыі мы абнаўляем некалькі сутнасцей, некалькі табліц.

Пры выкананні транзакцыі можа адбыцца канкурэнтнае мадыфікаванне тых жа самых даных з іншай сістэмы. Напрыклад, Антыспам можа вырашыць, што карыстач нейкі падазроны і таму ўсе фатаграфіі ў карыстача больш не павінны быць публічнымі, іх трэба адправіць на мадэрацыю, а значыць памяняць photo.status на нейкае іншае значэнне і адкруціць адпаведныя лічыльнікі. Відавочна, што калі дадзеная аперацыя будзе адбывацца без гарантый атамарнасці прымянення і ізаляцыі канкуруючых мадыфікацый, як у ACID, то вынік будзе не тым, што неабходна - ці лічыльнік фота будзе паказваць няправільнае значэнне, ці не ўсе фота адправяцца на мадэрацыю.

Падобнага кода, які маніпулюе з рознымі бізнес-сутнасцямі ў рамках адной транзакцыі, за ўвесь час існавання Аднакласнікаў напісана вельмі шмат. Па досведзе ж міграцый на NoSQL з Eventual Consistency мы ведаем, што самыя вялікія складанасці (і часовыя выдаткі) выклікае неабходнасць распрацоўваць код, накіраваны на падтрыманне ўзгодненасці дадзеных. Таму галоўным патрабаваннем да новага сховішча мы лічылі забеспячэнне для прыкладной логікі сапраўдных ACID-транзакцый.

Іншымі, не менш важнымі, патрабаваннямі былі:

  • Пры адмове дата-цэнтра павінны быць даступны і чытанне, і запіс у новае сховішча.
  • Захаванне бягучай хуткасці распрацоўкі. Гэта значыць пры працы з новым сховішчам колькасць кода павінна быць прыблізна тым жа самым, не павінна з'яўляцца неабходнасці дапісваць нешта ў сховішча, распрацоўваць алгарытмы дазволу канфліктаў, падтрыманні другасных азначнікаў і т.п.
  • Хуткасць працы новага сховішча павінна быць дастаткова высокай, як пры чытанні даных, так і пры апрацоўцы транзакцый, што эфектыўна азначала непрымяняльнасць акадэмічна строгіх, універсальных, але павольных рашэнняў, як, напрыклад, двухфазных комітаў.
  • Аўтаматычнае маштабаванне на лета.
  • Выкарыстанне звычайных танных сервераў, без неабходнасці пакупкі экзатычных жалязяк.
  • Магчымасць развіцця сховішчы сіламі распрацоўшчыкаў кампаніі. Іншымі словамі, прыярытэт аддаваўся сваім ці заснаваным на адчыненым кодзе рашэнням, пажадана на Java.

Рашэнні, рашэнні

Аналізуючы магчымыя рашэнні, мы прыйшлі да двух магчымых выбараў архітэктуры:

Першы - узяць любы SQL-сервер і рэалізаваць патрэбную адмоваўстойлівасць, механізм маштабавання, адмоваўстойлівасць кластар, дазвол канфліктаў і размеркаваныя, надзейныя і хуткія ACID-транзакцыі. Мы ацанілі гэты варыянт як вельмі нетрывіяльны і працаёмкі.

Другі варыянт - узяць гатовае NoSQL-сховішча з рэалізаваным маштабаваннем, адмоваўстойлівым кластарам, дазволам канфліктаў і рэалізаваць транзакцыі і SQL самім. На першы погляд нават задача рэалізацыі SQL, не кажучы ўжо аб ACID транзакцыях, выглядае задачкай на гады. Але потым мы зразумелі, што набор магчымасцяў SQL, які мы выкарыстоўваем на практыцы, далёкі ад ANSI SQL гэтак жа далёка, як Cassandra CQL далёкі ад ANSI SQL. Прыгледзеўшыся яшчэ больш уважліва да CQL, мы зразумелі, што ён дастаткова блізкі да таго, што нам трэба.

Cassandra і CQL

Дык вось, чым жа цікавая Cassandra, якімі магчымасцямі яна валодае?

Па-першае, тут можна ствараць табліцы з падтрымкай розных тыпаў дадзеных, можна рабіць SELECT ці UPDATE па першасным ключы.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

Для забеспячэння ўзгодненасці дадзеных рэплік, Cassandra выкарыстоўвае кворумны падыход. У найпростым выпадку гэта азначае, што пры размяшчэнні трох рэплік аднаго і таго ж шэрагу на розных нодах кластара, запіс лічыцца паспяховым, калі большасць нод (г.зн. дзве з трох) пацвердзілі паспяховасць гэтай аперацыі запісу. Дадзеныя шэрагу лічацца ўзгодненымі, калі пры чытанні большасць нод былі апытаны і пацвердзілі іх. Такім чынам, пры наяўнасці трох рэплік гарантуецца поўная і імгненная ўзгодненасць даных пры адмове адной ноды. Такі падыход дазволіў нам рэалізаваць яшчэ больш надзейную схему: заўсёды адпраўляць запыты на ўсе тры рэплікі, чакаючы адказу ад двух самых хуткіх. Запознены адказ трэцяй рэплікі ў такім выпадку адкідаецца. У запозненай з адказам ноды пры гэтым могуць быць сур'ёзныя праблемы - тормазы, зборка смецця ў JVM, direct memory reclaim у linux kernel, збой жалеза, адключэнне ад сеткі. Аднак на аперацыі кліента і на дадзеныя гэта ніяк не ўплывае.

Падыход, калі мы звяртаемся да трох нодаў, а атрымліваем адказ ад двух, называецца спекуляцыяй: запыт на лішнія рэплікі адпраўляецца яшчэ да таго, як "адваліцца".

Яшчэ адной з пераваг Cassandra з'яўляецца Batchlog – механізм, які гарантуе альбо поўнае прымяненне, альбо поўнае непрымяненне пакета ўносяцца вамі змен. Гэта дазваляе нам вырашыць A у ACID - атамарнасць са скрынкі.

Бліжэйшае да транзакцый у Cassandra.lightweight transactions“. Але ад "сапраўдных" ACID-транзакцый яны далёкія: насамрэч, гэта магчымасць зрабіць CAS на дадзеных толькі аднаго запісу, выкарыстоўваючы кансэнсус па цяжкавагавым пратаколе Paxos. Таму хуткасць такіх транзакцый невялікая.

Чаго нам не хапіла ў Cassandra

Такім чынам, нам трэба было рэалізаваць у Cassandra сапраўдныя ACID-транзакцыі. З выкарыстаннем якіх мы маглі б лёгка рэалізаваць дзве іншыя зручныя магчымасці класічных DBMS: кансістэнтныя хуткія індэксы, што дазволіла б нам выконваць выбаркі дадзеных не толькі па першасным ключы і звычайны генератар манатонных аўтаінкрэментных ID.

C*One

Так нарадзілася новая СКБД C*One, якая складаецца з трох тыпаў серверных нод:

  • Сховішчы – (амаль) стандартныя серверы Cassandra, якія адказваюць за захоўванне дадзеных на лакальных дысках. Па меры росту нагрузкі і аб'ёму дадзеных іх колькасць можна лёгка маштабаваць да дзясяткаў і сотняў.
  • Каардынатары транзакцый - забяспечваюць выкананне транзакцый.
  • Кліенты - серверы прыкладанняў, якія рэалізуюць бізнес-аперацыі і ініцыявальныя транзакцыі. Такіх кліентаў могуць быць тысячы.

NewSQL = NoSQL+ACID

Серверы ўсіх тыпаў складаюцца ў агульным кластары, выкарыстаюць унутраны пратакол паведамленняў Cassandra для зносін сябар з сябрам і плёткі для абмену кластарнай інфармацыяй. З дапамогай Heartbeat серверы даведаюцца аб узаемных адмовах, падтрымліваюць адзіную схему дадзеных - табліцы, іх структуру і рэплікацыю; схему партыцыянавання, тапалогію кластара, і да т.п.

Кліенты

NewSQL = NoSQL+ACID

Замест стандартных драйвераў выкарыстоўваецца рэжым Fat Сlient. Такая нода не захоўвае дадзеных, але можа выступаць у ролі каардынатара выканання запытаў, гэта значыць Кліент сам выконвае функцыю каардынатара сваіх запытаў: апытвае рэплікі сховішча і вырашае канфлікты. Гэта не толькі надзейней і хутчэй стандартнага драйвера, які патрабуе камунікацыі з выдаленым каардынатарам, але і дазваляе кіраваць перадачай запытаў. Па-за адчыненай на кліенце транзакцыі запыты накіроўваюцца ў сховішчы. Калі ж кліент адкрыў транзакцыю, то ўсе запыты ў рамках транзакцыі накіроўваюцца ў каардынатар транзакцый.
NewSQL = NoSQL+ACID

Каардынатар транзакцый C*One

Каардынатар - тое, што мы рэалізавалі для C * One з нуля. Ён адказвае за кіраванне транзакцыямі, блакіроўкамі і парадкам прымянення транзакцый.

Для кожнай абслугоўванай транзакцыі каардынатар генеруе часавую пазнаку: кожная наступная больш, чым у папярэдняй транзакцыі. Паколькі ў Cassandra сістэма дазволу канфліктаў заснавана на часавых пазнаках (з двух канфліктных запісаў актуальнай лічыцца з позняй часавай пазнакай), то канфлікт будзе заўсёды дазволены ў карысць наступнай транзакцыі. Такім чынам мы рэалізавалі гадзіннік Лэмпарта - танны спосаб вырашэння канфліктаў у размеркаванай сістэме.

Блакіроўкі

Для забеспячэння ізаляцыі мы вырашылі выкарыстаць самы просты спосаб - песімістычныя блакаванні па першасным ключы запісу. Іншымі словамі, у транзакцыі запіс неабходна спачатку заблакаваць, толькі затым прачытаць, мадыфікаваць і захаваць. Толькі пасля паспяховага комміта запіс можа быць разблакіраваны, каб канкуруючыя транзакцыі маглі яго выкарыстоўваць.

Рэалізацыя такой блакіроўкі простая ў неразмеркаваным асяроддзі. У размеркаванай сістэме ёсць два асноўных шляхі: альбо рэалізаваць размеркаваную блакіроўку на кластары, або размеркаваць транзакцыі так, каб транзакцыі з удзелам аднаго запісу заўсёды абслугоўваліся адным і тым жа каардынатарам.

Паколькі ў нашым выпадку дадзеныя ўжо размеркаваны па групах лакальных транзакцый у SQL, было вырашана замацаваць за каардынатарамі групы лакальных транзакцый: адзін каардынатар выконвае ўсе транзакцыі з токенам ад 0 да 9, другі - з токенам ад 10 да 19, і гэтак далей. У выніку кожны з экзэмпляраў каардынатара становіцца майстрам групы транзакцый.

Тады блакіроўкі могуць быць рэалізаваны ў выглядзе банальнага HashMap у памяці каардынатара.

Адмовы каардынатараў

Паколькі адзін каардынатар выключна абслугоўвае групу транзакцый, вельмі важна хутка вызначыць факт яго адмовы, каб паўторная спроба выканання транзакцыі ўклалася ў таймаўт. Каб гэта было хутка і надзейна, мы ўжылі паўнасувязны кворумны hearbeat пратакол:

У кожным дата-цэнтры размяшчаецца мінімум па дзве ноды каардынатара. Перыядычна кожны каардынатар рассылае heartbeat-паведамленне астатнім каардынатарам і паведамляе ім аб сваім функцыянаванні, а таксама аб тым, heartbeat-паведамленні ад якіх каардынатараў у кластары ён атрымліваў у апошні раз.

NewSQL = NoSQL+ACID

Атрымліваючы аналагічную інфармацыю ад астатніх у складзе іх heartbeat-паведамленняў, кожны каардынатар вырашае для сябе, якія ноды кластара функцыянуюць, а якія не, кіруючыся прынцыпам кворуму: калі нода Х атрымала ад большасці нод у кластары інфармацыю аб нармальным атрыманні паведамленняў з ноды Y, значыць , Y працуе. І наадварот, як толькі большасць паведаміць аб згубе паведамленняў з ноды Y, значыць, Y адмовіў. Цікаўна, што калі кворум паведаміць надзе Х, што не атрымлівае ад яе больш паведамленняў, значыць сама нода X будзе лічыць сябе якая адмовіла.

Heartbeat-паведамленні рассылаюцца з вялікай частатой, каля 20 раз у сек, з перыядам 50 мс. У Java складана гарантаваць водгук прыкладання на працягу 50 мс з-за параўнальнай працягласці паўзаў, выкліканых зборшчыкам смецця. Нам атрымалася дамагчыся такога часу водгуку з выкарыстаннем зборшчыка смецця G1, які дазваляе паказаць мэту па працягласці паўзаў GC. Аднак, часам, дастаткова рэдка, паўзы зборшчыка выходзяць за рамкі 50 мс, што можа прывесці да ілжывага выяўлення адмовы. Каб такога не было, каардынатар не паведамляе аб адмове выдаленай ноды пры згубе першага ж heartbeat-паведамленні ад яе, толькі калі знікла некалькі запар. Так нам атрымалася дамагчыся выяўленні адмовы ноды каардынатара за 200 мс.

Але мала хутка зразумець, якая нода перастала функцыянаваць. Трэба нешта рабіць з гэтым.

Рэзерваванне

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

NewSQL = NoSQL+ACID

Дапушчальны, мы жадаем выканаць транзакцыю ў групе 50. Загадзя вызначым схему замяшчэння, гэта значыць якія ноды будуць выконваць транзакцыі 50 групы ў выпадку адмовы асноўнага каардынатара. Наша мэта - захаваць працаздольнасць сістэмы пры адмове дата-цэнтра. Вызначым, што першым рэзервам будзе нода з іншага дата-цэнтра, а другім рэзервам - нода з трэцяга. Гэтая схема выбіраецца адзін раз і не мяняецца да таго часу, пакуль не памяняецца тапалогія кластара, гэта значыць пакуль у яго не ўвойдуць новыя ноды (што здараецца вельмі рэдка). Парадак выбару новага актыўнага майстра пры адмове старога будзе заўсёды такім: актыўным майстрам стане першы рэзерв, а калі і ён перастаў функцыянаваць - другі рэзерв.

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

Але як кліенты зразумеюць, які з майстроў зараз працуе? За 50 мс немагчыма разаслаць інфармацыю на тысячы кліентаў. Магчымая сітуацыя, калі кліент адпраўляе запыт на адкрыццё транзакцыі, яшчэ не ведаючы, што гэты майстар ужо не функцыянуе, і запыт завісне на таймаўце. Каб гэтага не здарылася, кліенты спекулятыўна дасылаюць запыт на адкрыццё транзакцыі адразу майстру групы і абодвум яго рэзервам, але адкажа на гэты запыт толькі той, хто з'яўляецца актыўным майстрам у дадзены момант. Усю наступную камунікацыю ў рамках транзакцыі кліент будзе праводзіць толькі з актыўным майстрам.

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

Як працуе транзакцыя

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

NewSQL = NoSQL+ACID

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

NewSQL = NoSQL+ACID

Калі кліент запытвае ў рамках актыўнай транзакцыі ўласныя змененыя дадзеныя, то каардынатар дзейнічае так:

  • калі ID ужо ёсць у транзакцыі, то дадзеныя бяруцца з памяці;
  • калі ID у памяці няма, то якія адсутнічаюць дадзеныя счытваюцца з нод-сховішчаў, аб'ядноўваюцца з ужо наяўнымі ў памяці, і вынік аддаецца кліенту.

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

NewSQL = NoSQL+ACID

Калі кліент дасылае commit, стан, якое было ў памяці ў сэрвісу, захоўваецца каардынатарам у logged batch, і ўжо ў выглядзе logged batch адпраўляецца ў сховішчы Cassandra. Сховішчы робяць усё неабходнае, каб гэты пакет быў атамарна (цалкам) ужыты, і вяртаюць адказ каардынатару, а той вызваляе блакаванні і пацвярджае паспяховасць транзакцыі кліенту.

NewSQL = NoSQL+ACID

А для адкату каардынатару дастаткова толькі вызваліць памяць, занятую станам транзакцыі.

У выніку вышэйапісаных дапрацовак мы рэалізавалі прынцыпы ACID:

  • Атамарнасць. Гэта гарантыя таго, што ніякая транзакцыя не будзе зафіксаваная ў сістэме часткова, будуць альбо выкананы ўсе яе падаперацыі, альбо не выканана ніводнай. У нас гэты прынцып выконваецца за кошт logged batch у Cassandra.
  • ўзгодненасць. Кожная паспяховая транзакцыя па вызначэнні фіксуе толькі дапушчальныя вынікі. Калі пасля адкрыцця транзакцыі і выканання часткі аперацый выяўляецца, што вынік недапушчальны, выконваецца адкат.
  • Ізаляванасць. Пры выкананні транзакцыі паралельныя транзакцыі не павінны ўплываць на яе рэзультат. Канкуруючыя транзакцыі ізаляваны з дапамогай песімістычных блакіровак на каардынатары. Для чытанняў па-за транзакцыяй выконваецца прынцып ізаляванасці на ўзроўні Read Committed.
  • ўстойлівасць. Незалежна ад праблем на ніжніх узроўнях - абясточванне сістэмы, збой у абсталяванні, - змены, зробленыя паспяхова завершанай транзакцыяй, павінны застацца захаванымі пасля аднаўлення функцыянавання.

Чытанне па індэксах

Возьмем простую табліцу:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

У яе ёсць ID (першасны ключ), уладальнік і дата змены. Трэба зрабіць вельмі просты запыт - выбраць дадзеныя па ўладальніку з датай змены «за апошнія суткі».

SELECT *
WHERE owner=?
AND modified>?

Каб падобны запыт адпрацоўваў хутка, у класічнай SQL СКБД трэба пабудаваць азначнік па калонках (owner, modified). Падобнае мы можам зрабіць дастаткова проста, так як зараз у нас ёсць гарантыі ACID!

Індэксы ў C*One

Ёсць зыходная табліца з фатаграфіямі, у якой ID запісу з'яўляецца першасным ключом.

NewSQL = NoSQL+ACID

Для азначніка C*One стварае новую табліцу, якая з'яўляецца копіяй зыходнай. Ключ супадае з індэксным выразам, пры гэтым у яго ўваходзіць яшчэ і першасны ключ запісу з зыходнай табліцы:

NewSQL = NoSQL+ACID

Цяпер запыт па "ўладальніку за апошнія суткі" можна перапісаць як select з іншай табліцы:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

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

C дапамогай ACID нам удалося рэалізаваць індэксы "як у SQL". Яны валодаюць узгодненасцю, могуць маштабавацца, хутка працуюць, могуць быць састаўнымі і ўбудаваны ў мову запытаў CQL. Для падтрымкі азначнікаў не трэба ўносіць змены ў прыкладны код. Усё проста, як у SQL. І што найважнейшае, азначнікі не ўплываюць на хуткасць выканання мадыфікацый зыходнай табліцы транзакцый.

Што атрымалася

Мы распрацавалі C*One тры гады таму і запусцілі ў прамысловую эксплуатацыю.

Што ж мы атрымалі ў выніку? Давайце ацэнім гэта на прыкладзе падсістэмы апрацоўкі і захоўванні фатаграфій, аднаго з найважнейшых тыпаў дадзеных у сацыяльнай сетцы. Гаворка не аб саміх целах фатаграфій, а аб разнастайнай метаінфармацыі. Цяпер у "Аднакласніках" каля 20 млрд такіх запісаў, сістэма апрацоўвае 80 тыс. запытаў на чытанне ў секунду, да 8 тыс. ACID-транзакцый у секунду, звязаных з мадыфікацыяй дадзеных.

Калі мы выкарыстоўвалі SQL з replication factor = 1 (але ў RAID 10), метаінфармацыя фатаграфій захоўвалася на высокадаступным кластары з 32 машын з Microsoft SQL Server (плюс 11 рэзервовых). Таксама было выдзелена 10 сервераў для захоўвання бэкапаў. Разам 50 дарагіх машын. Пры гэтым сістэма працавала на намінальнай нагрузцы, без запасу.

Пасля мігравання на новую сістэму мы атрымалі replication factor = 3 - па копіі ў кожным дата-цэнтры. Сістэма складаецца з 63 нод сховішчы Cassandra і 6 машын каардынатараў, разам 69 сервераў. Але гэтыя машыны значна танней, іх агульны кошт складае каля 30% кошту сістэмы на SQL. Пры гэтым нагрузка трымаецца на ўзроўні 30%.

З выкарыстаннем C * One знізіліся і затрымкі: у SQL аперацыя запісу займала каля 4,5 мс. У C * One - каля 1,6 мс. Працягласць транзакцыі ў сярэднім менш за 40 мс, коміт выконваецца за 2 мс, працягласць чытання і запісы - у сярэднім 2 мс. 99-й перцентиль - усяго 3-3,1 мс, колькасць таймаўтаў знізілася ў 100 разоў - усё за кошт шырокага прымянення спекуляцый.

Да бягучага моманту з эксплуатацыі выведзена большая частка нод SQL Server, новыя прадукты распрацоўваюцца толькі c выкарыстаннем C * One. Мы адаптавалі C*One для працы ў нашым воблаку one-cloud, што дазволіла паскорыць разгортванне новых кластараў, спрасціць канфігурацыю і аўтаматызаваць эксплуатацыю. Без зыходнага кода гэта зрабіць было б значна складаней і мыліцай.

Цяпер мы працуем над пераводам іншых нашых сховішчаў у воблака - але гэта ўжо зусім іншая гісторыя.

Крыніца: habr.com

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