Хабрахабр

Развитие баз данных в Dropbox. Путь от одной глобальной базы MySQL к тысячам серверов

Когда только Dropbox запустился, один пользователь на Hacker News прокомментировал, что реализовать его можно несколькими bash-скриптами с помощью FTP и Git. Сейчас такого сказать никак нельзя, это крупное облачное файловое хранилище с миллиардами новых файлов каждый день, которые не просто как-то хранятся в базе данных, а так, что любую базу можно восстановить на любую точку в течение последних шесть дней.

Под катом расшифровка доклада Славы Бахмутова (m0sth8) на Highload++ 2017, о том, как развивались базы данных в Dropbox и как они устроены сейчас.

О спикере: Слава Бахмутов — site reliability engineer в команде Dropbox, очень любит Go и иногда появляется в подкасте golangshow.com.

Содержание

Архитектура Dropbox простым языком

Dropbox появился в 2008 году. По сути, это облачное файловое хранилище. Когда только Dropbox запустился, пользователь на Hacker News прокомментировал, что реализовать его можно несколькими баш-скриптами с помощью FTP и Git. Но, тем не менее, Dropbox развивается, и сейчас это достаточно крупный сервис c более чем 1,5 миллиардами пользователей, 200 тысячами бизнесов и огромным количеством (несколько миллиардов!) новых файлов каждый день.

Как выглядит Dropbox?

Все эти клиенты используют API и общаются с двумя большими сервисами, которые логически можно разделить на: У нас есть несколько клиентов (web интерфейс, API для приложений, которые пользуются Dropbox, desktop-приложения).

  1. Metaserver.
  2. Blockserver.

На Metaserver хранится метаинформация о файле: размер, комментарии к нему, ссылки на этот файл в Dropbox и т.п. В Blockserver хранится информация только о файлах: папки, пути и т.д.

Как это работает?

Например, у вас есть файл video.avi с каким-то видео.
Ссылка со слайда

  • Клиент дробит этот файл на несколько чанков (в данном случае по 4 МБ), подсчитывает контрольную сумму и отправляет к Metaserver запрос: «У меня есть файл *.avi, я хочу его загрузить, хэш-суммы такие-то».
  • Metaserver возвращает ответ: «У меня нет этих блоков, давай загрузи!» Либо он может ответить, что у него есть все или некоторые блоки, и нужно загрузить только оставшиеся.

Ссылка со слайда

  • После этого клиент идет в Blockserver, отправляет хэш-сумму и сам блок данных, который сохраняется на Blockserver.
  • Blockserver подтверждает операцию.

Ссылка со слайда

Это достаточно сложный протокол, но схематически он работает примерно так.
Конечно, это очень упрощенная схема, протокол намного сложней: там есть синхронизация между клиентами внутри одной сети, есть драйверы ядра, возможность разрешать коллизии и т.д.

Blockserver информацию о файлах, о том, как они структурированы, из каких блоков состоят, тоже хранит в MySQL. Когда клиент сохраняет что-то на Metaserver, вся информация попадает в MySQL. Также Blockserver хранит сами блоки в Block Storage, который в свою очередь информацию о том, где лежит какой блок, на каком сервере и как он обработан в данный момент, тоже сохраняет в MYSQL.

Для хранения экзабайтов пользовательских файлов мы параллельно сохраняем дополнительную информацию в базу данных на несколько десятков петабайт, раскиданных по 6 тысячам серверов.

История развития баз данных

Как развивались базы данных в Dropbox?

Всю информацию, которую Dropbox нужно было куда-то сохранять, он сохранял в единственный глобальный MySQL. В 2008 году все начиналось с одного Metaserver и одной глобальной базы данных. Так продолжалось недолго, потому что количество пользователей росло, и отдельные базы и таблички внутри баз разбухали быстрее, чем другие.

Поэтому в 2011 году несколько таблиц были вынесены на отдельные сервера:

  • User, с информацией о пользователях, например, логинами и oAuth токенами;
  • Host, с информацией о файлах от Blockserver;
  • Misc, которая не участвовала в обработке запросов с продакшена, но использовалась для служебных функций, вроде batch jobs.

Но после 2012 года Dropbox начал очень сильно расти, с тех пор мы растем примерно на 100 млн пользователей в год.

Изначально всего 8 серверов по 200 шардов на каждом. Нужно было учитывать такой огромный рост, и поэтому в конце 2011 года у нас появились шарды — база, состоящая из 1 600 шардов. Сейчас это 400 мастер-серверов по 4 шарда на каждом.
Ссылка со слайда

Поэтому в 2012 году мы изобрели свой собственный графовый storage, который назвали Edgestore, и с тех пор вся бизнес-логика и метаинформация, которую генерирует приложение сохраняется в Edgestore. В 2012 году мы поняли, что создавать таблицы и обновлять их в БД на каждую добавляемую бизнес-логику очень сложно, муторно и проблематично.

У клиентов есть некие сущности, которые соединены между собой ссылками из gRPC API к Edgestore Сore, который преобразует эти данные в MySQL и каким-то образом там хранит их (в основном он отдает все это из кэша).
Ссылка со слайда Edgestore, по сути, абстрагирует MySQL от клиентов.

В нем информация о том, где какой блок-файл находится, на каком сервере, о перемещениях этих блоков между серверами, хранится в MySQL.
Ссылка со слайда В 2015 году мы ушли с Amazon S3, разработали собственное облачное хранилище под названием Magic Pocket.

Это очень разная нагрузка, в основном, на чтение случайных записей. 90% утилизации это I/O. Но MySQL используется очень хитрым образом — по сути, как большая распределеннуя хэш-таблица.

Архитектура баз данных

Во-первых, мы сразу же определили некие принципы, по которым строим архитектуру нашей базы данных:

  1. Надежность и долговечность. Это самый главный принцип и то, чего от нас ждут клиенты — данные не должны теряться.
  2. Оптимальность решения — не менее важный принцип. Например, бэкапы должны делаться быстро и восстанавливаться тоже быстро.
  3. Простота решения — как архитектурно, так и с точки зрения обслуживания и дальнейшей поддержки разработки.
  4. Стоимость владения. Если что-то оптимизирует решение, но стоит очень дорого, это нам не подходит. Например, slave, который отстает от master на день, очень удобен для бэкапов, но тогда к 6 000 серверов нужно добавить еще 1 000 — стоимость владения таким slave очень высока.

Все принципы должны быть верифицируемыми и измеряемыми, то есть на них должны быть метрики. Если речь идет о стоимости владения, то мы должны посчитать, сколько у нас серверов, например, уходит под базы данных, сколько серверов уходит под бэкапы, и сколько это в итоге стоит для Dropbox. Когда мы выбираем новое решение, мы подсчитываем все метрики и ориентируемся на них. При выборе любого решения мы полностью руководствуемся этими принципами.

Базовая топология

База данных устроена примерно следующим образом:

  • В основном дата-центре у нас есть master, в который происходят все записи.
  • У master-сервера есть два slave-сервера, на которые происходит semisync репликация. Серверы часто умирают (порядка 10 в неделю), поэтому нам необходимо два slave-сервера.
  • Slave-серверы находятся в отдельных кластерах. Кластеры — это совершенно отдельные комнаты в дата-центре, которые не связаны друг с другом. Если одна комната сгорает, вторая остается вполне себе рабочей.
  • Также в другом дата-центре у нас есть так называемый pseudo master (intermediate master), который на самом деле просто slave, у которого есть другой slave.

Мы просто меняем в Discovery все адреса, и клиенты могут работать. Такая топология выбрана потому, что если у нас вдруг умирает первый дата-центр, то во втором дата-центре у нас уже практически полная топология.

Специализированные топологии

Также у нас есть специализированные топологии.

Так сделано, потому что сам Magic Pocket дублирует данные среди зон. Топология Magic Pocket состоит из одного master-сервера и двух slave-серверов. Если он теряет одни кластер, то может восстановить через erasure code все данные с других зон.

В ней есть по одному master и двум slave в каждом из двух дата-центров, и они являются slave друг для друга. Топология active-active — кастомная топология, которая используется в Edgestore. Поэтому эта топология не ломается.
Это очень опасная схема, но Edgestore на своем уровне точно знает, какие данные на какой master по какому range он может записать.

Instance

У нас установлены достаточно простые сервера с конфигурацией 4-5 летней давности:

  • 2x Xeon 10 cores;
  • 5TB (8 SSD Raid 0*);
  • 384 GB memory.

* Raid 0 — потому что нам проще и намного быстрее заменить целый сервер, чем диски.

Single Instance

На этом сервере у нас есть один большой инстанс MySQL, на котором находятся несколько шардов. Этот MySQL инстанс сразу выделяет себе практически всю память. На сервере запущены и другие процессы: proxy, сбор статистики, логи и т.д.

Это решение хорошо тем, что:

Если нужно заменить MySQL инстанс, просто заменяем сервер.   + Им легко управлять.

  + Просто делать фейловеры.

С другой стороны:

Например, если нужно сделать бэкап, мы делаем бэкап сразу всех шардов.   − Проблематично то, что любые операции происходят над целым инстансом MySQL и сразу над всеми шардами. Соответственно, доступность страдает в 4 раза больше. Если нужно сделать фейловер, мы делаем фейловер сразу всех четырех шардов.

Репликация в MySQL не параллельная, и все шарды работают в один поток.   − Проблемы с репликацией одного шарда влияют на другие шарды. Если с одним шардом что-то происходит, то остальные тоже становятся жертвами.

Поэтому сейчас мы переходим на другую топологию.

Multi Instance

Чем это лучше? В новом варианте на сервере запущено сразу несколько инстансов MySQL, в каждом есть по одному шарду.

То есть если нужен фейловер, переключаем только один шард, если нужен бэкап, делаем бэкап только одного шарда.   + Мы можем проводить операции только над одним конкретным шардом. Это значит, что операции очень сильно ускоряются — в 4 раза для четырех-шардового сервера.

  + Шарды почти не влияют друг на друга.

Edgestore занимает очень много места, например, все 4 Тб, а Magic Pocket занимает всего 1 Тб, но у него утилизация 90%.   + Улучшение в репликации. Мы можем миксовать разные категории и классы баз данных. То есть мы можем объединять различные категории, которые по-разному используют I/O и ресурсы машины, и запустить 4 потока репликаций.

Конечно, у этого решения есть и минусы:

Нужен какой-то умный планировщик, который будет понимать, куда он может вынести этот инстанс, где будет оптимальная нагрузка.   − Самый большой минус — намного сложнее всем этим управлять.

  − Сложнее фейловеры.

Поэтому мы только сейчас переходим на это решение.

Discovery

Клиенты должны как-то знать, как подключаться к нужной базе, поэтому у нас есть Discovery, который должен:

  1. Очень быстро нотифицировать клиента об изменениях топологии. Если мы поменяли master и slave, клиенты должны узнать об этом практически мгновенно.
  2. Топология не должна зависеть от топологии репликации MySQL, потому что при некоторых операциях мы меняем топологию MySQL. Например, когда мы делаем split, на подготовительном шаге на target master, куда будем выносить часть шардов, часть slave-серверов перенастраивается на этот target master. Клиентам нет необходимости знать об этом.
  3. Важно, чтобы была атомарность операций и верификация состояния. Нельзя, чтобы два разных сервера одной базы данных стали были master в один и тот же момент.

Как развивался Discovery

Сначала все было просто: адрес базы данных в исходном коде в конфиге. Когда нам нужно было обновить адрес, то просто все деплоилось очень быстро.

К сожалению, это не работает, если серверов становится очень много.

Были скрипты базы данных, которые изменяли табличку в ConfigDB — это была отдельная табличка MySQL, а клиенты уже слушали эту БД и периодически забирали оттуда данные.
Выше самый первый Discovery, который у нас появился.

По сути, клиент запрашивал категорию, класс БД, ключ шарда, и ему возвращался MySQL-ный адрес, по которому он уже мог устанавливать соединение.
Таблица очень простая, есть категория базы данных, ключ шарда, класс БД master/slave, proxy и адрес БД.

Как только серверов стало очень много, добавился Memcaсhе и клиенты стали общаться уже с ним.

MySQL скрипты начали общаться через gRPC, через тонкий клиент с сервисом, который мы назвали RegisterService. Но потом мы это переработали. RegisterService сохранял данные в AFS. Когда какие-то изменения происходили, у RegisterService была очередь, и он понимал, как применять эти изменения. AFS — это наша внутренняя система, построенная на базе ZooKeeper.

Например, 100 тысяч клиентов подключаются к ZooKeeper, если они вдруг умерли из-за какого-то бага все вместе, потом прийдет сразу 100 тысяч запросов к ZooKeeper, что просто его уронит, и он не сможет подняться. Второе решение, которое здесь не показано, напрямую использовало ZooKeeper, и это создавало проблемы, потому что у нас каждый шард был узлом в ZooKeeper.

По сути, она абстрагирует работу с ZooKeeper для всех клиентов. Поэтому была разработана система AFS, которой пользуется весь Dropbox. То есть можно попробовать заменить файл с какой-то версии, а если эта версия поменялась в процессе смены, то операция отменяется. AFS демон локально крутится на каждом сервере и предоставляет очень простой файловый API вида: создать файл, удалить файл, запросить файл, получить нотификацию на изменение файла и compare and swap операции.

ZooKeeper уже не падает под нагрузкой. По существу, такая абстракция над ZooKeeper, в которой есть локальный backoff и джиттер-алгоритмы. С AFS мы снимаем бэкапы в S3 и в GIT, потом сам локальный AFS уведомляет клиентов о том, что данные изменились.

Например, выше приведен файл shard.slave_proxy — самый большой, он занимает порядка 28 Кб, и когда мы изменяем категорию shard и slave_proxy класс, то все клиенты, которые подписаны на этот файл, получают нотификацию. В AFS данные хранятся в виде файлов, то есть это API файловой системы. По shard key получают категорию и перенастраивают пул соединения к базе данных. Они перечитывают этот файл, в котором есть вся нужная информация.

Операции

Мы используем очень простые операции: promotion, clone, backups/recovery.

Когда мы заходим в операцию, мы производим какие-то проверки, например, spin-check, который несколько раз по таймауту проверяет, можно ли нам выполнить эту операцию. Операция — это простая стейт-машина. Дальше собственно сама операция. После этого мы делаем какое-то подготовительное действие, которое не влияет на внешние системы.

Если с операцией возникла какая-то проблема, то операция пытается восстановить систему в исходное положение. У всех шагов внутри операции есть rollback-step (отмена). Если все нормально, то происходит cleanup, и операция завершена.

Такая простая стейт-машина у нас на любой операции.

Promotion (смена мастера)

Это очень частая операция в БД. Были вопросы о том, как делать alter на горячем master-сервере, который работает — он же встанет колом. Просто все эти операции производятся на slave-серверах, и потом slave меняется с master местами. Поэтому операция promotion очень частая.

Нужно обновить kernel — делаем swap, нужно обновить версию MySQL — обновляем на slave, переключаем на master, обновляем там.

Например, у нас для четырех шардов сейчас promotion порядка 10-15 с. На графике выше видно, что при promotion availability пострадало на 0,0003%. Мы добились очень быстрого promotion.

Интересны фейловеры. Но нормальные promotion не так интересны, потому что это обычные операции, которые выполняются каждый день.

Фейловер (замена поломанного мастера)

Фейловер (failover) значит, что база данных умерла.

  • Если сервер действительно умер, это просто идеальный случай.
  • На самом деле бывает так, что серверы частично жив.
  • Иногда сервер очень медленно умирает. У него отказывают raid-контроллеры, дисковая система, какие-то запросы возвращают ответы, но какие-то потоки блокируются и не возвращают ответы.
  • Бывает такое, что master просто перегружен и не отвечает на наши health-check. Но если мы сделаем promotion, то новый master тоже будет перегружен, и станет только хуже.

Замена умерших master серверов у нас происходит примерно 2–3 раза в день, это полностью автоматизированный процесс, никакая интервенция человека не нужна. Критическая секция занимает примерно 30 с, и в ней есть куча дополнительных проверок того, жив ли сервер на самом деле, или, может быть, он уже умер.

Ниже примерная схема того, как работает фейловер.

Это нужно, потому что у нас MySQL 5. В выделенной секции мы перезагружаем master-сервер. Поэтому возможны phantom reads, и нам нужно этот master, даже если он не умер, как можно быстрее убить, чтобы клиенты от него отключились. 6, а в нем semisync репликация не lossless. В MySQL 5. Поэтому мы делаем hard reset через Ipmi — это первая самая важная операция, которую мы должны сделать. 7 версии это не так критично.

Синхронизация кластера. Зачем нам нужна синхронизация кластера?

При promotion нам нужно, чтобы master был в том же основном дата-центре. Если вспомнить предыдущую картинку с нашей топологией, у одного master-сервера есть три slave-сервера: два в одном дата-центре, один — в другом. Поэтому нам нужно сначала синхронизировать весь кластер, а потом уже сделать promotion на slave в нужном нам дата-центре. Но иногда, когда slave нагружены, при semisync бывает так, что semisync-slave’ом становится slave в другом дата-центре, потому что он-то не нагружен. Это делается очень просто:

  • Мы останавливаем все I/O thread на всех slave-серверах.
  • После этого мы уже точно знаем, что master «read-only», так как отключился semisync и туда больше никто ничего записать не может.
  • Дальше мы выбираем slave с наибольшим retrieved/executed GTID Set, то есть с наибольшей транзакцией, которую он либо скачал, либо уже применил.
  • Перенастраиваем все slave-серверы на этот выбранный slave, запускаем I/O thread, и они синхронизируются.
  • Ждем, пока они синхронизируются, после этого у нас весь кластер становится синхронизированным. В конце проверяем, что у нас везде executed GTID set установлен на одну и ту же позицию.

Вторая важная операция — синхронизация кластеров. Дальше начинается promotion, который происходит следующим образом:

  • Мы выбираем любой slave в нужном нам дата-центре, говорим ему, что он master, и запускаем операцию стандартного promotion.
  • Мы перенастраиваем все slave-сервера на этот master, останавливаем там репликацию, применяем ACLs, вбиваем пользователей, останавливаем какие-то proxy, возможно, что-то перезагружаем.
  • В конце концов мы делаем read_only = 0, то есть говорим, что теперь на master можно записывать, и обновляем топологию. С этого момента клиенты идут на этот master и у них все работает.
  • Дальше у нас есть не критичные пост-шаги обработки. В них мы перезапускаем какие-то сервисы на этом хосте, перерисовываем конфигурации, делаем дополнительные проверки, что все точно работает, например, что proxy пропускает трафик.
  • После этого вся операция завершена.

На любом шаге, в случае ошибки мы пытаемся сделать rollback до того момента, до которого можем. То есть мы не можем сделать rollback для reboot. Но для операций, для которых это возможно, например, переназначения — change master — мы можем вернуть master на предыдущий шаг.

Бэкапы

Бэкапы — очень важная тема в базах данных. Я не знаю, делаете ли вы бэкапы, но мне кажется, все должны их делать, это уже избитая шутка.

Паттерны использования

  ● Добавить новый slave

Это происходит постоянно. Самый главный паттерн, который мы используем при добавлении нового slave-сервера, мы просто его восстанавливаем из бэкапа.

  ● Восстановление данных на точку в прошлом

Эта достаточно частая операция восстановления данных на точку в прошлом у нас автоматизирована. Достаточно часто пользователи удаляют данные, а потом просят их восстановить, в том числе из базы данных.

  ● Восстановить целиком весь кластер с нуля

На самом деле эта операция практически никогда нам не требовалась. Все думают, что бэкапы нужны для того, чтобы восстановить все данные с нуля. Последний раз мы использовали ее 3 года назад.

Мы смотрим на бэкапы, как на продукт, поэтому говорим клиентам, что у нас есть гарантии:

  1. Мы можем восстановить любую базу данных. В нормальных условиях ожидаемая скорость восстановления 1Тб за 40 минут.
  2. Любую базу можно восстановить на любую точку в течение последних шесть дней.

Это наши основные гарантии, которые мы даем нашим клиентам. Скорость в 1 Тб за 40 минут, потому что есть ограничения по сети, мы не одни на этих стойках, на них еще продакшен трафик.

Цикл

Мы ввели такую абстракцию, как цикл. В одном цикле мы стараемся забэкапить практически все наши базы данных. У нас одновременно крутится 4 разных цикла.

  • Первый цикл выполняется каждые 24 часа. Мы бэкапим все наши шардированные базы данных на HDFS, это порядка тысячи с лишним хостов.
  • Каждые 6 часов мы делаем бэкапы для unsharded databases, у нас еще есть некоторые данные на Global DB. Мы очень хотим от них избавиться, но, к сожалению, они до сих пор есть.
  • Каждые 3 дня мы сохраняем полностью всю информацию шардированных баз данных на S3.
  • Каждые 3 дня мы полностью сохраняем на S3 всю информацию нешардированных баз данных.

Допустим, если мы храним 3 цикла, то в HDFS у нас есть последние 3 дня, и последние 6 дней в S3. Все это хранится в течении нескольких циклов. Так мы поддерживаем наши гарантии.

Это пример, как они работают.

При завершении каждого цикла мы обязательно верифицируем, что бэкапы работают, то есть делаем recovery на какой-то процент базы данных. В данном случае у нас запущено два длинных цикла, которые делают бэкапы шардированных баз данных, и один короткий. В итоге у нас будет 100 процентов бекапов, которые мы восстановили. К сожалению, мы не можем восстановить все данные, но какой-то процент данных для цикла мы обязательно проверяем.

Плюс при клонировании мы тоже восстанавливаемся из бэкапов. У нас есть определенные шарды, которые мы всегда восстанавливаем, чтобы смотреть скорость восстановления, чтобы мониторить возможные регрессии, и есть шарды, которые мы восстанавливаем просто рандомно, чтобы проверить, что они восстановились и работают.

Горячие бэкапы

Запускаем его в режиме —stream=xbstream, и он нам возвращает на рабочей базе данных, поток бинарных данных. Сейчас у нас происходит hot-бэкап, для которого мы используем инструмент Percona xtrabackup. Дальше у нас есть script-splitter, который этот бинарный поток делит на четыре части, и потом мы сжимаем этот поток.

Если база данных занимает 3 Тб, то, в результате сжатия, бэкап занимает примерно 1 500 Гб. MySQL хранит данные на диске очень странным образом и у нас получилась компрессия больше 2x. Дальше мы шифруем эти данные, чтобы никто не мог их прочитать, и отправляем в HDFS и в S3.

В обратную сторону работает абсолютно точно так же.

Потом происходит crash-recovery. Подготавливаем сервер, куда будем устанавливать бэкап, достаем бэкап из HDFS или из S3, декодируем и декомпрессируем его, splitter сжимает это все и отправляет в xtrabackup, который восстанавливает все данные на сервер.

В целом нужно проиграть все транзакции за то время, пока вы делаете бэкап. Некоторое время самой главной проблемой hot бэкапов было то, что crash-recovery занимает достаточно длительное время. После этого мы проигрываем binlog, чтобы наш сервер догнал текущий master.

Как мы сохраняем binlogs?

Мы собирали на master файлики, чередовали их каждые 4 минуты, либо по 100 Мб, и сохраняли в HDFS. Раньше мы сохраняли файлики binlog’ов.

Он, по сути, постоянно сливает binlog к себе и сохраняет их на HDFS.
Сейчас у нас используется новая схема: есть некий Binlog Backuper, который подключен к репликациям и ко всем базам данных.

Все сохраненное в HDFS и в S3 хранится в течение месяца. Соответственно, в предыдущей реализации мы могли потерять 4 минуты бинарных логов, если потеряли все 5 серверов, в этой же реализации, в зависимости от нагрузки, мы теряем буквально секунды.

Холодные бэкапы

Мы подумываем перейти на холодные бэкапы.

Предпосылки для этого:

  1. Скорость каналов на наших серверах стала больше — было 10 Гб, стало 45 Гб — можно утилизировать больше.
  2. Хочется восстанавливать и создавать клоны быстрее, потому что нам нужен более умный scheduler для multi instance и хочется очень часто перекидывать slave и master с сервера на сервер.
  3. Самый важный момент — при холодном бэкапе мы можем гарантировать, что бэкап работает. Потому что, когда мы делаем холодный бэкап, мы просто копируем файл, потом запускаем базу данных, и как только она запустилась, мы знаем, что этот бэкап работает. После pt-table-checksum мы точно знаем, что данные на файловой системе консистентны.

Гарантии, которые получились при холодных бэкапах в наших экспериментах:

  1. В нормальных условиях ожидаемая скорость восстановления 1Тб за 10 минут, потому что это просто копирование файлов. Не нужно делать crash-recovery, а это самое проблемное место.
  2. Любую базу можно восстановить на любой период времени за последние шесть дней.

Мы периодически его останавливаем, делаем холодный бэкап и запускаем обратно. В нашей топологии есть slave в другом дата-центре, который практически ничего не делает. Все очень просто.

Планы ++

Это планы на дальнее будущее. Когда мы будем делать обновление нашего Hardware парка, мы хотим добавить на каждый сервер дополнительный шпиндельный диск (HDD) порядка 10 Тб, и делать на него горячие бэкапы + crash recovery xtrabackup, а после этого загружать уже бэкапы. Соответственно, у нас будут бэкапы на всех пяти серверах одновременно, в разные точки времени. Это, конечно, усложнит всю обработку и оперирование, но снизит стоимость, потому что HDD стоит копейки, а огромный кластер HDFS стоит дорого.

Клон

Как я уже говорил, клонирование — это простая операция:

  1. это либо восстановление из бэкапа и проигрывание бинарных логов;
  2. либо процесс бэкапа сразу на целевой сервер.

В диаграмме, где мы копируем на HDFS, также данные просто копируются на другой сервер, где есть ресивер, который принимает все данные и восстанавливает их.

Автоматизация

Конечно же, на 6 000 серверах никто ничего не делает вручную. Поэтому у нас есть различные скрипты и сервисы автоматизации, их очень много, но основные из них — это:

  • Auto-replace;
  • DBManager;
  • Naoru, Wheelhouse

Auto-replace

Этот скрипт нужен, когда сервер умер, и нужно понять, правда ли он умер, и что за проблема — может, сеть поломалась или еще что-то. Это нужно решить, как можно быстрее.

Починить мы можем очень быстро — у нас recovery очень быстрый, поэтому нам нужно как можно скорее определить существование проблемы.
Availability (доступность) — это функция от времени между возникновением ошибок и временем, за которое вы можете детектировать и починить эту ошибку.

Heartbeat — это текущий timestamp.
На каждом сервере MySQL запущен сервис, которые пишет heartbeat.

После этого второй сервис отправляет в центральное хранилище этот heartbeat. Есть также другой сервис, который пишет значение некоторых предикатов, например, что master в режиме read-write.

У нас есть auto-replace скрипт, работающий по такой схеме.
Схема в лучшем качестве и отдельно ее увеличенные фрагменты есть в презентации доклада, начиная с 91 слайда.

Что здесь происходит?

  • Есть основной цикл, в котором мы проверяем heartbeat в глобальной базе данных. Смотрим, зарегистрирован этот сервис или нет. Подсчитываем heartbeat’ы, например, есть ли два heartbeat’а за 30 с.
  • Далее, смотрим, удовлетворяет ли их количество пороговому значению. Если нет, то значит, что-то с сервером не так — раз он не послал heartbeat.
  • После этого мы делаем reverse check на всякий случай — вдруг эти два сервиса умерли, что-то с сетью, или глобальная база данных не может почему-то записать heartbeat. В reverse check мы подсоединяемся к поломанной базе данных и проверяем ее состояние.
  • Если уже ничего не помогло, мы смотрим, прогрессирует ли master position или нет, происходят ли на него записи. Если ничего не происходит, то этот сервер точно не работает.
  • Последний этап — собственно auto-replace.

Auto-replace очень консервативен Он никогда не хочет делать много автоматических операций.

  1. Во-первых, мы проверяем, не было ли операций с топологией недавно? Может быть, этот сервер только что был добавлен и что-то на нем еще не запущено.
  2. Проверяем, не было ли каких-то замен в этом же кластере в какой-то промежуток времени.
  3. Проверяем, какой у нас failure limit. Если у нас много проблем одномоментно — 10, 20 — то мы не будем автоматически их все решать, потому что можем ненароком нарушить работу всех баз данных.

Поэтому решаем только одну проблему за раз.

Соответственно, для slave-сервера мы запускаем клонирование и просто удаляем его из топологии, а если это master, то запускаем фейловер, так называемый emergency promotion.

DBManager

DBManager — это сервис для управления нашими базами данных. В нем есть:

  • умный планировщик задач, который точно знает, когда какой job запустить;
  • логи и вся информация: кто, когда и что запускал — это источник правды;
  • точка синхронизации.

DBManager достаточно прост архитектурно.

  • Есть клиенты, это либо DBA, которые что-то делают через web интерфейс, либо скрипты/сервисы, которые написали DBA, которые обращаются по gRPC.
  • Есть внешние системы вроде Wheelhouse и Naoru, которая по gRPC ходит в DBManager.
  • Есть планировщик, который понимает, какую операцию, когда и где он может запустить.
  • Есть очень тупой worker, который, когда к нему приходит операция, запускает ее, проверяет по PID. Worker может перезагружаться, процессы не прерываются. Все worker’ы расположены как можно ближе к серверам, на которых происходят операции, чтобы, например, при обновлении ACLS нам не нужно было делать много раунд-трипов.
  • На каждом SQL-хосте у нас есть некий DBAgent — это RPC сервер. Когда нужно провести какую-то операцию на сервере, мы отправляем RPC запрос.

У нас есть web интерфейс для DBManager, где можно посмотреть текущие запущенные задачи, логи к этим задачам, кто и когда что запустил, какие операции были проведены для сервера конкретной базы данных и т.д.

Есть достаточно простой CLI интерфейс, где можно запускать задачи и также просматривать их в удобных представлениях.

Remediations

Еще у нас есть система реагирования на проблемы. Когда у нас что-то поломалось, например, диск вышел из строя, либо какой-то сервис не работает, срабатывает Naoru. Это система, которая работает во всем Dropbox, все ею пользуются, и она построена именно для таких небольших задач. Про Naoru я рассказывал в своем докладе в 2016 году.

Например, нам нужно обновить ядро на всех MySQL на всем нашем кластере из 6000 машин. Система Wheelhouse основана на базе стейт-машины и предназначена для долгих процессов. Эта операция может занять месяц или даже два. Wheelhouse четко это делает — обновляет на slave-сервере, запускает promotion, slave становится master, обновляет на master-сервере.

Мониторинг

Это очень важно.

Если вы не мониторите систему, то скорее всего она не работает.

Мы мониторим все в MySQL — вся информация, которую мы можем получить из MySQL, у нас где-то сохраняется, мы можем получить к ней доступ по времени. Мы сохраняем информацию по InnoDb, статистику по запросам, по транзакциям, по длине транзакций, перцентили на длины транзакции, по репликации, по сети — все-все-все — огромное количество метрик.

Alert

У нас настроено 992 алерта. Вообще-то на метрики никто не смотрит, мне кажется, что нет людей, которые приходят на работу и начинают смотреть на график метрик, есть более интересные задачи.

У нас 992 алерта, что бы ни случилось, мы об этом узнаем. Поэтому есть алерты, которые срабатывают при достижении определенных пороговых значений.

Инциденты

У нас есть PagerDuty — это сервис, через который распространяются алерты на ответственных лиц, которые начинают принимать меры.

После этого дежурный проверил, что помешало emergency promotion, и сделал необходимые ручные операции. В данном случае произошла ошибка в emergency promotion и сразу после этого зарегистрировался алерт о том, что упал master.

Даже если этот инцидент — проблема в наших алертах, мы тоже создаем задачу, потому что, если проблема в логике алерта и порогах срабатывания, то их надо поменять. Мы обязательно разбираем каждый произошедший инцидент, для каждого инцидента у нас есть задача в task tracker. Алерт — это всегда больно, особенно в 4 часа ночи. Алерты не должны просто так портить людям жизнь.

Тестирование

Как и с мониторингом, я уверен, что тестированием занимаются все. Помимо юнит-тестов, которыми мы покрываем наш код, у нас есть интеграционные тесты, в которых мы тестируем:

  • все топологии, которые у нас есть;
  • всех операции над этими топологиями.

Если у нас есть promotion операции, мы тестируем в интеграционном тесте promotion операции. Если у нас есть клонирования, мы делаем клонирование для всех топологий, которые у нас есть.

Пример топологии

У нас есть топологии на все случаи жизни: 2 дата-центра с multi instance, с шардами, без шардов, с кластерами, один дата-центр — вообще практически любая топология — даже те, которые мы не используем, просто, чтобы посмотреть.

Например, нам нужно поднять master, и мы говорим, что сделать это нужно с такими-то данными инстансов, с такими-то базами данных на таких-то портах. В этом файле у нас просто есть настройки, какие сервера и с чем нам нужно поднимать. У нас практически все собирается с помощью Bazel, который на базе этих файлов создает топологию, запускает MySQL сервера, после этого запускается тест.

В данном тесте Уы тестируем auto_replace. Тест выглядит очень просто: мы указываем, какая используется топология.

  • Создаем сервис auto_replace, стартуем его.
  • Убиваем master в нашей топологии, ждем некоторое время и смотрим, что target-slave, стал master. Если нет, то тест не пройден.

Stages

Stage-окружение — это такие же базы данных, как и в продакшене, но на них нет пользовательского трафика, а есть некий синтетический трафик, который похож на продакшен, через Percona Playback, sysbench и похожие системы.

То есть это искусственная, но очень близкая к реальной нагрузка. В Percona Playback мы записываем трафик, потом проигрываем его на stage-окружении с различной интенсивностью, можем в 2-3 раза быстрее проиграть.

Мы не можем протестировать алерт или то, что метрики работают. Это нужно, потому что в интеграционных тестах мы не можем протестировать наш продакшен. На стейджинге мы тестируем алерты, метрики, операции, периодически убиваем сервера и смотрим, что они нормально собираются.

Иногда вы думаете, что система поведет себя так, а не иначе, но она может повести себя вообще по-другому. Плюс мы тестируем все автоматизации вместе, потому что в интеграционных тестах, скорее всего, тестируется одна часть системы, а в стейджинге все автоматизированные системы работают одновременно.

DRT (Disaster recovery testing)

Также мы проводим тесты в продакшене — прямо на реальных базах. Это называется Disaster recovery testing. Почему нам это нужно?

  ● Мы хотим протестировать наши гарантии.

Например, в Google есть один сервис, который работал настолько стабильно — 100% времени, — что все сервисы, которые его использовали, решили, что этот сервис реально 100% стабилен и никогда не падает. Это делают многие крупные компании. Поэтому Google пришлось этот сервис ронять специально, чтобы пользователи учитывали и такую возможность.

И у нас есть гарантия, что он может не работать какой-то промежуток времени, клиенты должны это учитывать. Так и мы — у нас есть гарантия, что MySQL работает — а иногда не работает! Периодически мы убиваем production master, либо, если мы хотим сделать фейловер, убиваем все slave-серверы, чтобы посмотреть, как поведет себя semisync репликация.

  ● Клиенты готовы к этим ошибкам (замена и смерть мастера)

У нас был случай, когда при promotion 4 шардов из 1600, availability падала до 20%. Почему это хорошо? Фейловеры для этой системы происходили достаточно редко, примерно раз в месяц, и все решили: «Ну, это фейловер, бывает». Кажется, что что-то не так, для 4 шардов из 1600 должны быть какие-то другие цифры.

Этот сервис делал еще что-то и, в конечном итоге, умирал и переставали записываться heartbeat’ы. В какой-то момент, когда мы переходили на новую систему, один человек решил оптимизировать те два сервиса записи heartbeat и объединил их в одни. Все лежало — 20% availability. Так получилось, что для этого клиента у нас стало 8 фейловеров в день.

Соответственно, как только master умирал, у нас все соединения держались еще 6 часов. Оказалось, что в этом клиенте keep-alive 6 часов. Это починили. Пул не мог дальше работать — у него коннекты держатся, он ограничен и не работает.

Что-то все-равно не так. Делаем фейловер опять — уже не 20%, но все равно много. Пул при запросе обращался ко многим шардам, а потом соединял все это. Оказалось, что баг в реализации пула. Все эти шарды не могли больше работать. Если какие-то шарды фейловерились, происходил какой-то race condition в Go коде, и весь пул забивался.

Disaster recovery тестирование очень полезно, потому что клиенты должны быть готовы к этим ошибкам, они должны проверять свой код.

Это происходит не ночью, и это здорово.   ● Плюс Disaster recovery testing хорош тем, что проходит в бизнес часы и все на месте, меньше стресса, люди знают, что сейчас произойдет.

Заключение

  1. Всё нужно автоматизировать, никогда не лезть руками.
Каждый раз, когда у нас кто-то лезет в систему руками, у нас все умирает и ломается — каждый божий раз! — даже на простых операциях. Например, умер один slave, человек должен был добавить второй, но решил удалить умерший slave руками из топологии. Однако вместо умершего он скопировал в команду живой — master остался вообще без slave. Такие операции не должны делаться вручную.

Тесты должны быть постоянные и автоматизированные (и в продакшене).
Ваша система меняется, ваша инфраструктура меняется.   2. Поэтому нужно постоянно, каждый день делать автоматизированное тестирование, в продакшене в том числе. Если вы один раз проверили, и она вроде работала, это не значит, что она будет и завтра работать.

Обязательно нужно владеть клиентами (библиотеками).
Пользователи могут не знать, как работают базы данных.   3. Поэтому лучше владеть этими клиентами — вам будет спокойнее. Они могут не понимать, зачем нужны таймауты, keep-alive.

Нужно определить свои принципы построения системы и свои гарантии, и всегда соблюдать их.   4.

Таким образом можно поддерживать 6 тысяч серверов баз данных.

В вопросах после доклада, и особенно ответах на них, тоже много полезной информации.

Вопросы и ответы

Есть ли возможность этот шард расплитить, или нагрузка на шарды не отличается нигде на порядки? — Что будет, если есть дисбаланс нагрузки на шарды — какая-то метаинформация о каком-то файле оказалась популярнее?

Она не отличается на порядки. Она практически нормально распределена. У нас есть троттлинг, то есть мы не можем перегрузить шард по сути, мы троттлим на уровне клиента. Вообще бывает такое, что какая-нибудь звезда выкладывает фотографию, и шард практически взрывается. Тогда мы баним эту ссылку

Можно поподробнее, что это такое — это из коробки или это создается? — Вы говорили у вас 992 алерта. Если создается, то это ручной труд или что-то вроде машинного обучения?

Это все создается вручную. У нас есть собственная внутренняя система, называется Vortex, где хранятся метрики, в ней поддерживаются алерты. Есть yaml-файл, в котором написано, что есть условие, например, что бэкапы должны выполняться каждый день, и если это условие выполняется, то алерт не срабатывает. Если не выполняется, тогда приходит алерт.

Это наша внутренняя разработка, потому что мало кто умеет хранить столько метрик, сколько нам нужно.

Ты уронил, CODERED, не поднимается, с каждой минутой паники все больше. — Насколько крепкие должны быть нервы, чтобы делать DRT?

Вообще работать в базах данных — это реально боль. Если база данных упала, сервис не работает, весь Dropbox не работает. Это реальная боль. DRT полезно тем, что это бизнес-часы. То есть я готов, я сижу за рабочим столом, я выпил кофе, я свеж, я готов сделать все, что угодно.

Например, последний сильный сбой у нас был недавно. Хуже, когда это происходит в 4 часа ночи, и это не DRT. Там еще был другой сервис, который читал binlog. При вливании новой системы мы забыли выставить OOM Score для нашего MySQL. — запускает команду по удалению в Percona checksum-table какой-то информации. В какой-то момент наш оператор вручную — опять же вручную! Сервис прочитал этот binlog в память, OOM Killer пришел и думает, кого бы убить? Просто обычное удаление, простая операция, но эта операция породила огромный binlog. А мы забыли OOM Score выставить, и он убивает MySQL!

Когда умирает 40 мастеров, это реально очень страшно и опасно. У нас в 4 часа ночи умирают 40 мастеров. Мы лежали где-то час. DRT — это не страшно и не опасно.

Кстати, DRT — это хороший способ отрепетировать такие моменты, чтобы мы точно знали, какая последовательность действий нужна, если что-то массово поломается.

Во-первых, почему не используется кластер, к примеру? — Хотел бы подробнее узнать про переключение master-master. Кластер баз данных, то есть не master-slave с переключением, а master-master аппликация, чтобы если один упал, то и не страшно.

Вы имеете в виду что-нибудь вроде group replication, galera cluster и т.п.? Мне кажется, group application еще не готов к жизни. Galera мы, к сожалению, еще не пробовали. Это здорово, когда фейловер есть внутри вашего протокола, но, к сожалению, у них есть очень многих дргуих проблем, и не так просто перейти на это решение.

Не пробовали? — Кажется, в MySQL 8 есть что-то типа InnoDb кластера.

У нас до сих пор еще 5.6 стоит. Я не знаю, когда мы перейдем на 8. Может, попробуем.

Если master погасить, надо, чтобы очередь добежала, чтобы slave переключить в режим master — или как-то по-другому это делается? — В таком случае, если у вас есть один большой master, при переключении с одного на другой, получается на slave-серверах высокой нагрузкой скапливается очередь.

Нагрузка на master регулируется semisync’ом. Semisync ограничивает запись на мастер производительностью slave-серверов. Конечно, может быть такое, что транзакция пришла, semisync отработал, но slave’ы очень долго проигрывают эту транзакцию. Нужно тогда подождать, пока slave проиграет эту транзакцию до конца.

— Но тогда на master будут поступать новые данные, и надо будет...

Когда мы запускаем процесс promotion, мы отключаем I/O. После этого master не может ничего записать, потому что semisync репликация. Может прийти фантомное чтение, к сожалению, но это другая проблема уже.

Что нужно сделать тому, кто пишет эту систему? — Эти все красивые стейт—машины — на чем написаны скрипты и как сложно добавить новый шаг?

Все скрипты написаны на Python, все сервисы написаны на Go. Это наша политика. Логику поменять несложно — просто в Python-коде, по которому генерируется стейт-диаграмма.

Как написаны тесты, как они разворачивают ноды в виртуалке — это контейнеры? — А можно подробнее про тестирование.

Да. Тестирование у нас собирается с помощью Bazel. Есть некие настроечные файлы (json) и Bazel поднимает скрипт, который по этому настроечному файлу создает топологию для нашего теста. Там описаны разные топологии.

У нас есть система Devbox. У нас это все работает в docker-контейнерах: либо это работает в CI, либо на Devbox. Там это тоже запускается внутри Bazel, внутри docker-контейнера или в Bazel Sandbox. Мы все разрабатываем на некоем удаленном сервере, и это может на нем работать, например. Bazel очень сложный, но прикольный.

— Когда вы сделали на одном сервере 4 инстанса, не потеряли ли вы в эффективности использования памяти?

Каждый инстанс стал меньше. Соответственно, чем с меньшей памятью MySQL оперирует, тем ему проще жить. Любой системе проще оперировать небольшим количеством памяти. В этом месте мы ничего не потеряли. У нас есть простейшие С-группы, которые ограничивают по памяти эти инстансы.

— Если у вас 6 000 серверов хранят базы данных, можете назвать, сколько миллиардов петабайт хранится в ваших файлах?

Это десятки экзабайт, мы переливали данные с Амазона в течение года.

У вас 1600 шардов — это какое-то жестко заданное значение? — Получается, у вас вначале было 8 серверов, на них по 200 шардов, потом 400 серверов по 4 шарда. Это будет больно, если вам понадобится, например, 3 200 шардов? Вы больше не сможете никогда сделать?

Да, изначально было 1600. Это было сделано чуть меньше 10 лет назад, и до сих пор живем. Но у нас еще есть 4 шарда — в 4 раза мы можем еще увеличить место.

Что происходит чаще, что реже, и особенно интересно, происходят ли спонтанные карапты блоков? — Как умирают сервера, в основном по каким причинам?

Самое главное — это диски вылетают. У нас RAID 0 — диск вылетел, мастер умер. Это самая главная проблема, но нам проще заменить этот сервер. Google проще заменить дата-центр, нам сервер пока еще. Corruption checksum у нас практически не бывало. Если честно, я не помню, когда последний раз такое было. Просто мы достаточно часто обновляем мастера. У нас время жизни одного мастера ограничено 60 днями. Он не может жить дольше, после этого мы его заменяем на новый сервер, потому что почему-то в MySQL постоянно что-то накапливается, и через 60 дней мы видим, что начинают проблемы происходить. Может быть, не в MySQL, может быть, в Linux.

Мы просто ограничили время 60 днями, и обновляем весь стек. Мы не знаем, что это за проблема и не хотим с этим разбираться. Не нужно прикипать к одному мастеру.

Например, человек залил JPEG с одним названием, потом залил такой же JPEG, но измененный, то вы можете достать первую версию? — Вы сказали, что за последние 6 дней можете восстановиться из бэкапа на любое состояние. Если человек попросит — я хочу достать первую версию файла, вы можете ему это отдать или нет? То есть, получается, вы храните версионность файлов и какие-то метаданные с версиями?

Мы храним информацию о файле, о блоках. Мы можем — в Dropbox есть возможность восстанавливать файлы.

Нет проблем с фрагментацией на дисках и так далее? — Как вы потом вычищаете это все? Допустим, человек залил 10 версий файлов поочередно. Много данных стирается с диска, получается, через какое-то время, когда версия становится ненужной, протухшей? Или они вечно хранятся? Очевидно, через 7 дней в бэкапе вы поймете, что вам первые 6 версий уже не нужны, и их нужно удалить.

Вообще в Dropbox есть какие-то гарантии, за какой промежуток времени сколько версий хранится. Это немножко другое. Есть система, которая умеет восстанавливать файлы, и там файлы просто не удаляются сразу, они в какую-то корзину кладутся.

Файлы есть, есть блоки, но в базе данных нет информации, как из этих блоков файл собрать. Есть проблема, когда совершенно все удалено. В такой момент мы можем проиграть до какого-то момента, то есть восстановились за 6 дней, проиграли до момента, когда этот файл был удален, не стали его удалять, восстановили и отдали пользователю.

В последнем можно принять деятельное участие, до 1 сентября отправив заявку на доклад.
Следите за блогом или подпишитесь на рассылку, в facebook или youtube-канал — мы регулярно публикуем свежие материалы и обновления в подготовке Highload++ 2018.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»