Для основной массы таблиц использовался более простой, надежный и дешевый способ миграции через SymmetricDS. В конце проекта «легкие» таблицы кончились и последние 1.5 месяца мы потратили на ручную миграцию через специально написанные очереди миграции данных. Такой подход требует больше ресурсов на разработку и внимательного тестирования.
По аналогии с SymmetricDS, очередь выполняла не только синхронизацию новых и измененных данных, но и начальную массовую загрузку исторических данных в батч режиме.
Особенно важно при работе с критичными таблицами для нас было иметь возможность отката на Oracle без потери данных после переключения на Postgres. Несколько раз мы воспользовались этой функциональностью, когда Postgres не вытянул нагрузку и пришлось переключаться обратно, до решения проблемы.
SELECT EVENT_ID, EVENT_TYPE
FROM EVENT
WHERE EVENT_DATE < :eventDate AND ROWNUM = 1 FOR UPDATE SKIP LOCKED
Для Postgres мы переписали его вот так:
SELECT EVENT_ID, EVENT_TYPE
FROM EVENT
WHERE EVENT_DATE < :eventDate FOR UPDATE SKIP LOCKED LIMIT 1
И поняли, что получили нереально большую просадку по производительности.
При этом количество записей, которые попадали под условие EVENT_DATE < :eventDate было небольшим — десятки.
Индекс по EVENT_DATE есть, но видно, что Postgres его не использует и выполняется Seq Scan.
Лечение заключается в прямом указании Postgres на использование нужного индекса путем добавления сортировки, которая не требуется с точки зрения бизнес-логики клиента:
SELECT EVENT_ID, EVENT_TYPE
FROM EVENT
WHERE EVENT_DATE < $1 order by EVENT_DATE FOR UPDATE SKIP LOCKED limit 1
В итоге получаем Index Scan:
postgres@demo:5432 (demo-service) # explain (analyze,buffers)
SELECT EVENT_ID, EVENT_TYPE FROM EVENT WHERE EVENT_DATE < now() + '30 day' order by EVENT_DATE limit 100;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Limit (cost=0.44..87.03 rows=100 width=26) (actual time=0.061..183.099 rows=100 loops=1)
Buffers: shared hit=28 read=89
I/O Timings: read=182.489
-> Index Scan using event_idx1 on event (cost=0.44..205111.55 rows=559239 width=26) (actual time=0.060..183.057 rows=100 loops=1)
Index Cond: (event_date < (now() + '30 days'::interval))
Buffers: shared hit=28 read=89
I/O Timings: read=182.489
Planning time: 0.301 ms
Execution time: 183.175 ms
Важно, что меняется не только БД, но и её тип. А значит нужен код, который может одновременно работать с двумя типами БД.
Учитывая что код разрабатывался годами и разными людьми, такая миграция представляет ещё большой риск.
И наша статья про то, как давно живущее приложение аккуратно перенести в сжатый срок на новое окружение с полной миграцией всех данных без прерывания процесса.
Как уже отмечалось в комментариях, разные сценарии миграции требуют разных подходов. В статье описана конкретная методика на примере одного типа таблиц, для которого целесообразен был именно описанный вариант.
Разумеется, переключение связанных таблиц происходило одновременно, с учетом явной связи в БД — join и неявной — по коду.
Что касается логики в БД, то мы принципиально против такого подхода в своей системе.
Oracle хороший мощный инструмент. Особенности нашей работы не позволяют использовать все его возможности. Нам важны производительность сервиса, утилизация железа, возможность дешевого масштабирования и применение автоматического тестирования.
Все это зачастую закрывает для нас такие теоретические подходы как нормальная форма, логические связи в бд и даже внешние ключи.
Все верно — сервис очень живой: постоянно добавляется и удаляется логика, связанная с профилем.
Что-то взлетает и остается навсегда, а что-то отмирает со временем и остается как рудимент.
При миграции смотрели на нагрузку на таблицу в БД и на использование в коде.
К слову, на старте было 70, а не 50 таблиц.
Но, к сожалению разработчиков самые сложные таблицы для миграции в этот список не попали :)
В тексте статьи проблема описана неточно.
Суть проблемы — если запросе есть limit, то планировщик Postgres решает, что ему дешевле искать полным перебором.
В итоге получаем full scan, несмотря на наличие индекса.
Oracle в запросах с limit корректно использует индекс.
Лечится проблема в Postgres:
• либо оборачиванием запроса в set enable_seqscan to off; set enable_seqscan to on;
• либо сортировкой (order by) по полю, для которого есть индекс.
Все неструктурированные данные, которые нельзя красиво уместить в реляционную модель:
— входные параметры платежа
— описание состояния платежа (контексты операций)
— результат платежа
— кастомные параметры профиля пользователя
Размер LOB у нас обычно варьируется от 1 до 8 Кб на одну строку.
Information
Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Функциональности пока хватает.
По аналогии с SymmetricDS, очередь выполняла не только синхронизацию новых и измененных данных, но и начальную массовую загрузку исторических данных в батч режиме.
Особенно важно при работе с критичными таблицами для нас было иметь возможность отката на Oracle без потери данных после переключения на Postgres. Несколько раз мы воспользовались этой функциональностью, когда Postgres не вытянул нагрузку и пришлось переключаться обратно, до решения проблемы.
На Oracle запрос выглядел вот так:
Для Postgres мы переписали его вот так:
И поняли, что получили нереально большую просадку по производительности.
При этом количество записей, которые попадали под условие EVENT_DATE < :eventDate было небольшим — десятки.
Посмотрели план запроса:
Индекс по EVENT_DATE есть, но видно, что Postgres его не использует и выполняется Seq Scan.
Лечение заключается в прямом указании Postgres на использование нужного индекса путем добавления сортировки, которая не требуется с точки зрения бизнес-логики клиента:
В итоге получаем Index Scan:
Учитывая что код разрабатывался годами и разными людьми, такая миграция представляет ещё большой риск.
И наша статья про то, как давно живущее приложение аккуратно перенести в сжатый срок на новое окружение с полной миграцией всех данных без прерывания процесса.
Разумеется, переключение связанных таблиц происходило одновременно, с учетом явной связи в БД — join и неявной — по коду.
Что касается логики в БД, то мы принципиально против такого подхода в своей системе.
Oracle хороший мощный инструмент. Особенности нашей работы не позволяют использовать все его возможности. Нам важны производительность сервиса, утилизация железа, возможность дешевого масштабирования и применение автоматического тестирования.
Все это зачастую закрывает для нас такие теоретические подходы как нормальная форма, логические связи в бд и даже внешние ключи.
Что-то взлетает и остается навсегда, а что-то отмирает со временем и остается как рудимент.
При миграции смотрели на нагрузку на таблицу в БД и на использование в коде.
К слову, на старте было 70, а не 50 таблиц.
Но, к сожалению разработчиков самые сложные таблицы для миграции в этот список не попали :)
Суть проблемы — если запросе есть limit, то планировщик Postgres решает, что ему дешевле искать полным перебором.
В итоге получаем full scan, несмотря на наличие индекса.
Oracle в запросах с limit корректно использует индекс.
Лечится проблема в Postgres:
• либо оборачиванием запроса в set enable_seqscan to off; set enable_seqscan to on;
• либо сортировкой (order by) по полю, для которого есть индекс.
Ссылки по теме:
1) https://www.postgresql.org
2) stackoverflow
А вот по SymmetricDS потребовалась помощь их саппорта.
— входные параметры платежа
— описание состояния платежа (контексты операций)
— результат платежа
— кастомные параметры профиля пользователя
Размер LOB у нас обычно варьируется от 1 до 8 Кб на одну строку.