Миграция с MySQL на PostgreSQL
Эта инструкция описывает перенос действующего проекта iEXExchanger с MySQL на PostgreSQL 18 с помощью встроенного инструмента iEX DB Migrator.
Мигратор создаёт структуру PostgreSQL, переносит данные, проверяет результат и переключает подключение проекта только после успешного завершения основных проверок. При необходимости отдельно переносится база Laravel Pulse.
Для обычной миграции используется пустая PostgreSQL-база, заранее созданная в FASTPANEL. В конфигурации необходимо указать:
"schema_mode": "empty"Другие режимы работы со схемой предназначены для технических специалистов и описаны отдельно в конце документа.
Перед началом выполните инструкцию
В FASTPANEL должны быть созданы пустые PostgreSQL-базы и отдельные пользователи для основной базы и Laravel Pulse.
Внимание
Не удаляйте исходную MySQL-базу после переноса.
Она понадобится для контрольной проверки и возможного возврата проекта до начала записи новых данных в PostgreSQL.
Как проходит миграция

Что переносит мигратор
iEX DB Migrator переносит:
основную базу iEXExchanger;
отдельную базу Laravel Pulse;
таблицы и колонки;
все строки выбранных таблиц;
первичные ключи;
уникальные ограничения;
обычные и составные индексы;
внешние ключи;
CHECK-ограничения;identity и serial sequences;
generated columns;
аналоги
ON UPDATE CURRENT_TIMESTAMP;значения JSON с преобразованием в
jsonb;числовые, строковые, временные и логические значения.
Перед переносом проверяются:
версии MySQL и PostgreSQL;
кодировки баз;
права пользователей;
структура таблиц;
определения колонок;
индексы и ограничения;
unsigned-значения;
zero-date;
значения
TIME;JSON;
generated columns;
числовые диапазоны;
количество строк.
После переноса мигратор сравнивает структуру и вычисляет SHA-256 digest данных выбранных таблиц.
Если обнаружена неподдерживаемая структура или значение, миграция останавливается с ошибкой.
Промпт для выполнения миграции
Этот промпт можно скопировать и передать техническому специалисту или агенту, который имеет доступ к серверу.
Перед началом
Проверьте:
PostgreSQL имеет версию
18.x;PostgreSQL работает и доступен на локальном порту;
PHP 8.4 видит модули
pdo_pgsqlиpgsql;PostgreSQL добавлен в FASTPANEL;
PostgreSQL-сервер имеет статус «Доступен»;
создана пустая основная PostgreSQL-база;
создан отдельный пользователь основной базы;
при необходимости создана пустая база Laravel Pulse;
база и пользователь Pulse называются
pulse_pg;PostgreSQL-базы используют кодировку
UTF8;исходная MySQL доступна;
в Backend-проекте присутствуют
.envиartisan;на сервере достаточно свободного места;
подготовлено окно обслуживания.
До переноса не запускайте Laravel migrations и Product Updates для пустых PostgreSQL-баз.
Иначе базы перестанут быть пустыми и не пройдут проверку режима empty.
Файлы системы
tools/db-migrator/iex-db-migrator
Основная команда переноса
tools/db-migrator/migration.example.json
Пример конфигурации
tools/db-migrator/db-migrator.json
Приватная конфигурация переноса
storage/logs/db-migrator/
Постоянные логи мигратора
tools/db-backup/iex-db-backup
Инструмент резервного копирования
tools/db-backup/db-backup.json
Приватная конфигурация резервного копирования
storage/logs/db-backup/
Логи резервного копирования
tools/db-auditor/iex-db-auditor
Аудит PostgreSQL после обновления
Импорт и экспорт SQL выполняются через iex-db-backup.
Не используйте мигратор как инструмент обычного импорта, экспорта или резервного копирования.
Какие пользователи используются
Во время переноса задействованы разные учётные записи.
root
Пользователь операционной системы, от которого запускается мигратор
Пользователь MySQL приложения
Чтение исходной базы
Пользователь MySQL lock
Получение глобальной блокировки записи, если недоступен /root/.my.cnf
fastuser
Управление PostgreSQL через FASTPANEL
Пользователь PostgreSQL приложения
Подключение к новой основной базе
pulse_pg
Подключение к новой базе Laravel Pulse
Владелец сайта
Запуск Laravel-команд и процессов проекта
Пример:
Не указывайте fastuser в target.username.
Для приложения используется пользователь, созданный вместе с соответствующей базой в FASTPANEL.
Основной режим схемы — empty
Для клиентской миграции используется:
Этот режим предназначен для пустых PostgreSQL-баз, заранее созданных в FASTPANEL.
При запуске мигратор:
подключается через пользователя из
target.username;проверяет версию PostgreSQL;
проверяет кодировку
UTF8;проверяет права на создание объектов;
проверяет отсутствие пользовательских таблиц;
создаёт структуру PostgreSQL;
переносит данные;
создаёт индексы, ключи и ограничения;
настраивает sequences;
выполняет точную проверку.
Мигратор не меняет владельца базы и не удаляет саму базу при обычной ошибке переноса.
Не используйте schema_mode=existing для пустой базы FASTPANEL.
Режим existing ожидает, что необходимые таблицы уже созданы. Для стандартной миграции используйте только empty.
Подготовка конфигурации
Подключитесь к серверу
Подключение к серверу по SSHМигратор запускается от пользователя root, чтобы точный режим мог получить MySQL read-lock через защищённый файл /root/.my.cnf.
Перейдите в Backend-проект
Файлы сайта в FastPanelПример:
Проверьте текущую директорию и обязательные файлы:
Пример db-migrator.json
После сохранения повторно установите права:
Откуда брать реквизиты
main.source.database
Текущее DB_DATABASE из .env
main.source.username
Текущее DB_USERNAME
main.source.password
Текущее DB_PASSWORD
main.target.database
Основная PostgreSQL-база из FASTPANEL
main.target.username
Пользователь основной PostgreSQL-базы
main.target.password
Пароль пользователя PostgreSQL
pulse.source.database
Текущее PULSE_DB_DATABASE
pulse.source.username
Текущее PULSE_DB_USERNAME
pulse.source.password
Текущее PULSE_DB_PASSWORD
pulse.target.database
pulse_pg
pulse.target.username
pulse_pg
pulse.target.password
Пароль пользователя pulse_pg
Посмотреть основные параметры .env без вывода паролей:
Не меняйте рабочий .env вручную.
При switch_env=true мигратор самостоятельно переключит подключение после успешного переноса и проверки.
Если Laravel Pulse не используется
Отключите профиль:
После этого команда с параметром --profile all обработает только основную базу.
Если Pulse настроен не у всех проектов, профиль можно оставить необязательным:
При отсутствии доступной базы мигратор должен явно вывести:
Отдельный запуск с параметром --profile pulse при недоступной базе завершится ошибкой.
Политики миграции
Для стандартной клиентской миграции используйте следующие значения:
exact
true
Включает точную проверку и MySQL read-lock
maintenance
false
Мигратор не управляет Laravel maintenance mode
switch_env
true
Переключает .env после успешного переноса
workers
3
Количество параллельно обрабатываемых таблиц
schema_mode
empty
Заполняет пустые PostgreSQL-базы
allow_non_empty
false
Запрещает перенос в непустую базу
allow_target_reset
false
Запрещает удаление PostgreSQL target
Значение maintenance=false не означает, что миграцию можно выполнять без остановки записи.
Оно означает только то, что мигратор не выполняет команды php artisan down и php artisan up. Остановить пользовательские и фоновые процессы записи нужно отдельно.
Как работает точная блокировка MySQL
Для получения согласованного снимка мигратор использует:
Пока блокировка активна, MySQL не принимает:
INSERT;UPDATE;DELETE;изменение структуры таблиц;
другие DDL-операции.
Чтение данных продолжает работать.
Блокировка удерживается до завершения переноса, проверки PostgreSQL, обработки Pulse и переключения .env.
Подключение через /root/.my.cnf
При запуске от пользователя root мигратор проверяет:
Файл должен:
принадлежать пользователю
root;иметь права
0600или строже;не быть символической ссылкой;
содержать секцию
[client]или[mysql].
Мигратор читает из файла только имя пользователя и пароль. Пароль не выводится в консоль и постоянный лог.
Проверка конфигурации
Выполните:
Команда не подключается к базам и не изменяет данные.
Она проверяет:
формат JSON;
обязательные поля;
драйверы;
настройки профилей;
политики безопасности;
неизвестные параметры.
Исправьте все найденные ошибки до продолжения.
Проверка PostgreSQL-баз
Выполните:
Команда не блокирует MySQL и не переносит данные.
Она проверяет:
PostgreSQL имеет версию
18.x;кодировка базы равна
UTF8;база существует;
имя пользователя и пароль подходят;
пользователь имеет право подключения;
пользователь может создавать объекты;
база доступна для записи;
база не содержит пользовательских таблиц;
база не содержит следов предыдущего переноса.
target-check должен завершиться успешно для всех обязательных профилей.
Проверка плана
Выполните от пользователя root:
Команда не получает глобальную блокировку и не записывает данные.
Она проверяет:
соединение с MySQL;
учётную запись для read-lock;
наличие требуемых прав;
версию и кодировку MySQL;
таблицы и колонки;
индексы и внешние ключи;
generated columns;
значения JSON;
zero-date;
числовые диапазоны;
количество строк;
объём данных;
режим схемы;
число workers;
параметры будущего
.env.
Перед переносом должны успешно завершиться:
Создание резервной копии MySQL
Резервная копия создаётся до запуска мигратора, пока .env указывает на MySQL.
Для миграции и резервного копирования используются разные конфигурации:
Если db-backup.json ещё не существует:
Проверьте конфигурацию:
Создайте резервную копию:
По умолчанию файлы сохраняются в каталоге:
Для каждого SQL-файла создаётся файл:
Manifest содержит:
профиль;
метку запуска;
тип базы данных;
размер файла;
контрольную сумму SHA-256;
количество таблиц;
индексы и ключи;
количество строк.
После создания резервной копии:
Проверьте наличие SQL-файлов и manifest.
Скачайте файлы с сервера.
Сохраните их во внешнем защищённом хранилище.
Не оставляйте единственную копию на сервере с проектом.
Подготовка окна обслуживания
Перед запуском run:
запретите создание новых заявок;
остановите внешний трафик, который может записывать данные;
временно остановите очереди;
остановите процессы записи курсов, логов и метрик;
не запускайте Product Updates;
не запускайте Laravel migrations;
убедитесь, что второй экземпляр мигратора не работает;
предупредите сотрудников о технических работах.
Процессы, продолжающие запись в MySQL, будут ждать снятия read-lock и могут завершиться по тайм-ауту.
Сайт должен оставаться закрытым для записи до успешного завершения отдельной команды verify.
Запуск миграции
Находясь в корне Backend-проекта и работая от пользователя root, выполните:
Параметр --schema-mode empty явно подтверждает, что перенос выполняется в пустые базы, созданные в FASTPANEL.
Во время запуска мигратор:
повторно проверяет конфигурацию;
получает MySQL read-lock;
проверяет значения источника;
проверяет пустоту PostgreSQL-баз;
создаёт PostgreSQL DDL;
проверяет DDL внутри транзакции с
ROLLBACK;создаёт таблицы;
переносит строки;
создаёт первичные и уникальные ключи;
создаёт индексы;
создаёт внешние ключи;
создаёт
CHECK-ограничения;настраивает identity и sequences;
создаёт необходимые триггеры;
выполняет
ANALYZE;проверяет структуру;
сравнивает количество строк;
вычисляет SHA-256 digest;
обрабатывает профиль Pulse;
переключает
.env;удаляет Laravel config cache;
снимает MySQL read-lock.
Не закрывайте SSH-сессию и не запускайте второй экземпляр команды.
Для длительного переноса используйте терминал с возможностью сохранить серверную сессию.
Как понять, что перенос завершился успешно
Успешный результат должен подтверждать:
профиль
mainзавершён успешно;профиль
pulseзавершён или явно пропущен;все выбранные таблицы обработаны;
все строки перенесены;
структура PostgreSQL совпала с ожидаемой;
количество строк совпало;
SHA-256 digest совпал;
.envпереключён;Laravel config cache удалён;
MySQL read-lock снят;
команда завершилась кодом
0.
Если в результате присутствует:
или:
миграция не считается завершённой.
Автоматическое переключение .env
При настройке:
мигратор записывает для основной базы:
Для Laravel Pulse:
Перед изменением создаётся резервная копия:
Проверить подключение без вывода паролей:
Не включайте сайт сразу после run.
Сначала выполните независимую команду verify.
Независимая точная проверка
До запуска приложения, Product Updates и Laravel migrations выполните от пользователя root:
Команда:
повторно получает MySQL read-lock;
заново читает исходную MySQL;
заново читает PostgreSQL;
сравнивает структуру;
сравнивает ограничения;
сравнивает sequences;
сравнивает количество строк;
заново вычисляет SHA-256 digest.
Между run и verify приложение не должно записывать данные в PostgreSQL.
Новые сессии, заявки, данные Pulse сделают базы закономерно различающимися.
Команда verify должна выполняться до Product Updates.
После Product Updates структура PostgreSQL может измениться. Для последующих проверок используется iEX DB Auditor.
Обновление продукта после переноса
После успешного verify переключитесь на владельца Backend-сайта.
Пример:
Laravel-команды запускайте от владельца сайта, а не от root.
Иначе в проекте могут появиться файлы с неправильным владельцем.
Сначала проверьте план:
Если проверка завершилась успешно, примените обновления:
Для строгой проверки можно использовать:
Если Product Updates или doctor сообщает об ошибке, не используйте --force вслепую.
Не создавайте отсутствующие таблицы или колонки вручную. Сначала изучите ошибку и постоянный лог мигратора.
Диагностика:
Последние запуски:
Проверка подключения и процессов
Проверьте PostgreSQL-подключение:
Проверьте migrations:
Проверьте Product Updates:
При необходимости очистите runtime cache:
Перезапустите очереди:
Если проект использует Horizon, Reverb, PM2 или systemd-службы, перезапустите их установленным для сервера способом.
Долгоживущие процессы необходимо перезапустить, потому что они могли сохранить старое MySQL-подключение в памяти.
Функциональная проверка
Перед возвратом клиентского трафика проверьте:
открытие административной панели;
авторизацию администратора;
список пользователей;
валюты;
резервы;
направления обмена;
платёжные системы;
мерчанты;
существующие заявки;
создание тестовой заявки;
изменение статуса тестовой заявки;
поиск и сортировку;
задания очередей;
CRON;
Laravel Pulse;
Reverb;
отправку уведомлений;
логи приложения.
Отдельно проверьте функции, поведение которых может зависеть от конкретной базы данных:
сортировку строк;
полнотекстовый поиск;
фильтры;
JSON-поля;
даты и время;
generated values;
автоматические временные метки.
Сортировка строк и полнотекстовый поиск в MySQL и PostgreSQL могут работать по-разному даже при точном переносе значений.
Аудит PostgreSQL
После Product Updates строгий verify с MySQL больше не используется, потому что структура PostgreSQL могла измениться.
Выполните проверку конфигурации аудитора:
Запустите аудит:
Аудитор проверяет:
таблицы;
первичные ключи;
внешние ключи;
индексы;
sequences;
triggers;
статистику;
блокировки;
длительные транзакции;
невалидные ограничения;
неготовые индексы.
Аудитор работает в режиме чтения и не изменяет базу.
Резервная копия PostgreSQL
После Product Updates и функциональной проверки создайте резервную копию PostgreSQL:
После переключения .env профили main и pulse будут использовать PostgreSQL.
Скачайте SQL-файлы и manifest с сервера и сохраните их во внешнем защищённом хранилище.
Возврат клиентского трафика
Возвращайте трафик только после того, как подтверждено:
runзавершился успешно;отдельный
verifyзавершился успешно;.envуказывает на PostgreSQL;Product Updates завершился успешно;
PostgreSQL-аудит не обнаружил критических ошибок;
очереди и долгоживущие процессы перезапущены;
основные функции приложения проверены;
резервная копия PostgreSQL создана.
После этого:
Разрешите создание заявок.
Запустите очереди и остальные процессы.
Создайте контрольную заявку.
Проверьте её обработку.
Наблюдайте за логами приложения.
Возврат подключения на MySQL
Перед переключением мигратор создаёт файл:
Посмотрите доступные копии:
Для восстановления используйте точные пути:
Команда:
атомарно восстанавливает
.env;удаляет Laravel config cache;
не удаляет PostgreSQL;
не изменяет MySQL;
не запускает PHP или Artisan.
После восстановления перезапустите очереди и другие долгоживущие процессы.
Безопасный возврат на MySQL возможен только до появления новых рабочих записей в PostgreSQL.
Если после переключения пользователи создавали заявки или изменяли данные, старая MySQL больше не содержит актуальное состояние проекта. Простое восстановление .env может привести к потере новых изменений.
Дополнительные режимы для технических специалистов
Стандартная клиентская миграция выполняется только в режиме:
Другие режимы предназначены для нестандартной инфраструктуры и требуют понимания PostgreSQL, прав ролей и структуры проекта.
empty
Заполнить пустую базу, заранее созданную в FASTPANEL
auto
Автоматически создать роль, промежуточную базу и финальную базу
existing
Использовать полностью подготовленную заранее структуру PostgreSQL
Режим auto
Режим может использоваться, когда мигратор должен самостоятельно создать PostgreSQL-роль и базы.
Он требует административных прав PostgreSQL и не является стандартным вариантом для баз, созданных через FASTPANEL.
Не используйте этот режим только ради автоматизации обычного клиентского переноса.
Режим existing
Режим предназначен для базы, в которой необходимая PostgreSQL-структура уже полностью создана:
Мигратор проверяет существующую структуру перед переносом данных.
Режим existing нельзя использовать для пустой базы.
Если PostgreSQL-база создана в FASTPANEL и не содержит таблиц, используйте schema_mode=empty.
Тестовая репетиция
Для репетиции создайте отдельные пустые PostgreSQL-базы.
Укажите тестовые имена в target:
Запустите перенос без переключения .env:
Параметр --no-switch:
переносит структуру;
переносит данные;
выполняет проверку;
не изменяет
.env;не переключает приложение.
Не используйте финальные рабочие PostgreSQL-базы для тестового запуска.
Для репетиции создаются отдельные пустые базы и отдельные пользователи.
Сброс целевой PostgreSQL-базы
Команда reset-target не используется при обычной клиентской миграции.
Для её разрешения необходимо временно установить:
После этого команда:
удалит выбранную PostgreSQL-базу целиком.
Команда не удаляет исходную MySQL и не удаляет PostgreSQL-роль.
Перед выполнением несколько раз проверьте target.database.
Для базы, созданной через FASTPANEL, после reset-target потребуется заново создать базу в панели.
После выполнения верните безопасное значение:
Команды мигратора
config-check
Нет
Проверить приватную JSON-конфигурацию
target-check
Нет
Проверить PostgreSQL, кодировку, права и пустоту
plan
Нет
Проверить источник и построить план
schema
Только указанный файл
Сформировать PostgreSQL DDL
run
Да
Выполнить перенос и при необходимости переключить .env
verify
Нет
Повторно сравнить MySQL и PostgreSQL
reset-target
Да
Удалить выбранную PostgreSQL-базу
rollback-env
Да
Восстановить .env из резервной копии
Параметры мигратора
--profile main
Обработать только основную базу
--profile pulse
Обработать только Laravel Pulse
--profile all
Обработать основную базу и Pulse
--config <path>
Указать путь к JSON-конфигурации
--env <path>
Указать путь к Laravel .env
--workers <n>
Временно изменить количество workers
--no-switch
Не переключать .env
--schema-mode empty
Использовать пустую PostgreSQL-базу
--schema-mode auto
Автоматически создать базу
--schema-mode existing
Использовать заранее подготовленную схему
--schema-out <path>
Указать путь для команды schema
--backup <path>
Указать резервную копию .env для возврата
--yes
Подтвердить команду, изменяющую данные
--version
Показать версию
Частые ошибки
Проверьте пользователя, от которого запущен мигратор:
Ожидаемый результат:
Проверьте файл:
Если файл отсутствует, настройте отдельное подключение source.lock.
Не выдавайте глобальные права обычному пользователю приложения и не отключайте exact.
Проверьте в секции target:
имя базы;
имя пользователя;
пароль;
порт;
sslmode.
Используйте пользователя, созданного вместе с PostgreSQL-базой в FASTPANEL.
Не используйте fastuser как пользователя приложения.
Проверить подключение можно командой:
PostgreSQL-база уже использовалась мигратором и не считается чистой.
Для рабочего переноса создайте новую пустую базу через FASTPANEL. Не удаляйте служебные таблицы вручную, если не уверены в происхождении объектов.
Для пустой базы выбран режим:
Используйте:
И запустите:
До переноса в ней были созданы таблицы.
Не выполняйте для пустой целевой базы:
Не загружайте SQL-схему вручную. Создайте новую пустую PostgreSQL-базу через FASTPANEL.
Удаление базы не всегда удаляет PostgreSQL-роль.
Используйте новый уникальный логин или удалите старого пользователя через FASTPANEL, только если он больше нигде не используется.
Не выполняйте DROP ROLE без проверки зависимостей.
Отключите профиль:
Не оставляйте включённый профиль с вымышленными реквизитами.
Мигратор принимает PostgreSQL 18.x.
Проверьте установленную версию и выполните инструкцию подключения PostgreSQL 18.
Точная команда verify должна выполняться до Product Updates.
Если Product Updates уже изменил структуру PostgreSQL, используйте аудитор:
Не создавайте объект вручную.
Проверьте:
завершился ли
runбез ошибок;завершился ли
verify;присутствовал ли объект в исходной MySQL;
не запускалось ли приложение между
runиverify;какую ошибку показывает doctor.
Диагностика:
Посмотрите последний постоянный лог:
Не удаляйте PostgreSQL-базу без проверки состояния переноса.
Если база была опубликована, сначала изучите лог и выполните verify.
Выполните:
Файл:
не должен быть символической ссылкой;
не должен быть доступен группе;
не должен быть доступен другим пользователям;
не должен попадать в Git.
Проверьте права:
Проверьте архитектуру:
Launcher должен выбрать соответствующий бинарный файл.
Короткая последовательность команд
Работа выполняется от пользователя root из корня Backend:
После успешного verify переключитесь на владельца сайта:
Выполните: