Files
asepharyana-hub/docs/superpowers/specs/2026-05-25-postgresql-migration-design.md
T

5.7 KiB

PostgreSQL Migration Design

Date: 2026-05-25
Scope: Migrate elysia (Drizzle ORM + MySQL) and rust (SeaORM + MySQL) apps to PostgreSQL
Approach: Direct cutover (Approach B)
Connection String: postgresql://asephs:hunterz@100.108.1.124:5432/hub


Current State

Elysia App

  • ORM: Drizzle ORM v0.45.2
  • Driver: mysql2 v3.20.0
  • Schema Location: apps/elysia/src/db/lib/schema.ts
  • Tables: users, accounts, sessions, roles, permissions, userRoles, and related junction tables
  • Database File: apps/elysia/src/db/lib/database.ts

Rust App

  • ORM: SeaORM v1.1.19
  • Feature: sqlx-mysql
  • DB Setup: apps/rust/src/infra/db_setup.rs
  • Tables: ImageCache (primary table managed by app)
  • Config: Environment-based connection string

Apps NOT Migrating

  • 9router (uses SQLite runtime, excluded per requirements)
  • nextjs (frontend, minimal DB usage)
  • leptos, solidjs, rust-auth (frontends, no DB)

Migration Steps

Phase 1: Pre-Migration (Preparation)

  1. Backup MySQL
    mysqldump -u <user> -p <db> > mysql_backup.sql
    
  2. Convert MySQL dump to PostgreSQL
    • Use pgloader or manual conversion for schema compatibility
    • Handle type conversions:
      • INTINTEGER
      • VARCHAR(n)VARCHAR(n) (PostgreSQL compatible)
      • DATETIMETIMESTAMP
      • AUTO_INCREMENTSERIAL or BIGSERIAL
    • Verify indexes and foreign keys convert correctly
  3. Test import into target PostgreSQL
    psql postgresql://asephs:hunterz@100.108.1.124:5432/hub < converted.sql
    
  4. Validate data integrity
    • Row counts match MySQL
    • Indexes exist
    • Foreign key constraints enforced

Phase 2: Code Updates

Elysia App

  1. Update apps/elysia/src/db/lib/database.ts

    • Replace mysql2 import with postgres driver
    • Change Drizzle dialect from drizzle-orm/mysql2 to drizzle-orm/postgres
    • Update connection string format
  2. Update apps/elysia/src/db/lib/schema.ts

    • Replace drizzle-orm/mysql-core imports with drizzle-orm/postgres-core
    • Change mysqlTable to pgTable
    • Adjust column types if needed (e.g., intinteger)
  3. Update apps/elysia/package.json

    • Replace mysql2 with pg (PostgreSQL driver)
    • Keep drizzle-orm and drizzle-kit versions
  4. Update environment/config

    • Change DATABASE_URL to PostgreSQL connection string

Rust App

  1. Update apps/rust/Cargo.toml

    • Replace sqlx-mysql feature with sqlx-postgres in sea-orm dependency
    • Add postgres feature if needed
  2. Update apps/rust/src/infra/db_setup.rs

    • Change DbBackend::MySql check to DbBackend::Postgres
    • Adjust SQL syntax for PostgreSQL (e.g., index creation)
    • Update error code handling (PostgreSQL uses different error codes)
  3. Update config/environment

    • Change DATABASE_URL to PostgreSQL connection string

Phase 3: Cutover (Execution)

  1. Stop all apps
    # Stop elysia
    # Stop rust app
    
  2. Export MySQL data
    mysqldump -u <user> -p <db> > final_backup.sql
    
  3. Convert and import to PostgreSQL
    # Convert dump
    # Import to PostgreSQL
    psql postgresql://asephs:hunterz@100.108.1.124:5432/hub < converted.sql
    
  4. Deploy updated apps
    • Deploy elysia with PostgreSQL driver
    • Deploy rust with PostgreSQL feature
  5. Verify connectivity
    • Test database queries from both apps
    • Check auth flow (users table)
    • Verify session management

Phase 4: Validation

  1. Smoke tests
    • User login/logout
    • Session creation and retrieval
    • Role/permission queries
    • ImageCache operations (rust app)
  2. Data integrity checks
    • Row counts match pre-migration
    • No orphaned foreign keys
    • Indexes performing as expected
  3. Performance baseline
    • Compare query times MySQL vs PostgreSQL
    • Monitor connection pool usage

Phase 5: Rollback Plan (if needed)

  1. Stop apps
  2. Restore MySQL from backup
    mysql -u <user> -p <db> < mysql_backup.sql
    
  3. Revert connection strings in apps
  4. Redeploy with MySQL drivers
  5. Restart apps

Technical Details

Elysia Driver Change

Before:

import { drizzle } from 'drizzle-orm/mysql2'
import { createPool } from 'mysql2/promise'

After:

import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'

Rust Feature Change

Before:

sea-orm = { version = "1.1.19", features = ["sqlx-mysql", ...] }

After:

sea-orm = { version = "1.1.19", features = ["sqlx-postgres", ...] }

Risk Assessment

Risk Mitigation
Data loss during migration Full MySQL backup before cutover; test import first
App downtime Cutover during low-traffic window; rollback plan ready
Connection string misconfiguration Test connection before deploying apps
Schema incompatibilities Pre-test conversion; validate indexes/constraints
Performance regression Baseline MySQL performance; monitor PostgreSQL after cutover

Success Criteria

  • All data migrated to PostgreSQL (row counts match)
  • Elysia app connects and queries work
  • Rust app connects and queries work
  • Auth flow functional (login/logout)
  • No orphaned foreign keys
  • Indexes present and performant
  • Rollback plan tested and documented

Timeline

  • Pre-migration: 30 min (backup, convert, test)
  • Code updates: 1-2 hours (driver changes, testing)
  • Cutover: 15-30 min (stop apps, migrate data, restart)
  • Validation: 30 min (smoke tests, data checks)
  • Total: ~3-4 hours (including buffer)