Модернизация legacy-системы крупного речного оператора
Услуги
- legacy-modernization
- complex-systems
- ai-development
Стек
- SvelteKit 2
- Svelte 5
- Supabase Postgres
- Hatchet
- Gotenberg
- PDFKit
- Tailwind 4
Крупный оператор речных круизов — туризм и пассажирские перевозки. Модернизация legacy PHP 5.4 + MariaDB монолита: 211 SQL миграций, 17 620 записей перенесено, 9 LIVE PDF-шаблонов. С 2024. Сохранено 8 лет истории заявок без downtime и без потери ни одного бронирования.
Один из крупнейших операторов речных круизов с оборотом десятки тысяч заявок в сезон. Legacy PHP 5.4 + MariaDB монолит за 8+ лет накопил техдолга — невозможно добавлять новые фичи, всё ломается. Задача: миграция на современный стек без потери истории заявок и без downtime.
Контекст
Систему писали разные подрядчики с 2016 года. Накопились классические проблемы:
- PHP 5.4 EOL — невозможно ставить современные библиотеки.
- MariaDB без формальных миграций — изменения схемы делались вручную через phpMyAdmin.
- Бизнес-логика в хранимых процедурах + в шаблонах — отслеживать изменения цен и скидок невозможно.
- Платёжный модуль inline в шаблонах — копипаст между разделами.
- 5 разных способов отправки уведомлений (
mail(), SMTP, telegram-бот, sms-шлюз) без единой шины. - Нет тестов. Релиз = «попробовать в проде, откатить если что».
При этом система обрабатывала тысячи заявок в сезон, и просто переписать всё с нуля означало потерять 8 лет истории заявок, цен, договоров с агентствами.
Какие вызовы решены
- Как мигрировать с PHP 5.4 на современный стек без downtime? Поэтапная миграция с прокси-уровнем: новый стек подключается к legacy-БД read-only, постепенно перетягивает функционал, затем переключается на Supabase. Без блокировки бронирований.
- Как сохранить 8 лет истории заявок и платежей? Все исторические данные мигрировали 1:1 в Supabase Postgres с маппингом ID → UUID и валидацией FK-цепочек (Sacred Integrity, contractors_without_name, Reconcile snapshot). 17 620 записей перенесено.
- Как заменить 5 систем уведомлений (mail(), SMTP, Telegram, SMS, in-app) единой шиной? Создали
shared-notificationsмодуль с adapter-pattern: один интерфейс, 5 драйверов, retry/backoff в Hatchet workflow. - Как извлечь бизнес-логику из шаблонов и хранимых процедур? Поэтапно вычленяли prices/discounts/promotions из inline-кода в Edge Functions с TypeScript. Бизнес-правила теперь покрыты тестами и версионируются в git.
- Как добавить тесты к legacy без полной переписки? Contract tests на новый Supabase API + smoke-тесты на legacy-проксируемые маршруты. Регрессии ловятся до прода.
Подход
Поэтапная миграция с сохранением старой системы в работе и постепенным переключением модулей.
- Migration_app — воспроизводимый pipeline. Extract из MariaDB → Transform (нормализация полей, исправление кодировок, выравнивание типов) → Insert в Supabase PG 17 → Verify (сравнение SQL-выборок legacy ↔ новая БД с диффом). Каждый прогон автоматически детектирует drift и предлагает фикс.
- Public_id 7-символьный для всех URL-сущностей вместо bigint. Backfill через триггеры BEFORE INSERT, без downtime. Короткие URL
/orders/Xy3kR9aвместо/order.php?id=12345. - Insert-only versioning через
valid_from. Единая модель для discounts, prices, booking_rules, promotions, document templates. Старые версии неприкосновенны. Резолвер:max(valid_from) ≤ effective_date. Никаких race conditions при изменении цен. - Universal Document Platform v2 (merged 2026-04-26, tag
pf-pdf-foundation-v1). Hatchet workflows + Gotenberg HTML→PDF + изолированная schemahatchetв общем Supabase Postgres. Заменили самописный PDFKit-pipeline. - Shared modules (shared-pay, shared-notifications, pay-service) для повторного использования между Круизным флотом и параллельным продуктом «Прогулочный флот».
- mTLS + HMAC bridge к остаточной legacy-системе для постепенной миграции — новый фронт ходит к старой PHP за остатками атомарных операций.
Результат
- 211 SQL миграций в production без блокировок (используем
CREATE INDEX CONCURRENTLY,ADD COLUMN ... DEFAULT NULL+ батч-backfill, feature-flags для rollback). - 17 620 единиц данных мигрировано (10 578 круизов + 7 042 прогулок) с побитовой верификацией.
- 312k платёжных записей скорректированы migration drift-fix процедурой за один сеанс (302 940 buh + 9 101 alfabank помечены
site_id='pf'). - 9 LIVE PDF-шаблонов в работе (1 voucher + 1 voucher_pf + 7 версий pf_excursion).
- >1000 одновременных пользователей поддерживается (требование ТЗ).
- 20+ ролей в системе (менеджер, бухгалтер, директор круиза, агент, менеджер агента, физлицо и др.) с разделением через RLS-политики.
- Бизнес-эффект. Время добавления новой фичи: было 1-3 месяца → стало 1-2 недели. Релизы: было редкие и опасные → теперь несколько раз в неделю.
Эффект для бизнеса
- Скорость релизов — раньше 1 фича = 1-2 недели и страх «всё упадёт»; теперь — 1-2 дня с тестами и rollback-планом.
- Стоимость поддержки ↓ — миграции декларативны, схема версионируется в git, не в phpMyAdmin.
- Команда не зависит от единственного PHP-разработчика, который понимает legacy.
Что мы можем сделать у вас
Если у вас legacy PHP 5.x/7.x с MySQL/MariaDB без миграций и 5+ лет истории — мы делаем поэтапную миграцию на современный стек (TypeScript + Postgres + декларативные миграции) без downtime и без потери истории. /contacts
Что использовали
SvelteKit 2 + Svelte 5 runes + Tailwind 4 + shadcn-svelte (frontend), Supabase Postgres 17 + RLS + Edge Functions Deno (backend), Hatchet TS SDK v1 (workflow engine), Gotenberg 8 (HTML→PDF), Альфа-банк SBP C2B + ЮKassa (платежи), mTLS + HMAC + Idempotency (legacy bridge), Caddy auto-TLS, systemd, Docker Compose. AI-усиление: Claude Code SDK + Hermes Stack (9 docker-сервисов) для оперативного контроля проектов.
Часто задаваемые вопросы
Что входит в проект модернизации?
Сколько занимает миграция 8-летнего legacy без downtime?
Можно ли было дописывать legacy вместо миграции?
Какие риски сохранения legacy?
Похожая задача?
Расскажите контекст — подскажу, что и как делать.
Обсудить похожий проект →