Компрессия и голденгейт
Представим ситуацию. У нас есть табличка, которая содержит информацию о заказах. Когда‑то заказов в табличке были сотни, потом тысячи, десятки тысяч, и в какой‑то момент оказалось, что заказов у нас миллиарды, а бизнес не хочет удалять исторические данные. При этом место у нас не резиновое и стоит дорого.
Тогда мы вспоминаем, что Oracle предоставляет неплохой инструмент для сжатия данных в таблицах — от классических Advanced/OLTP/Basic до специфических компрессий, которые есть на Exadata: FOR QUERY HIGH/LOW и FOR ARCHIVE HIGH/LOW. И это хорошо.
Но в таблицу идёт активная вставка данных (ведь у нас очень много покупателей и заказов), плюс на таблице выполняются операции обновлений, потому что заказы могут меняться, отменяться и т.д. Поэтому, если мы просто сожмём таблицу на нашей замечательной Exadata, мы, конечно, получим выигрыш в месте (возможно, даже 20‑кратный), но при этом полностью «убьём» приложения, которые изменяют таблицу.
Остановимся на том, что наша таблица заполняется на неком хранилище репликатом GoldenGate. С таблицей работает координированный репликат, потому что если не распараллеливать вставки и обновления, то у нас начинает накапливаться отставание.
Как только мы сожмём таблицу, наш координированный репликат начнёт выдавать ошибку ORA-00060 (deadlock detected) и падать на взаимоблокировках: одновременно несколько потоков пытаются изменить один и тот же блок.
Но выход есть. Надо таблицу партиционировать. Мы предполагаем, что заказы старше определённой даты уже не могут быть отменены или изменены. Мы партиционируем таблицу по интервалу в один месяц, сжимаем «старые» партиции, которые не будут меняться, той компрессией, которую мы выбрали, а новые партиции оставляем как есть — несжатыми.
По мере заполнения партиций мы можем автоматически сжимать устаревающие партиции. Для этого нам надо предусмотреть, чтобы индексы (для работы репликата нужен уникальный индекс в любом случае) были локальными.
Пример реализации
Создадим таблицу заказов, партиционированную по диапазону дат с интервалом в один месяц:
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
order_date DATE,
customer_id NUMBER,
order_status VARCHAR2(20),
...
)
PARTITION BY RANGE (order_date) INTERVAL (INTERVAL '1' MONTH)
(
PARTITION p_historical VALUES LESS THAN (TO_DATE('2020-01-01','YYYY-MM-DD'))
);
Затем создадим локальный уникальный индекс на поле order_id, необходимый для работы репликации:
CREATE UNIQUE INDEX orders_pk ON orders (order_id, order_date) LOCAL;
Теперь можно сжать партицию, данные в которой уже не меняются. Например, для партиции за январь 2020 года:
ALTER TABLE orders
MOVE PARTITION p_2020_jan
PARALLEL 16
COMPRESS FOR ARCHIVE HIGH;
SELECT 'ALTER INDEX ' || INDEX_NAME || ' REBUILD PARTITION ' || PARTITION_NAME || ' PARALLEL 16;'
FROM DBA_IND_PARTITIONS IP
WHERE IP.STATUS = 'UNUSABLE';
Это сжатие не затронет активные партиции, и репликат продолжит работать без конфликтов за блоки.
Таким образом, партиционирование в сочетании с выборочным сжатием позволяет эффективно экономить место, не жертвуя производительностью операций DML и не создавая проблем для параллельной репликации.