Использование диска дроссельной заслонки ALTER TABLE
Я начну с чего-то из MySQL Online Ограничения DDL:
Нет механизма приостановки операции DDL в режиме онлайн или дросселирования ввода-вывода или использования ЦП для онлайн-операции DDL.
Однако меня все еще интересуют решения, которые я, возможно, пропустил.
Ситуация: индексы становятся все больше и больше, и они становятся настолько большими, что не будет достаточно памяти для используемых запросов, что приведет к ускорению ввода-вывода диска, и все, чтобы спуститься в полный хаос, Созданы новые составные индексы, которые меньше, но проблема заключается в запуске ALTER TABLE
без нарушения.
Факты заключаются в следующем:
- Это таблица InnoDB.
- В таблице нет первичного ключа или уникального индекса.
- Никакая комбинация столбцов не подходит в качестве первичного ключа или уникального индекса.
- В таблице нет внешних ключей.
- Таблица разбивается на месяц (в настоящее время 50).
- Таблица должна принимать записи в любое время.
- Новые разделы 3-6 должны принимать сообщения.
- Существует столбец
id
, но это не уникально.
- Таблица состоит из приблизительно 2 миллиардов строк.
- Разделение текущего месяца является единственным, который получает записи.
- Перегородки производятся за 1 месяц; всегда есть один пустой раздел.
SHOW CREATE TABLE
(я не включил все разделы):
CREATE TABLE `my_wonky_table` (
`id` bigint(20) unsigned NOT NULL,
`login` varchar(127) DEFAULT NULL,
`timestamp` int(10) unsigned NOT NULL,
`ip` varchar(32) CHARACTER SET ascii DEFAULT NULL,
`val_1` int(10) unsigned DEFAULT NULL,
`val_2` varchar(127) DEFAULT NULL,
`val_3` varchar(255) DEFAULT NULL,
`val_4` varchar(127) DEFAULT NULL,
`val_5` int(10) unsigned DEFAULT NULL,
KEY `my_wonky_table_id_idx` (`id`),
KEY `my_wonky_table_timestamp_idx` (`timestamp`),
KEY `my_wonky_table_val_1_idx` (`val_1`,`id`),
KEY `my_wonky_table_val_2_idx` (`val_2`,`id`),
KEY `my_wonky_table_val_4_idx` (`val_4`,`id`),
KEY `my_wonky_table_val_5_idx` (`val_5`,`id`),
KEY `my_wonky_table_ip_idx` (`ip`,`id`),
KEY `my_wonky_table_login_idx` (`login`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (`id`)
(PARTITION pdefault VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
Относительно запросов: всегда SELECT
на id
, причем все, что используется для фильтрации.
Чего я бы хотел избежать:
- Отключение экземпляра базы данных.
- Дисковый ввод-вывод 100%
Я подумал об использовании инструмента pt-online-schema-change
для дросселирования, но столкнулся с отсутствием стены основного ключа. Другим решением было бы сделать это в коде, эффективно перемещая триггеры на базу кода и медленно копируя данные с использованием нескольких странных фрагментов (например, кусков данных с часами, используя столбец временной метки), потому что нет уникального индекса.
Существуют ли другие решения и/или инструменты?
Ответы
Ответ 1
- Создайте таблицу
new
, похожую на таблицу real
, но с пересмотренными индексами. Включите PRIMARY KEY
, чтобы снова не оказаться в ловушке. - Это ALTER
, но еще не "заполняется".
- В новой таблице используйте квартальные или годовые разделы для старых вещей; ежемесячно для текущих и (будущих) будущих разделов. - Это должно уменьшить общее количество разделов. Мое правило большого пальца - "не более 50 разделов". (Дайте мне знать, если у вас есть проблемы с этим планом.)
- Напишите script, чтобы медленно скопировать все данные из старых разделов в таблицу
new
. Мой совет по chunking может быть полезен здесь.
- Перед тем, как вы пойманы, создайте новый раздел. Но не копируйте его еще. Остановите "копию" script в конце предыдущего раздела.
- При завершении, за исключением этого нового раздела, прекратите запись.
- Скопируйте последний раздел. - Здесь, где шаг № 4 окупается.
- Атомный своп:
RENAME TABLE real TO old, new TO real;
. И снова включите запись.
Написание всех сценариев и упражнений на других машинах настоятельно рекомендуется. Практика может быть на небольшом подмножестве общего числа, но она должна иметь как минимум несколько разделов.
Ответ 2
Я представляю это как отдельный ответ, так как самая внутренняя часть совершенно другая.
Как и в моем другом ответе, вам нужна таблица new
с новыми индексами плюс script, чтобы скопировать все данные. Однако мясо должно имитировать триггер в вашем приложении.
К счастью, у вас есть id
, хотя это не PRIMARY KEY
. И даже если это не UNIQUE
, его можно использовать (если у вас нет тысяч строк с одним и тем же идентификатором - если вы это сделаете, мы можем поговорить дальше).
"copy script" и приложение разговаривают друг с другом.
Копия script находится в длинном цикле:
-
SELECT GET_LOCK('copy', 5), high_water_mark FROM tbl;
- (или другой таймаут)
- Скопируйте строки с помощью
id BETWEEN high_water_mark AND high_water_mark + 999
.
-
UPDATE tbl SET high_water_mark = high_water_mark + 1000;
- Временно приостановить (1 секунду?)
- Цикл до тех пор, пока не будет
ids
Приложение при чтении продолжает читать из старой таблицы. Но при написании это делает:
-
SELECT GET_LOCK('copy', 5), high_water_mark FROM tbl;
- (или другой таймаут)
- При истечении времени необходимо что-то исправлять.
- Пишите на старую таблицу - (следовательно, чтение продолжает работать)
- Если
id
<= high_water_mark
, напишите также в новую таблицу.
-
SELECT RELEASE_LOCK('copy');
Следить за прогрессом. В какой-то момент вам нужно будет остановить все, скопировать последние несколько строк и сделать RENAME TABLE
.
Я не знаю ваших оптимальных значений для тайм-аутов, сна или размера блока. Но я не думаю, что разумно, чтобы размер куска был больше 1К.
Этот метод имеет преимущества для различных изменений, которые вам могут потребоваться в будущем, поэтому держите кишки на месте.
Ответ 3
Это будет связано с тем, какой вариант и версия MySQL вы используете, но если это один поток для каждого соединения (my.cnf thread_handling=one-thread-per-connection
, который может быть по умолчанию в вашей сборке), и вы можете поместить свой ALTER TABLE
workload в новом соединении, тогда рабочая нагрузка - это уникальный PID, и вы можете использовать ionice
/renice
на нем.
У меня несколько дерьмовый ответ, но он менее инвазивен, чем другие варианты.
Если вы посмотрите ps -eLf |grep mysql
, вы можете увидеть потоки/облегченные процессы и просто нужно выяснить, какой PID принадлежит вашему конкретному соединению. Если вы подключаетесь через TCP, вы можете сопоставить свой локальный порт подключения и сопоставить его с lsof, чтобы найти конкретный поток. Возможны другие способы: w/strace, systemtap и т.д. Или запуск начального запроса, который вы можете наблюдать.
После этого вы можете использовать ionice
/renice
, чтобы повлиять на PID в системе. Вы действительно захотите убедиться, что вы зафиксируете, что такое PID, и reset хороший и приоритетный уровень впоследствии, чтобы не повлиять ни на что другое.
Как и в случае с другими, вам действительно нужно переформатировать эту таблицу в долгосрочной перспективе. Разделы полезны, но не эндшпиль, поскольку вы используете 1.3TiB онлайн-данных, и утверждаете, что вам нужно читать только из последних 3-6 разделов. Начиная с MySQL перед добавлением собственных разделов, я думаю, что это будет хорошим примером для VIEW и отдельных таблиц (атомизируйте обновление VIEW, когда вам нужно опрокинуться). Это также позволило бы тривиально переместить некоторые старые таблицы в автономное хранилище.