Перейти к содержанию

Миграция MESSAGING версии < 3.10 на 3.10+

Внимание!

Процедура миграции обязательна для обновления с версии ниже 3.10. После миграции на 3.10+ откатывать messaging на предыдущие версии не рекомендуется! Возвращение на прошлые версии приведет к сложной процедуре ручного удаления новых данных и повторения миграции. В случае возникновения проблем при миграции, необходимо обратиться в службу технической поддержки для получения патча.

Примечание

Время простоя в момент миграции может достигать от 5 до 30 минут в зависимости от объема базы.

Определение размера таблиц

Для определения размера таблиц:

  1. Войдите в веб-интерфейс администратора СУБД PostgreSQL и подключитесь к базе данных messaging_prod:

    • если база данных реализована в контейнере Docker выполните команды:
    docker exec -ti cts_postgres_1 psql -U postgres
    \c messaging_prod
    
    • если база данных внешняя (без кластера) выполните команды:
    su postgres
    psql
    \c messaging_prod 
    
    • если база данных внешняя (кластер на базе Patroni) выполните команды:
    psql -h внутренний_ип_ноды -p 5432 -U express -d postgres
    \c messaging_prod
    
  2. Выполните запрос размера таблиц базы данных:

    select schemaname as table_schema, 
        relname as table_name, 
        pg_size_pretty(pg_total_relation_size(relid)) as total_size, 
       pg_size_pretty(pg_relation_size(relid)) as data_size, 
       pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) 
          as external_size 
    from pg_catalog.pg_statio_user_tables
    where relname = 'user_chat_events' or relname = 'chat_events'
    order by pg_total_relation_size(relid) desc, 
             pg_relation_size(relid) desc;
    

    Внимание!

    Если total_size user_chat_events и/или chat_events превышает 20 Гб, то время простоя при миграции в автоматическом режиме может составлять > 1 часа. В этом случае рекомендуется провести миграцию по специальной инструкции, из раздела "Миграция больших баз данных". Для удобства рекомендуем заранее подготовить файл со всеми таймстемпами и запросами.

    Примечание

    Если таблицы в вашей базе данных меньше 20 Гб, вы можете мигрировать данные в автоматическом режиме (дополнительных действий не требуется).

Миграция больших баз данных

Некоторые запросы, выполняющиеся при подготовке к миграции, могут обрабатываться до десяти часов в зависимости от объемов базы данных. Например, миграция старой истории сообщений. В этом случае рекомендуется использовать специальные команды для выполнения долгих задач screen или nohup.

Пример использования:

  1. Добавьте запрос в текстовый файл с расширением .sql, например update.sql.
  2. Добавьте права на чтение файла пользователю от имени которого запускается psql.

Запустите миграцию в виртуальном терминале следующими способами:

  • способ 1. С помощью команды screen (рекомендуется);
  • способ 2. С помощью команды nohup (если знакомы с командой или нет возможности использовать screen).
  1. В терминале на сервере с базой данных выполните команду screen, откроется сессия терминала.
  2. Выполните следующую команду (замените значение флага -u и -f на свой):

    sudo -u postgres psql -d messaging_prod -f /home/postgres/update.sql
    
  3. Выйдите из сессии нажав сочетания клавиш Ctrl-A + Ctrl-D. Ни в коем случае не нажимайте Ctrl-C и Ctrl-D пока находитесь в screen сессии и запрос не завершился.

  4. Выполните команду screen -r чтобы вернуться к screen сессии и убедиться, что сессия работает и запрос выполняется. После чего можно выйти из сессии нажав Ctrl-A + Ctrl-D.
  5. Повторите процедуру из п. 4, чтобы проверить состояние задачи.
  6. Проверьте статус выполнения запроса (после минуты выполнения):

    SELECT
      pid,
      user,
      pg_stat_activity.query_start,
      now() - pg_stat_activity.query_start AS query_time,
      query,
      state,
      wait_event_type,
      wait_event
    FROM pg_stat_activity
    WHERE (now() - pg_stat_activity.query_start) > interval '1 minutes'
    AND datname = 'messaging_prod' AND state = 'active';
    
  7. В результате отображается запрос. Screen сессия должена быть активной.

  1. В терминале на сервере с базой данных выполните следующую команду (команда не должна требовать никакого input, например пароля от root юзера):

    nohup sudo -u postgres psql -d messaging_prod -f
    /home/postgres/update.sql &
    
  2. Проверьте статус задачи через команду jobs -l.

  3. Проверьте вывод в файле honup.out.
  4. Проверьте статус выполнения запроса (после минуты выполнения):

    SELECT
      pid,
      user,
      pg_stat_activity.query_start,
      now() - pg_stat_activity.query_start AS query_time,
      query,
      state,
      wait_event_type,
      wait_event
    FROM pg_stat_activity
    WHERE (now() - pg_stat_activity.query_start) > interval '1 minutes'
    AND datname = 'messaging_prod' AND state = 'active';
    
  5. В результате отображается запрос. Nohup процесс должен быть активным.

Подготовка к миграции

Внимание!

Перед началом операции убедитесь в достаточном количестве свободного места. Рекомендуется, чтобы объем свободного пространства в 2-3 раза превышал объем базы данных.

Примечание

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

Все запросы выполняются в psql, база данных messaging_prod.

  1. Добавьте новые колонки в таблицу chat_events:

    ALTER TABLE chat_events
    ADD COLUMN events_history_scope BOOLEAN,
    ADD COLUMN event_info_scope BOOLEAN;
    
  2. Запишите текущий timestamp. Этот timestamp необходимо сохранить до момента будущей миграции:

    messaging_test=# SELECT now();
                  now
    -------------------------------
     2023-12-25 11:06:06.037283+03
    (1 row)
    
  3. Запишите timestamp самого раннего сообщения:

    messaging_test=# SELECT inserted_at FROM chat_events ORDER BY inserted_at ASC LIMIT 1;
            inserted_at
    ----------------------------
     2018-02-05 10:21:44.191786
    (1 row)
    
  4. Выполните миграцию историй сообщений от даты, полученной из таймстемпа в п. 2 + 1 минута до даты самого раннего события п. 3 с шагом в 1 год. Каждый запрос будет выполняться длительное время, если по каким-то причинам запрос выполнить не получается, можно уменьшить шаг до полугода/трех месяцев.

    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2023-01-01' AND ce.inserted_at <= '2023-12-25 11:07';
    
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2022-01-01' AND ce.inserted_at <= '2023-01-01';
    
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2021-01-01' AND ce.inserted_at <= '2022-01-01';
    
    ...
    
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2018-02-05' AND ce.inserted_at <= '2019-01-01';
    
  5. Если total_size ваших таблиц больше 200 Гб, то быстрее будет выполнить один долгий общий запрос без временных шагов:

    --- запрос для миграции без временного шага, 
    --- выполнять только в случае очень объемных баз,
    --- обязательно с использованием nohup или screen,
    --- время выполнения может достигать > 10 часов
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed';
    
  6. Создайте новые индексы для таблицы chat_events:

    CREATE INDEX CONCURRENTLY chat_events_group_chat_id_inserted_at_stealth_index ON chat_events USING btree (group_chat_id, inserted_at) INCLUDE (sync_id, stealth) WHERE (NOT (stealth IS NULL));
    
    CREATE INDEX CONCURRENTLY chat_events_group_chat_id_inserted_at_shared_index ON chat_events USING btree (group_chat_id, inserted_at) WHERE ((events_history_scope = true) AND (shared = true));
    
    CREATE INDEX CONCURRENTLY chat_events_group_chat_id_inserted_at_non_shared_index ON chat_events USING btree (group_chat_id, inserted_at) WHERE ((events_history_scope = true) AND (shared = false));
    
    CREATE INDEX CONCURRENTLY chat_events_sync_id_event_info_scope_index ON chat_events USING btree (sync_id) WHERE (event_info_scope = true);
    
    CREATE INDEX CONCURRENTLY chat_events_epoch_migration_index_1 ON chat_events (event_type) WHERE event_type='added_to_chat' OR event_type='user_joined_to_chat';
    
    CREATE INDEX CONCURRENTLY chat_events_epoch_migration_index_2 ON chat_events (event_type) WHERE event_type='left_from_chat' OR event_type='deleted_from_chat' OR event_type='kicked_by_cts_logout';
    
  7. Отслеживание прогресса создания индексов:

    --- Отслеживание прогресса создания индексов
    SELECT phase, blocks_total, blocks_done, (blocks_total - blocks_done) as blocks_rest, tuples_total, tuples_done, (tuples_total - tuples_done) as tuples_rest FROM pg_stat_progress_create_index;
    

Миграция

Внимание!

Если этап подготовки к миграции был выполнен несколько дней назад, то необходимо мигрировать историю от текущего времени до времени начала этапа подготовки к миграции. Если вы выполняете этап подготовки к миграции и основную миграции в один день, то пропустите п. 1 и п. 2.

  1. Запишите текущий timestamp:

    messaging_test=# SELECT now();
                  now
    -------------------------------
     2023-12-27 12:14:01.026154+03
    (1 row)
    
  2. Обновите историю от времени полученного в разделе «Подготовка к миграции» п. 2:

    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2023-12-25 11:06:00';
    
  3. Остановите все сервисы, кроме postgres:

    • если база данных реализована в контейнере Docker, выполните команды:
    docker ps | grep postgres_1 | awk {'print$1'} # получаем и запоминаем id контейнера postgres
    docker stop $(docker ps -q | grep -v 00000000) # вставляем вместо 00000000 id контейнера postgres
    
    • если база данных внешняя, выполните команду:
    dpl --dc stop
    
  4. Зайдите в psql, подключитесь к базе данных messaging_prod.

  5. С помощью следующей команды пропустите миграции, которые выполняли (текст команды вводится как указано, без изменений):

    INSERT INTO 
        schema_migrations (version, inserted_at)
    VALUES
        (20231031201400, now()),
        (20231031201640, now()),
        (20231101121946, now()),
        (20231101134329, now()),
        (20231116000454, now()),
        (20231129210158, now()),
        (20231205114132, now()),
        (20231205115340, now()),
        (20231205123846, now()),
        (20231211072757, now());
    
  6. Запишите текущий timestamp:

    messaging_test=# SELECT now();
                  now
    -------------------------------
     2023-12-27 12:34:39.258821+03
    (1 row)
    
  7. Мигрируйте историю от момента подготовки миграции. Используйте дату и время с точностью до минуты:

    • из п. 1 если был выполнен процесс дополнительной миграции истории:
    ---- обновление с временем из пункта 1
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2023-12-27 12:14:00';
    
    • из п. 2 раздела «Подготовка к миграции», если дополнительной миграции истории не было:
    ---- обновление с датой из раздела "Подготовка к миграции" пункт 2
    UPDATE chat_events ce
    SET events_history_scope = uce.events_history_scope, event_info_scope = uce.event_info_scope
    FROM user_chat_events uce
    WHERE ce.sync_id = uce.event_sync_id
    AND ce.event_type != 'routing_changed'
    AND ce.inserted_at >= '2023-12-25 11:06:00';
    
  8. Запустите сервисы:

    • если база данных реализована в контейнере Docker, выполните команду:
    docker start $(docker ps -aq | grep -v 00000000) # вместо 00000000 вставляем id контейнера postgres
    
    • если база данных внешняя, выполните команду:
    dpl --dc start # дождитесь завершения автоматических миграций и запуска сервиса messaging
    
  9. Мигрируйте следующие данные используя таймстемп полученный в п. 6:

    INSERT INTO user_chat_events_v2 (user_huid, group_chat_id, event_sync_id, inserted_at)
    SELECT jsonb_array_elements_text((event_params->>'recipients')::jsonb)::uuid as user_huid, group_chat_id, sync_id, ce.inserted_at
    FROM chat_events ce
    INNER JOIN group_chats gc ON gc.id = ce.group_chat_id
    WHERE global = true AND
    shared = false AND
    chat_type = 'global' AND
    ce.inserted_at <= '2023-12-27 12:34:39';
    
    INSERT INTO user_chat_events_v2 (user_huid, group_chat_id, event_sync_id, inserted_at)
    SELECT jsonb_array_elements_text((event_params->>'recipients')::jsonb)::uuid as user_huid, group_chat_id, sync_id, inserted_at
    FROM chat_events ce
    WHERE jsonb_array_length((event_params->>'recipients')::jsonb) > 0 AND
    event_type = ANY('{message_new,call_end}') AND
    ce.inserted_at <= '2023-12-27 12:34:39';
    
    INSERT INTO user_chat_events_v2 (user_huid, group_chat_id, event_sync_id, inserted_at)
    SELECT jsonb_array_elements_text((event_params->>'recipients')::jsonb)::uuid as user_huid, group_chat_id, sync_id, inserted_at
    FROM chat_events ce
    WHERE jsonb_array_length((event_params->>'recipients')::jsonb) = 1 AND
    event_type = 'app_event' AND
    payload->>'event_type' = 'bot_notification' AND
    ce.inserted_at <= '2023-12-27 12:34:39';
    
  10. Выполните дамп системных событий из каналов. Его можно будет удалить спустя время, когда релиз проверится временем:

    CREATE TABLE channel_events_dump AS
        SELECT ce.* FROM chat_events ce INNER JOIN group_chats c ON c.id = ce.group_chat_id WHERE chat_type = 'channel'
        AND event_type = ANY('{added_to_chat,deleted_from_chat,left_from_chat,admin_added_to_chat,kicked_by_cts_logout,user_joined_to_chat}');
    
  11. Удалите лишние системные события из каналов:

    DELETE FROM chat_events ce
    USING group_chats c
    WHERE c.id = ce.group_chat_id
    AND chat_type = 'channel'
    AND event_type = ANY('{added_to_chat,deleted_from_chat,left_from_chat,admin_added_to_chat,kicked_by_cts_logout,user_joined_to_chat}');
    
  12. Если на вашем сервере использовался notifications_bot от BS (не внутренний бот) вам необходимо мигрировать его сообщения, для этого выполните следующий запрос и подставьте в качестве bot_id идентификатор notifications_bot используя таймстемп полученный в п. 6:

    INSERT INTO user_chat_events_v2 (user_huid, group_chat_id, event_sync_id, inserted_at)
    SELECT jsonb_array_elements_text((event_params->>'recipients')::jsonb)::uuid as user_huid, group_chat_id, sync_id, now()
    FROM chat_events ce
    WHERE jsonb_array_length((event_params->>'recipients')::jsonb) > 0 AND
    event_type = 'app_event' AND
    payload->>'event_type' = 'bot_notification' AND
    sender = 'bot_id' AND
    ce.inserted_at <= '2023-12-27 12:34:39';
    
  13. Обновите статистику (см. раздел "Обновление статистики").

  14. Удалите миграционные индексы:

    DROP INDEX chat_events_epoch_migration_index_1;
    DROP INDEX chat_events_epoch_migration_index_2;
    

Обновление статистики

После автоматической или ручной миграции необходимо обновить статистику. В psql выполните следующие запросы:

VACUUM ANALYZE user_chat_events_v2;
VACUUM ANALYZE chat_event_meta;
VACUUM ANALYZE chat_member_epochs;
VACUUM ANALYZE chat_events;