
Больше года назад мы в LEADS.SU задумались над высокодоступностью нашей БД и начали искать различные варианты. Круг решений сужало то, что мы используем TokuDB, который уже не поддерживается. Вариантов было несколько, но точно было понятно что запуск кластера повлечет за собой полное клонирование файлов /var/lib/mysql
, к тому моменту размер этой директории уже перевалил за пару сотен гигабайт и мы задумались над ревизией данных, что привело к долгоиграющему процессу по уменьшению размера БД.
По ходу реализации мы сталкивались с различными трудностями и препятствиями, в этой статье я ретроспективно опишу весь пройденный нами путь, полученные результаты и совершенные ошибки.
Ревизия данных
Первым делом мы решили избавиться от баз данных и таблиц, которые точно нам не нужны, но по какой-то причине все еще существуют на сервере. Почему так случается: старые фичи теряют актуальность, их утилизация не приносить ценности бизнесу, а "все эти разговоры про перспективу" мало эффективны.

Проще всего удалить базы данных целиком, чем выявлять неактуальные таблицы. Звучит легко, но нельзя так просто взять и удалить базы данных:
Во-первых, ненужность данных было предположением с уверенностью в 99%, но никто не гарантировал достоверность. Значит нам нужно придумать схему безопасного удаления.
Во-вторых, а вдруг данные будут нужны позже, например через пару лет? Значит делаем бэкап.
Первый шаг это найти потенциально заинтересованных лиц, которым нужны эти данные. Поискали, выяснили что данные не нужны, как минимум сейчас в ближайшем обозримом будущем. БД для удаления нашли, теперь идем удалять.
Схема безопасного удаления баз данных и таблиц
Логичный вариант удалять базу данных или таблицу через миграцию в коде. Как минимум так сказали разработчики, и они правы. Но нам нужна не логичность, а безопасность удаления. Поэтому мы решили разделить задачу на 3 части:
переименовываем базу данных / таблицу в
{name}_to_delete
ждем N дней (в нашем случае 7) и наблюдаем за ошибками в приложениях, ждем вопросы от менеджеров "куда делись данные?"
если ошибок и вопросов нет - удаляем
И конечно, перед удалением мы собрали критическую массу доказательств неиспользуемости, но ... нам нужна безопасность и возможность быстрого отката изменений.

Стоит заметить что MariaDB не поддерживает переименование баз данных в отличие от таблиц, поэтому чтобы переименовать базу данных нужно:
создать новую базу данных
путем переименования таблиц перенести в новую БД таблицы из старой БД
удалить старую ПУСТУЮ БД, именно пустую
Этот процесс для нас оказался достаточно простым и заключался только в согласовании и наблюдении. В итоге мы удалили 13 баз данных и 31 таблицу и освободили ~10% пространства на диске. Удаляли по паре штук за итерацию.
Мы прошли первый шаг, который занял у отдела DevOps и продуктовой разработки минимум усилий. Но нам нужно больше профита.
Удаление неактуальных данных в таблицах
Мы предполагали что у нас есть неактуальные данные в таблицах, даже было примерное понимание в каких. Например, это могли быть старые данные 10-ти летней давности, которые уже несколько лет никто не запрашивает и не будет. Или это могли быть данные, срок актуальности, которых исчисляется месяцами, например уведомления в ЛК.
В БД несколько сотен таблиц, все не пройти, поэтому для ориентира, мы взяли таблицы размером больше 1гб данных, здесь счет пошел на десятки.
Для того чтобы выявить неактуальные данные в массивных таблицах мы пошли в продуктовые команды, общались с разработчиками, которые когда-то делали функционал для работы с этими данными. В результате у нас сформировался список таблиц для более детального анализа актуальности данных.
Теперь нам придется вступить в бой диалог с продактами и продать им необходимость (чтобы это сделали разработчики, у нас нет компетенций):
выпилить из кода некоторые функций
вслед за ним и удалить данные
а где-то даже написать код периодического удаления неактуальных данных.

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

В конечном итоге, что-то из таблиц было удалено, а что-то было сокращено, где-то написаны демоны удаления неактуальных данных с течением времени, и в итоге было удалено 145гб несжатых данных, как минимум так нам говорила MariaDB, но на диске мы видели только уменьшение свободного места. Что-то здесь не так ...
Данные в таблицах не удалились
Я выгрузил данные и вывел ТОП 20 таблиц после уменьшения, на скрине видно, что есть таблицы, которые значительно выросли в объеме после удаления данных, а общий прирост в объеме данных составил 235гб (несжатых данных):

Ситуация такая: мы значительно сократили объем данных, но свободного места на диске не прибавилось, его даже стало меньше. Как так?
Я начал анализировать миграции данных за последние полгода и высчитывать размеры колонок в старых таблицах. Выяснилось что были миграции где varchar
увеличили с 256 до 1024, а это дает как минимум 1байт
прироста на размер строки, без учета изменения данных, а длина данных строк не увеличилась, я все проверил. Итого рассчитываемый прирост ~5гб
, но никак не 235.

Но все цифры были ничтожно малы, чтобы это было правдой. Немного погуглив нашел статью где говорится про фрагментацию диска при использовании varchar
. Но это пока ничего не дает. Идем дальше.
Из ТОП 20 взял самую жирную таблицу и проанализировал рост данных: поступление данных с 2024 года кратно выросло:

А вот сравнение данных до и после уменьшения этой таблицы:

Однако, эта таблица выросла всего-лишь на 9млн записей (+20%
), а по объему занимаемого места на диске выросла почте в 3 раза. Хотя в этой таблице уже долгое время удаляются старые записи миллионами штук.
И тут до меня дошло: удаленные данные никуда не делись ...
И действительно, есть обсуждение этой проблемы где говорится что DELETE
не освобождает место на диске.

Тут нужно понимать тип движка таблицы БД, в нашем случае это TokuDB, который версионник (MVCC), где DELETE
всего-лишь помечает данные как удаленные, но фактически не удаляет их с диска. Оно и понятно, не будет же движок перестраивать все дерево каждого индекса на каждый DELETE
.
В итоге: мы удалили внушительные объемы данных, но они лишь помечены как удаленные, но фактически остались на диске. Значит, нам нужны другие механизмы очистки данных.
Как удалить удаленные данные?
В этой статье говорится что OPTIMIZE TABLE освобождает место на диске. Значит это что нам нужно.
Мы склонировали продовый сервер БД и начали на нем эксперименты. Брали таблицы в несколько сотен мегабайт и проводили OPTIMIZE TABLE
. При первой же попытке мы увидели странное предупреждение от MariaDB, о том, что сервер не умеет OPTIMIZE
и будет делать recreate + analyze
, нам было не до конца понятно.

Если сервер будет делать recreate
таблиц, то мы должны предусмотреть чтобы на сервере хватило места на диске для пересоздания таблиц, ведь мы будем иметь дело с весьма объемными данными. Для этого временно прикрутили prometheus
+ node_exporter
и наблюдали за графиками во время манипуляций на тестовом стенде.

Результаты были неоднозначные, где-то файлы таблицы становились меньше, а где-то наоборот больше. Где-то по ссылкам выше было написано о том, что первый OPTIMIZE
может раздуть, а второй схлопнуть, но оказалось не совсем так.

Забегая немного вперед, после небольшой лекции нашего сисадмина, на тему как изменять размер диска сервера на лету, прокручивая в голове все эксперименты, я понял что мы не на тех таблицах проводим тесты и слишком "дословно восприняли" предупреждение MariaDB по поводу reacreate
. А позже я выяснил что OPTIMIZE TABLE
не накладывает блокировки на таблицу и из нее можно читать и писать во время операции, что весьма странно для recreate
.
Выясняя причины увеличения файлов после OPTIMIZE TABLE
мы наткнулись на это обсуждение, где ребята столкнулись с аналогичной ситуацией и предлагают решение в виде ALTER TABLE, который действительно создает новую таблицу и копирует в нее данные. И к тому же создает блокировку на запись в эту таблицу, причем на все время выполнения операции.
Мы попробовали, на раздутой после OPTIMIZE
таблице и получилось ее сократить на 1гб
(~3%
) даже меньше чем она была до всех манипуляций.
А теперь разберем что не так было в понимании OPTIMIZE TABLE
:
Во-первых,
OPTIMIZE
не делает пересоздание таблицы (либо я не правильно понимаю слово recreate), а работает с текущими файлами, иначе быALTER TABLE
не смог уменьшить таблицу.Во-вторых,
OPTIMIZE
не производит "мелочную" дефрагментацию - если в таблице 1000 записей, а потом было 100 удалений в разных местах, и нет единого и непрерывного пула удаленной памяти, то не будет сокращения места.
Как-то так я визуализирую свое представление про OPTIMIZE TABLE
на TokuDB:

Резюмируем предварительный вывод по
OPTIMIZE TABLE
: эта операция действительно удаляет удаленные данные, но только если их много и скорее всего если они в непрерывном пуле памяти.
Кажется варианты решения есть, но давайте поговорим про альтернативы.
Изменение схемы без простоя
Выясняя как нам быть дальше мы нашли pt-online-schema-change - тулзу для применения ALTER TABLE
без даунтайма. Суть простая:
скрипт тулзы создает новую таблицу
копирует данные из старой таблицы в новую
устанавливает свои триггеры на старую таблицу чтобы изменения в старой таблице во время копирования перенеслись в новую таблицу
удаляет старую таблицу и переименовывает новую

Кроме pt-online-schema-change
есть еще gh-ost и прочие. Мы потестировали, нас устроило. Идем дальше.
Подготовка к работам на проде
Перед тем как шатать прод выполнять все операции на боевой БД, мы провели все на тестовом стенде, который сделали пару месяцев назад с прода. Результаты на скрине из таблицы:

Мы выделили 4 наиболее объемных таблицы, в трех из них действительно происходили удаления, и только 2 из них с высоким КПД: дают ощутимый профит в освобождении места на диске и выполняются меньше всего.
И кстати, мы использовали OPTIMIZE LOCAL TABLE
, чтобы операция не попала в бинлог и не заехала на реплики. Зачем? Там много обстоятельств сложились воедино: не хотели ломать реплики, к тому же нужен запасной вариант переезда, на случай если сломаем прод. А еще эти реплики заканчивают свой жизненный цикл, потому что мы собираемся переезжать на другие сервера.
Мы согласовали ночные работы с командой инженеров и менеджеров и настал тот час ...

Работа на проде и профит
Вместо тысячи слов - скрины:


А что в итоге?
Перед сдачей задачи я решил еще раз провести анализ проделанной работы и сделать выводы.
Я понял, что изначально была неправильно взята метрика размера БД, взял на основании того что выдает сама БД, а надо было на основании диска на сервере и сколько занимается каждая таблица на диске. Впоследствии я написал скрипт обрабатывающий вывод ls -la /var/lib/mysql/*.tokudb
и объединяющий размеры файлов по таблицам. Но на старте почему-то я до этого не догадался.
Однако, несмотря на неверную сравнительную метрику, у нас был развернут бэкап продовой БД, с которого уже можно было взять правильную метрику. Проведя нехитрые расчеты (df -h
на бэкапе и проде) получилось что за все время выполнения мы освободили ~10%
диска.

Если опять вернуться к списку ТОП 20 выросших таблиц, вспомнить про график роста самой жирной таблицы, то можно понять что стало больше приходить данных. В конечном итоге я насчитал что объем БД за год вырос на ~32% (здесь уже по табличное сравнение до и после на основании файловой системы). И несмотря на рост, нам удалось освободить ~10%
диска, а значит мы удалили ~42%
данных.
Написал статью и выполнял задачу Бутурлин Виталий (сайт, телеграм-канал).