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 з Кінцева послідовність ми знаємо, що найбільші складнощі (і тимчасові витрати) викликає необхідність розробляти код, спрямований на підтримку узгодженості даних. Тому головною вимогою нового сховища ми вважали забезпечення для прикладної логіки справжніх 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 підтримується координатором автоматично. З однієї лише схеми даних при отриманні зміни координатор генерує і запам'ятовує зміна як основний таблиці, а й зміни копій. Жодних додаткових дій із таблицею індексу не виконується, логи не зчитуються, блокування не використовуються. Тобто додавання індексів майже не споживає ресурсів і практично не впливає на швидкість застосування модифікацій.

За допомогою 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*One. Ми адаптували C*One для роботи в нашій хмарі one-cloud, що дозволило прискорити розгортання нових кластерів, спростити конфігурацію та автоматизувати експлуатацію. Без вихідного коду це зробити було б значно складніше і милицею.

Зараз ми працюємо над переведенням інших наших сховищ у хмару, але це вже зовсім інша історія.

Джерело: habr.com

Додати коментар або відгук