Pull to refresh

Comments 19

Спасибо. Да, такое решение напрашивается. Однако в нашем случае получалось печально.
OPTIMIZE TABLE для InnoDB делает recreate + analyze, что требует блокировки таблицы.
Происходило следующее: для получения блокировки СУБД ждала завершения запросов и коммита транзакций. При этом в БД без остановки ходил Clickhouse для синхронизации словарей - длинные запросы были всегда, были и больше транзакции. Пока ждали - запросы от клиентов накапливались, причём как запросы в БД, так и запросы к веб-серверу. Сама таблица довольно большая, сервер неплохо нагружен - на перестроение требовалось время. Нагрузка на сервер и очередь в СУБД непрерывно росли. Если при этом на сервере запускалось что-то потреблявшее RAM мог появится oem killer, и появлялся, убивал СУБД, после чего она не могла стартануть тк появлялись битые данные. Значит стартуем СУБД в режиме восстановления, пока чиним не принимаем запросы в веб-сервер - лежим по полной программе. То есть нужна целая группа подготовительных операций, требующих частичной остановки сервисов, чтобы всё прошло гладко.
Поэтому нужно было другое решение.

А почему не воспользовались percona toolkit? Там утилиты для подобных работ, которые не блокируют таблицы

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

OPTIMIZE TABLE для InnoDB делает recreate + analyze, что требует блокировки таблицы
В MySQL 8 уже не требует полной блокировки и работает местами даже не в один поток.

Побольше бы таких статей на Хабре. Михаил, спасибо за интересное чтиво.

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

Пока читал вспомнил случай с этой же проблемой.

Байка

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

Так вот, в один прекрасный день этот отчёт вместо одного часа начал завис почти на сутки. Сначала мой знакомый обратился по моему совету к тому, кто на мой взгляд "шарил" в MariaDB. Он как раз получил совет - OPTIMIZE TABLE. Собственно это и было сделано. Но проблема возникла снова уже через месяц и задача уже зависла на почти 2 суток. Сначала в лоб было предложено решение делать OPTIMIZE TABLE перед каждым запуском таски. Но как оказалось, операция была тоже не быстрая и хотелось найти проблему.

Собственно, я спросил что менялось за последние 3-4 месяца в системе. Ничего существенного не было. Изменения были в разработке - стали чаще релизить на прод, от чего количество уведомлений в систему увеличилось. Ещё одно изменение было в том, что из-за частых релизов, начали происходить частые откаты. Не буду в даваться в подробности, но мы снова обратились к знакомому "эксперту". Вариант вызывать OPTIMIZE TABLE каждый день не понравился нам обоим, поэтому мы начали копать дальше.

Самое забавное, когда оказалось, что при переходе этой самописной системы с чего-то там и палок на фреймворк, они создали ORM соответствующий имеющимся таблицам. Но фреймворк по умолчанию как раз для миграций использует varchar, поэтому при скромном изменении длины в большую сторону, миграция создалась автоматически и изменила тип поля. Это было много раньше (почти год до начала событий), но проявилось после того как стали чаще релизить проект.

Я проникся уважением к админу, который писал изначально эту систему под себя, когда мы подключили его к проблеме (он и указал на решение). Он как раз сказал, что при планировании таблицы у него было желание сразу использовать varchar, но он заметил, что комментарий после закрытия всегда есть и он осмысленный (занимает почти весь размер), поэтому такая оптимизация бессмысленна, а char "ровнее в память ложится" (цитата). Вот что значит понимать как устроен современный CPU!

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

От статьи сложилось мнение, что вы пожинаете плоды изначально плохой архитектуры. Какой rps на обновление данных? Почему бы не использовать партиционирование для "удаленных" строк. Объемы базы весьма скромные, что бы создавать такие проблемы. Почему был выбран xml для хранения настроек? Как по мне, это совсем неудачный формат - большой объем плохо писать и читать. Насколько динамична структура файла настроек?

Сама статья хорошо показывает, что динамический размер строки в БД, в часто обновляемой таблице, является злом, но, ИМХО, вы пытаетесь лечить последствия, а не саму проблему.

Спасибо за интерес.

От статьи сложилось мнение, что вы пожинаете плоды изначально плохой архитектуры.

С "изначально плохой" архитектурой всё в проекте "хорошо", о чём есть пояснения в статье. Архитектуру и немалый беклог мы получили от аутсорсеров. В момент обострения проблемы (фаза 3), решались другие более важные для бизнеса проблемы.

Почему бы не использовать партиционирование для "удаленных" строк. 

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

Почему был выбран xml для хранения настроек?

Как это часто бывает он не был выбран. Он уже был, причём связывал 2 системы.

Как по мне, это совсем неудачный формат - большой объем плохо писать и читать.

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

Насколько динамична структура файла настроек?

В обсуждаемом периоде 2-4 изменений в месяц.

ИМХО, вы пытаетесь лечить последствия, а не саму проблему.

На момент проблемы фаза 2(на графике) мы работали с условиях дефицита времени и ресурсов. В момент 3 и на начало фазы 4 добавился ещё дефицит персонала, решение позволило забыть о проблеме на продолжительное время. За это время были сделаны важные для бизнеса задачи. Когда появились ресурсы и время - фаза 5, время сократили в 10 раз до 2х минут и вопрос стал неактуален в любом разрезе, тк для ядра обновление чаще чем каждые 5 минут было излишним.

Спасибо, что поделились опытом !

По ходу чтению возник вопрос: если возникнет потребность увеличить размер поля : char(44) -> char(96) , что произойдет в таком случае, испортится ли описаная идеалистическая картина хранения ?

Если у вас был char и будет char иделическая картина сохранится, но не будет лучше чем была. В некоторых случаях можно перестать быть char, но это вероятно не ваш случай.

Хотел бы обратить внимание что сия проблема в этом виде рождается и таким способом решается (замена varchar на char) исключительно в блокировочниках. В версионниках новая версия записи никогда не пишется на старое место, всегда на новое, и озвученная проблема либо не возникнет, либо возникнет, но таким методом не решится.

Это камент, на случай если кто-то попытается сделать вывод что "varchar это плохо". Это плохо только на конкретном сервере, на конкретном движке, в определенного типа приложениях.

Вообще-то InnoDB - версионник. И при этом, внезапно, пишет новую версию на старое место. Просто старую версию уносит в другое место.

Oracle делает так же.

"Строки фиксированной длины безусловно имеют значительные минусы"

Очень бы хотелось эти минусы еще прочитать

Паддинг не бесплатный, особенно, если у вас 95% записей имеют длину 7-10 символов, оставшиеся 5 процентов, скажем, под сотню.

Из чисто спортивного интереса любопытно было-бы узнать как в аналогичной ситуации покажет себя Postgres.

Ситуацию as is не воспроизвести. Но планы посмотреть хотя бы на синтетически данных есть. Но это не точно.

У постгреса есть автовакуум, который будет снижать фрагментацию. А без него будет почти то же самое. Фактически recreate + analyze это постгресовый вакуум фулл.

В постгресе всё воспроизводится на 146%.

В нём любое изменение создаёт новую версию (читай - копию) строки. Даже UPDATE test_data_varchar_with_update SET height = height + 1 (изменение значения в int колонке) удвоит размер занимаемого места, а потом придёт дефрагментатор (автовакуум) и почистит мусор.

Погонял немного запросы, получил такое:

CREATE TABLE test_data_varchar_with_update (
     id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY ,
     id_pointer varchar(100) DEFAULT NULL,
     name text NOT NULL,
     width int NOT NULL DEFAULT 0,
     height int NOT NULL DEFAULT 0
)WITH (autovacuum_enabled=off);

insert into test_data_varchar_with_update (id_pointer, name, width, height)
select random()::text, round(random() * 100000)::text, (random() * 20000)::int, generate_series(1, 1000000);

create table t2 as select * from test_data_varchar_with_update;
alter table t2 add primary key (id);

UPDATE test_data_varchar_with_update SET height = (random() * 1000000)::int;
UPDATE test_data_varchar_with_update SET name = substr(repeat(md5(random()::text), 8), 0, 10 + (random() * 240)::int);
UPDATE test_data_varchar_with_update SET name = substr(repeat(md5(random()::text), 8), 0, 10 + (random() * 240)::int);

select pg_size_pretty(pg_relation_size('t2')); // 72 MB
select pg_size_pretty(pg_relation_size('test_data_varchar_with_update')); //526 MB
explain analyze select * from test_data_varchar_with_update where id in (select (random()*100000)::int as id from generate_series(1, 1000));
explain analyze select * from t2 where id in (select (random()*100000)::int as id from generate_series(1, 1000));

Последние два стартуют с 70 мс vs 20 мс, но после 5 - 10 запросов устаканивается на 15 - 30 мс vs 10 - 15 мс.

Хоть кто-то проверил в реальном примере то, о чем я говорил на хабре уже не раз. Особенно камраду @FanatPHP. В нашем случае, мы знаем, что некоторые наши данные не меньше условных 128 не больше 255, и жертвуя местом используем varchar 255, но fixed row format и AriaDB Engine (по факту это char, ибо место на строку фиксируется максимальным размером данных). Зато скорость и отсутствие фрагментации. Ибо на 30 миллионах строк optimize на нашем древнем железе (Baremetal: Xeon E5-2609, 32Gb RAM. MariaDB VM: 4 core, 12GB RAM) будет делаться сутки :-)

Sign up to leave a comment.

Articles