Історія успіху «Яндекс.Пошти» з PostgreSQL

Володимир Бородін, системний адміністратор групи експлуатації систем зберігання даних в «Яндекс.Поштою», знайомить зі складнощами міграції великого проекту з Oracle Database на PostgreSQL. Це — розшифровка доповіді конференції HighLoad++ 2016.

Всім привіт! Мене звуть Вова, сьогодні я буду розповідати про бази даних «Яндекс.Пошти».

Спочатку кілька фактів, які будуть мати значення в майбутньому. «Яндекс.Пошта» — сервіс досить старий: він був запущений в 2000 році, і тому ми накопичили багато legacy. У нас — як це прийнято і модно говорити — цілком собі highload-сервіс, більше 10 мільйонів користувачів на добу, якісь сотні мільйонів. У бекенд нас прилітає більше 200 тисяч запитів в секунду в піке. Ми складаємо більше 150 мільйонів листів на добу, що пройшли перевірки на спам та віруси. Сумарний обсяг листів за всі 16 років — більше 20 петабайт.

Про що піде мова? Про те, як ми перевезли метадані з Oracle в PostgreSQL. Метаданих там не петабайты — їх трохи більше трьохсот терабайт. В бази влітає понад 250 тисяч запитів в секунду. Треба мати на увазі, що це маленькі OLTP-запити, здебільшого читання (80%).

Це — не перша наша спроба позбутися від Oracle. На початку нульових була спроба переїхати на MySQL, вона провалилася. В 2007 або 2008 була спроба написати щось своє, вона теж провалилася. В обох випадках був провал не стільки технічно причин, скільки з організаційних.

Що є метаданими? Тут вони виділені стрілочками. Це папки, які являють собою якусь ієрархію з лічильниками, мітки (теж, по суті, списки з лічильниками), складальники, треди і, звичайно ж, листи.



Ми не зберігаємо самі листи в метабазах, тіла листів знаходяться в окремому сховищі. У метабазах ми зберігаємо конверти. Конверти — це деякі поштові заголовки: від кого, кому, тема листа, дата і подібні речі. Ми зберігаємо інформацію про вкладення і ланцюжках листів.



Назад на 2012 рік
Все це лежало в Oracle. У нас було дуже багато логіки в самій збереженої базі. Оракловые бази були найефективнішим залізом по утилізації: ми складали дуже багато даних на шард, більше 10 терабайт. Умовно кажучи, при 30 ядрах у нас нормальний робочий load average був 100. Це не коли все погано, а при штатному режимі роботи.

Баз було мало, тому багато чого робилося руками, без автоматизації. Було багато ручних операцій. Для економії ми ділили бази на «теплі» (75%) і «холодні» (25%). «Теплі» — це для активних користувачів, вони з SSD. «Холодні» — для неактивних користувачів, с SATA.

Шардирование і відмовостійкість — важлива тема в «Яндексі». Шардирование — тому що в один шард все не запихаешь, а відмовостійкість — тому що ми регулярно беремо і відключаємо один з наших дата-центрів, щоб побачити, що все працює.

Як це було реалізовано? У нас є внутрішній сервіс BlackBox (чорний ящик). Коли один запит прилітає на один з наших бэкендов, бекенд обмінює аутентифікаційні дані — логін, пароль, cookie, token або щось подібне. Він йде з цим BlackBox, який у разі успіху повертає йому ідентифікатор і ім'я шарда.



Потім бекенд згодовував це ім'я шарда в оракловый драйвер OCCI, далі всередині цього драйвера була реалізована вся логіка відмовостійкості. Тобто, грубо кажучи, в спеціальному файлику /etc/tnsnames.ora були записані shardname і список хостів, які в цей шард входять, його обслуговують. Oracle сам вирішував, хто з них майстер, хто репліка, хто живий, а хто мертвий і т. д. Разом, шардирование було реалізовано засобами зовнішнього сервісу, а відмовостійкість — засобами драйвера Oracle.

Велика частина бэкендов була написана на C++. Для того, щоб не плодити «велосипедів», у них тривалий час існувала загальна абстракція macs meta access. Це просто абстракція для ходіння бази. Практично весь час у неї була одна реалізація macs_ora для ходіння безпосередньо в Oracle. В самому низу, звичайно ж, OCCI. Ще була невелика прошарок dbpool, яка реалізовувала пул з'єднання.



Ось так це коли-то давно замислювалося, дизайнилось і реалізовувалося. З плином часу абстракції протекли, бэкенды почали використовувати методи реалізації macs_ora, ще гірше, якщо з dbpool. З'явилися Java і всякі інші бэкенды, які не могли використовувати цю бібліотеку. Всю цю локшину потім довелося болісно розгрібати.

Oracle — прекрасна база даних, але і з нею були проблеми. Наприклад, викладка PL/SQL коду — це біль, тому що є library cache. Якщо база під навантаженням, то не можна просто взяти і оновити код функції, який зараз використовується якимись сесіями.

Інші проблеми пов'язані не стільки з Oracle, скільки з тим підходом, який ми використовували. Знову ж таки: безліч ручних операцій. Перемикання майстрів, наливка нових баз, запуск переносів користувачів — все робилося руками, бо баз було небагато.

З точки зору розробки є недолік в тому, що плюсовий [C++] оракловый драйвер має тільки синхронний інтерфейс. Тобто нормальний асинхронний бекенд поверх написати не вийде. Це викликало деяку біль в розробці. Другу біль у розробці викликало те, що підняти тестову базу проблематично. По-перше, тому що руками, по-друге, тому що це гроші.

Хто б що ні говорив, підтримка у Oracle є. Хоча підтримка enterprise-компаній часто далека від ідеалу. Але головна причина переходу — це гроші. Oracle коштує дорого.

Хронологія
В жовтні 2012 року, більше 4 років тому, було прийнято рішення про те, що ми повинні позбутися від Oracle. Не звучало слів PostgreSQL, не звучало жодних технічних подробиць — це було суто політичне рішення: позбутися, термін в 3 роки.

Через півроку ми почали перші експерименти. На що були витрачені ці півроку, я можу трохи пізніше розповісти. Ці півроку були важливі. Ми експериментували з PostgreSQL. Тоді був дуже модний тренд на всякі NoSQL-рішення, і ми спробували багато всякого різного. Ми згадали, що у нас всі метадані вже зберігаються в бэкенде пошуку по пошті, і може бути, можна використовувати його. Це рішення теж спробували.

Перший успішний експеримент зі збирачами, про нього я розповідав на митапе в «Яндексі» в 2014 році.

Ми взяли невеликий шматочок (2 терабайта) досить навантажених (40 тисяч запитів в секунду) поштових метаданих і забрали їх з Oracle в PostgreSQL. Той шматочок, який не дуже пов'язаний з основними метаданими. У нас вийшло, і нам сподобалося. Ми вирішили, що PostgreSQL — наш вибір.

Далі ми запилили прототип поштового схеми вже для PostgreSQL і почали складати в нього весь потік листів. Робили ми це асинхронно: всі 150 мільйонів листів в день ми складали в PostgreSQL. Якщо покладка не вдалася б, то нам було б все одно. Це був чистий експеримент, продакшн він не зачіпав.

Це дозволило нам перевірити первинні гіпотези зі схемою. Коли є дані, які не шкода викинути — це дуже зручно. Зробив якусь схему, напихав у неї листів, побачив, що не працює, всі дропнул і почав заново. Відмінні дані, які можна дропан, ми любимо.

Також завдяки цьому вийшло в деякій мірі провести стрес-тестування прямо під живий навантаженням, а не якийсь синтетикою, не на окремих стендах. Так вийшло зробити попередні прикидки по залізу, яке знадобиться для PostgreSQL. І звичайно ж, досвід. Основна мета попереднього експерименту і прототипу — це досвід.

Далі розпочалася основна робота. Розробка зайняла приблизно рік календарного часу. Задовго до того, як вона закінчилася, ми перенесли з Oracle в PostgreSQL свої ящики. Ми завжди розуміли, що ніколи не буде такого, що ми всім покажемо на одну ніч «вибачте, технічні роботи», перенесемо 300 терабайт і почнемо працювати на PostgreSQL. Так не буває. Ми б обов'язково зламалися, відкочувалися, і все було б погано. Ми розуміли, що буде досить тривалий період часу, коли частина ящиків буде жити в Oracle, а частина — в PostgreSQL, буде йти повільна міграція.

Влітку 2015 року ми перенесли свої ящики. Команда «Пошти», яка її пише, тестує, админит і так далі, перенесла свої ящики. Це дуже сильно прискорило розробку. Страждає абстрактний Вася, чи страждаєш ти, але можеш це поправити, — це дві різні речі.

Навіть до того, як ми дописали і реалізували всі фічі, ми почали нести неактивних користувачів. Неактивним ми називаємо такого користувача, якому пошта приходить, ми складаємо листи, але він їх не читає: ні вебом, ні з мобільним, ні IMAP — вони йому нецікаві. Є такі користувачі, до нещастя. Ми почали нести таких неактивних користувачів, коли у нас, припустимо, ще не повністю був реалізований IMAP, або не працювала половина ручок в мобільному додатку.

Але це не тому, що ми такі сміливі і вирішили всім ящики зламати, а тому що у нас був план Б у вигляді перенесення, і він нам дуже сильно допоміг. Була навіть автоматизація. Якщо ми перенесли користувача, і він раптом спробував, наприклад, зайти у веб інтерфейс — прокинувся і став активним — ми назад переносили його в Oracle, щоб не ламати йому ніякі фічі. Це дозволило нам поправити купу багів у коді трансферу.

Потім послідувала міграція. Ось кілька цікавих фактів. 10 людино-років ми витратили на те, щоб переписати всю нашу локшину, яку ми накопичили за 12-15 років.

Сама міграція при цьому пройшла дуже швидко. Це графік за 4 місяці. Кожна лінія — це відсоток навантаження, який сервіс віддає з PostgreSQL. Розбито на сервіси: IMAP, web, POP3, покладка, мобільні і так далі.



На жаль, перестрибнути прірву на 95% не можна. Ми не змогли всіх перенести до квітня, тому що реєстрація залишалася в Oracle, це досить складний механізм. Вийшло так, що ми реєстрували нових користувачів Oracle і відразу ж ночі їх переносили в PostgreSQL. У травні ми запилили реєстрацію, і в липні вже погасили всі бази даних Oracle.

Основні зміни
У нашій абстракції з'явилася ще одна реалізація macs_pg, і ми розплутали всю локшину. Всі ті протекшие абстракції довелося акуратно переписати. Внизу у неї libpq, зробили ще невеликий прошарок apq, де реалізовано пул з'єднань, таймаут, обробка помилок, і все це асинхронно.



Шардирование і відмовостійкість — все те ж саме. Бекенд отримує аутентифікаційні дані від користувача, обмінює їх на BlackBox ідентифікатор і ім'я шарда. Якщо в імені шарда є буква pg, то далі він робить ще один запит у новий сервіс, який ми назвали Sharpei. Бекенд передає туди ідентифікатор користувача і режим, в якому він хоче отримати базу. Наприклад, «я хочу майстер», «я хочу синхронну репліку» або «я хочу найближчий хост». Sharpei повертає йому рядки підключення. Далі бекенд відкриває з'єднання, його тримає і використовує.

Щоб знати інформацію, хто майстер, хто репліка, хто живий, а хто мертвий, хто відстав, хто ні, Sharpei разів в секунду ходить в кінцеві бази та запитує їх статуси. У цьому місці з'явився компонент, який взяв на себе обидві функції: і шардирования, і відмовостійкості.



В плані заліза ми зробили кілька змін. Оскільки Oracle ліцензується за процесорних ядер, ми були змушені масштабуватися вертикально. На одне процесорне ядро ми напихали багато пам'яті, багато SSD-дисків. Була невелика кількість баз з невеликою кількістю процесорних ядер, але з величезними масивами пам'яті і дисків. У нас завжди була строго одна репліка для відмовостійкості, тому що всі наступні — це гроші.

В PostgreSQL ми змінили підхід. Ми стали робити бази поменше і по дві репліки в кожному шарде. Це дозволило нам загортати читають навантаження на репліки. Тобто в Oracle всі обслуговувалося з майстра, а в PostgreSQL — три машини замість двох поменше, і читання загортаємо в PostgreSQL. У випадку з Oracle ми масштабувалися вертикально, у випадку з PostgreSQL масштабируемся горизонтально.



Крім «теплих» і «холодних» баз з'явилися ще й «гарячі». Чому? Тому що ми несподівано виявили, що 2% активних користувачів створюють 50% навантаження. Є такі нехороші користувачі, які нас ґвалтують. Під них ми зробили окремі бази. Вони мало чим відрізняються від теплих, там теж SSD, але їх менше на одне процесорне ядро, тому що процесор там активніше використовується.

Зрозуміло, ми запилили автоматизацію перенесення користувачів між шардами. Наприклад, якщо користувач неактивний, зараз живе в саташной [з SATA-накопичувачем] базі і раптом почав використовувати IMAP, ми перенесемо його в «теплу» базу. Або якщо він в теплій базі півроку не ворушиться, то ми перенесемо його в «холодну».

Переміщення старих листів активних користувачів з SSD на SATA — це те, що ми дуже хочемо зробити, але поки не можемо. Якщо ти активний користувач, живеш на SSD і в тебе 10 мільйонів листів, вони всі лежать на SSD, що не дуже ефективно. Але поки що в PostgreSQL нормального секціонування немає.

Ми поміняли всі ідентифікатори. У випадку з Oracle вони були все глобально-унікальними. У нас була окрема база, де було написано, що в цьому шарде такі діапазони, в цьому — такі. Зрозуміло, у нас був факап, коли в силу помилки перетнулися ідентифікатори, а на їх унікальність була зосереджена приблизно половина всього. Це було боляче.

У випадку з PostgreSQL ми вирішили перейти до нової схеми, коли у нас унікальні ідентифікатори в межах одного користувача. Якщо раніше унікальним був mid ідентифікатор листа, то тепер унікальною є пара uid mid. У всіх табличках у нас першим полем uid, їм все префиксивано, він є частиною поки скрізь.

Крім того, що це менше місця, є ще один неочевидний плюс. Оскільки всі ці ідентифікатори беруться з сиквенсов, у нас менше конкуренція за останню сторінку індексу. В Oracle ми для вирішення цієї проблеми вкрячивали реверсивні індекси. У разі PostgreSQL так як вставки йдуть в різні сторінки індексу, ми використовуємо звичайні B-Tree, і у нас є trench-скани, всі дані одного користувача в індексі лежать поруч. Це дуже зручно.

Ми ввели ревізії для всіх об'єктів. Це дозволило читати з реплік, по-перше, застарілі дані, по-друге, инкрементальные оновлення для IMAP, мобільних. Тобто відповідь на запитання «що змінилося в цій папці з такою-то ревізії» за рахунок цього сильно спростився.

В PostgreSQL все добре з масивами, композитами. Ми зробили денормализацию частини даних. Ось один з прикладів:



Це наша основна табличка mail.box. Вона містить по рядку на кожен лист. Первинним ключем у неї є пара uid mid. Ще там є масив міток lids, тому що на одному листі може бути більше однієї позначки. При цьому є завдання відповідати на питання «дай мені всі листи з такою-то міткою». Очевидно, що для цього потрібен якийсь індекс. Якщо побудувати B-Tree індекс масиву, то він не буде відповідати на таке питання. Для цього у нас використовується хитрий функціональний індекс gin по полю uid і lids. Він дозволяє нам відповідати на питання «дай мені всі листи такого-то з такими-то мітками або з такою-то міткою».

Збережена логіка
  • Оскільки з Oracle було дуже багато болю з збереженої логікою, ми зареклися що в PostgreSQL збереженої логіки не буде взагалі ніякої. Але в процесі наших експериментів і прототипів ми зрозуміли, що PL/pgSQL дуже навіть хороший. Він не страждає проблемою з library cache, і ми не знайшли інших сильно критичних проблем.
  • При цьому кількість логіки сильно скоротили, залишили тільки ту, що потрібна для логічної цілісності даних. Наприклад, якщо ви кладете лист, то збільшити лічильник в табличці з папками.
  • Оскільки немає undo, ціна помилки стала сильно вище. У undo ми залазили пару раз після викладення поганого коду, про це мій колега Олександр робив окремий доповідь у нас на митапе.
  • З-за відсутності library cache воно сильно простіше деплоится. Ми катаємося по пару раз в тиждень замість разу на квартал, як було раніше.
Підхід до обслуговування
  • Оскільки ми поміняли апаратне забезпечення і стали масштабуватися горизонтально, то і підхід до обслуговую баз ми поміняли. Базами тепер ми рухаємось SaltStack. Найголовніша його кілер-фіча для нас — це можливість бачити детальний diff між тим, що зараз є на базі, і тим, що ми від неї очікуємо. Якщо спостережуване влаштовує, то людина натискає кнопку «викотити», і воно котиться.
  • Схему та код ми змінюємо через міграції. У нас був окремий доповідь про це.
  • Від ручного обслуговування ми пішли, все, що можна, автоматизували. Перемикання майстрів, переноси користувачів, наливки нових шардов і так далі — все це по кнопці і дуже просто.
  • Оскільки розгорнути нову базу — це одна кнопка, ми отримали репрезентативні тестові оточення для розробки. Кожному розробнику по базі, по дві, скільки захоче — це дуже зручно.
Проблеми
Такі речі не проходять гладко ніколи.

Це список тредів в ком'юніті з проблемами, які ми самостійно вирішити не змогли.

  • Problem with ExclusiveLock on inserts
  • Checkpoint distribution
  • ExclusiveLock on extension of relation with huge shared_buffers
  • Hanging startup process on the replica after vacuuming on master
  • Replication slots isolation and levels
  • Segfault in BackendIdGetTransactions
Тобто ми пішли в ком'юніті, і нам допомогли. Це була перевірка, що робити, коли в тебе немає enterprise-підтримки: є ком'юніті, і воно працює. І це дуже здорово. Зрозуміло, сильно більше проблем ми вирішили самі.

Наприклад, у нас була дуже популярна ось такий жарт: «У будь незрозумілої ситуації винен autovacuum». Ці проблеми ми теж порішали.

Нам дуже не вистачало способів діагностики PostgreSQL. Хлопці з Postgres Pro запилили нам веб інтерфейс. Про це я вже розповідав на PG Day в 2015 році Пітері. Там можна почитати, як це працює. З допомогою хлопців з Postgres Pro і EnterpriseDB воно увійшло в ядро 9.6. Не всі, але якась частина цих напрацювань увійшла в 9.6. Далі ця функціональність буде поліпшуватися. У 9.6 з'явилися стовпці, які дозволяють сильно краще розуміти, що відбувається в базі.

Сюрприз. Ми зіткнулися з проблемою з бэкапами. У нас recovery window 7 днів, тобто ми повинні мати можливість відновитися на будь-який момент у минулому за останні 7 днів. В Oracle розмір місця під всі бекапи і архивлоги був дорівнює приблизно розміру бази. База 15 терабайт — і її бекап за 7 днів займає 15 терабайт.

В PostgreSQL ми використовуємо barman, і в ньому під бекапи потрібно місця мінімум в 5 разів більше, ніж розмір бази. Тому що WAL стискаються, а бекапи немає, там є File-level increments, які толком не працюють, взагалі все однопотоковий і дуже повільне. Якщо б ми бэкапили as is ці 300 терабайт мета-даних, у нас знадобилося б приблизно 2 петабайти під бекапи. Нагадаю, всього сховище «Пошти» — 20 петабайт. Тобто, 10% ми повинні були б відрізати тільки під бекапи мета-баз за останні 7 днів, що досить поганий план.

Ми не придумали нічого кращого і запатчили barman, ось pull request. Вже майже рік минув, як ми їх просимо запив цю кілер-фічу, а вони просять нас грошей, щоб замержить її. Дуже нахабні хлопці. Мій колега Євген, який все це і запив, розповідав про це на PGday в 2016 році. Воно правда сильно краще тисне бекапи, прискорює їх, там чесні инкременты.

З досвіду експерименту, прототипу, інших баз, які до того часу з'явилися у нас на PostgreSQL, ми очікували купу граблів під час перенесення. А їх не було. Було багато проблем, але з PostgreSQL вони пов'язані не були, що було для нас дивно. Було повно проблем з даними, тому що за 10 років накопичилося багато всякого legacy. Раптово виявилося, що в якихось базах дані лежать в кодуванні KOI8-R, або інші дивні речі. Зрозуміло, були помилки в логіці перенесення, тому дані теж доводилося лагодити.

Завершення
Є речі, яких нам дуже не вистачає в PostgreSQL.

Наприклад, секционирование, щоб рухати старі дані з SSD на SATA. Нам не вистачає хорошого вбудованого recovery manager, щоб не використовувати форк batman, тому що до ядра barman це, напевно, не доїде ніколи. Ми вже втомилися: майже рік їх штовхаємо, а вони не дуже-то поспішають. Здається, це має бути не в стороні від PostgreSQL, а саме в ядрі.

Ми будемо розвивати wait-інтерфейс. Думаю, у 10-й версії трапиться quourum commit, там патч в хорошому стані. Ще ми дуже хочемо нормальну роботу з диском. У плані дискового I/O PostgreSQL сильно програє Oracle.

Що в підсумку? Якщо враховувати рейди-репліки, то у нас в PostgreSQL більше 1 петабайт. Нещодавно я вважав, там трохи більше 500 мільярдів рядків. Туди влітає 250 тисяч запитів в секунду. Всього у нас це зайняло 3 календарних роки, але ми витратили більше 10 людино-років. Тобто зусилля всієї команди досить значна.

Що ми отримали? Став швидше деплой, незважаючи на те, що баз стало значно більше, а число DBA зменшилася. DBA на цей проект зараз менше, ніж коли був Oracle.

Хотіли того чи ні, але нам довелося порефакторить весь код бекенду. Все те legacy, яке накопичувалося роками, выпилилось. Наш код зараз чистіше, і це дуже добре.

Без дьогтю не буває. У нас зараз в 3 рази більше заліза під PostgreSQL, але це ніщо порівняно з вартістю Oracle. Поки у нас не було великих факапов.

Невелике зауваження від мене. В «Пошті» ми використовуємо багато open source бібліотек, проектів і готових рішень. До трьох стільців, на яких ми щільно сиділи, які у нас є майже скрізь, — Linux, nginx, postfix — додався PostgreSQL. Зараз ми його використовуємо під багато баз в інших проектах. Він нам сподобався. Четвертий — хороший, надійний стілець. Я вважаю, це історія успіху.

У мене все. Спасибі!


Володимир Бородін — Історія успіху «Яндекс.Пошти» з PostgreSQL
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.