Rời Supabase không mất dữ liệu: Quy trình thực chiến cho dev

22/05/2026 · P T P · Chung

Di chuyển khỏi Supabase không mất dữ liệu: quy trình thực chiến cho đội dev

Supabase giúp đội dev đi nhanh: Postgres managed, Auth, Storage, Realtime, Edge Functions, API tự sinh. Nhưng đến lúc sản phẩm lớn hơn, yêu cầu khác hơn: cần kiểm soát hạ tầng sâu hơn, chi phí khó đoán, lock-in tính năng, compliance, latency vùng riêng, hoặc muốn tự vận hành trên AWS/GCP/Hetzner/Kubernetes. Khi đó, câu hỏi lớn không phải “có migrate được không”, mà là migrate không mất dữ liệu, không vỡ auth, không downtime dài bằng cách nào.

Bài này đưa quy trình thực chiến. Trọng tâm: Postgres, Auth, Storage, Realtime, API, cutover. Mục tiêu: dữ liệu toàn vẹn, rollback được, đội dev ngủ được.

1. Kiểm kê Supabase trước khi động vào dữ liệu

Đừng dump database ngay. Trước hết, lập bản đồ phụ thuộc.

Thành phần cần kiểm kê

Database
– schema, table, view, materialized view
– function, trigger, extension
– RLS policy
– index, constraint, foreign key
– cron job qua pg_cron nếu có
Auth
– user, identity provider
– JWT claim
– email template
– magic link, OAuth, SSO
– bảng auth.users, auth.identities
Storage
– bucket, object path
– public/private policy
– signed URL usage
Realtime
– channel, subscription pattern
– bảng bật replication
Edge Functions
– biến môi trường
– secret
– webhook
Client
– SDK Supabase gọi ở đâu
– query phụ thuộc .from(), .rpc(), .auth, .storage
– hardcoded URL/key

Kết quả cần có

Tạo file migration plan:

docs/migration-supabase-exit.md

Nội dung nên có:

– hệ thống đích
– thời gian cutover
– người chịu trách nhiệm
– checklist backup
– checklist verify
– rollback plan
– danh sách rủi ro

Không có inventory, migration mù. Migration mù mất dữ liệu.

2. Chọn kiến trúc đích đúng trước khi migrate

Supabase là nhiều dịch vụ gộp lại. Rời Supabase nghĩa là chọn thay thế từng phần.

Database đích

Thường chọn:

– AWS RDS Postgres
– Google Cloud SQL Postgres
– Azure Database for PostgreSQL
– Neon
– Crunchy Bridge
– self-hosted Postgres

Tiêu chí chọn:

– version Postgres tương thích
– extension hỗ trợ: uuid-ossp, pgcrypto, postgis, pg_trgm, vector
– backup PITR
– replica
– network private
– monitoring
– quyền superuser hạn chế hay không

Auth đích

Các lựa chọn:

– tự viết auth bằng Postgres + JWT
– Keycloak
– Auth0
– Clerk
– Ory
– Firebase Auth

Điểm đau lớn: password hash. Supabase Auth dùng GoTrue. Không phải lúc nào chuyển password sang hệ khác cũng đăng nhập được ngay. Cần kiểm tra thuật toán hash, format, provider identity.

Cách an toàn:

– nếu hệ đích hỗ trợ import hash: import trực tiếp
– nếu không: dùng “lazy migration”
– lần đăng nhập đầu, xác thực qua Supabase cũ
– đúng password thì tạo user trong hệ mới
– sau giai đoạn chuyển, tắt Supabase auth

Storage đích

Thường chuyển sang:

– AWS S3
– Cloudflare R2
– Google Cloud Storage
– MinIO

Cần giữ:

– object key
– content-type
– metadata
– access policy
– signed URL logic

3. Backup chuẩn: dump, object, secret, cấu hình

Trước ngày cutover, làm backup nhiều lớp.

Dump schema và data

Dùng pg_dump từ Supabase connection string:

pg_dump "$SUPABASE_DB_URL" 
  --format=custom 
  --verbose 
  --no-owner 
  --no-privileges 
  --file=supabase-full.dump

Dump schema riêng:

pg_dump "$SUPABASE_DB_URL" 
  --schema-only 
  --no-owner 
  --no-privileges 
  --file=schema.sql

Dump data riêng nếu cần kiểm tra diff:

pg_dump "$SUPABASE_DB_URL" 
  --data-only 
  --format=custom 
  --file=data.dump

Dump từng bảng quan trọng dạng CSV

Dùng cho audit nhanh:

psql "$SUPABASE_DB_URL" -c "copy public.orders TO 'orders.csv' CSV HEADER"
psql "$SUPABASE_DB_URL" -c "copy public.customers TO 'customers.csv' CSV HEADER"

Backup Storage

Nếu chuyển sang S3/R2, dùng script liệt kê object qua Supabase Storage API rồi tải về hoặc stream sang bucket mới. Không chỉ copy file. Cần copy metadata.

Checklist:

– tổng số object mỗi bucket
– tổng bytes
– random sample hash
– private object test
– public URL mapping

Backup secret

Xuất:

– service role key
– anon key
– JWT secret nếu tự quản
– OAuth client secret
– SMTP config
– webhook secret
– Edge Function env

Lưu trong secret manager, không commit Git.

4. Khôi phục sang Postgres mới

Tạo database đích cùng version hoặc version cao hơn đã test.

createdb "$TARGET_DB_NAME"

Restore:

pg_restore 
  --verbose 
  --clean 
  --if-exists 
  --no-owner 
  --no-privileges 
  --dbname="$TARGET_DB_URL" 
  supabase-full.dump

Nếu lỗi extension:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

Với auth schema, cân nhắc kỹ. Nếu còn dùng Supabase Auth tạm thời, đừng sửa. Nếu chuyển auth, cần mapping user ID ổn định.

Kiểm tra sau restore

Chạy count:

SELECT schemaname, relname, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;

So sánh count nguồn/đích bằng script. Không tin cảm giác.

Kiểm tra constraint:

SELECT conname, conrelid::regclass
FROM pg_constraint
WHERE contype = 'f';

Kiểm tra sequence:

SELECT sequence_schema, sequence_name
FROM information_schema.sequences;

Sau restore, sequence có thể lệch nếu import không đúng. Fix:

SELECT setval('public.orders_id_seq', (SELECT MAX(id) FROM public.orders));

5. Xử lý RLS và quyền truy cập

Supabase dùng RLS nhiều. Khi rời Supabase, có hai hướng.

Giữ RLS

Nếu app vẫn query trực tiếp Postgres qua API layer tự viết, giữ RLS có lợi. Nhưng cần thay auth.uid()auth.jwt() vì Supabase cung cấp helper này.

Có thể tạo function tương đương dựa trên session variable:

CREATE SCHEMA IF NOT EXISTS auth;

CREATE OR REPLACE FUNCTION auth.uid() RETURNS uuid LANGUAGE sql STABLE AS $$ SELECT nullif(current_setting('request.jwt.claim.sub', true), '')::uuid; $$;

API layer set biến:

SET LOCAL request.jwt.claim.sub = 'user-id';

Bỏ RLS, chuyển logic lên backend

Hướng này dễ debug hơn, nhưng tăng trách nhiệm backend. Cần audit toàn bộ query. Không được bỏ RLS rồi để client gọi database trực tiếp.

Quy tắc: nếu bỏ RLS, phải có backend gatekeeper.

6. Di chuyển Auth không khóa user ngoài cửa

Auth là phần dễ làm khách hàng tức giận nhất.

Giữ user ID

Nếu bảng nghiệp vụ tham chiếu auth.users.id, phải giữ ID. Khi import sang hệ auth mới, dùng cùng UUID nếu hệ đó cho phép. Nếu không, tạo bảng mapping:

CREATE TABLE auth_user_mapping (
  old_user_id uuid PRIMARY KEY,
  new_user_id text NOT NULL UNIQUE,
  migrated_at timestamptz DEFAULT now()
);

Backend phải resolve mapping trong mọi request. Cồng kềnh nhưng an toàn.

Chiến lược password

Ba hướng:

1. Import hash trực tiếp nếu tương thích.
2. Lazy migration qua đăng nhập thật.
3. Forced reset password cho toàn bộ user.

Hướng 3 đơn giản nhưng trải nghiệm xấu. Chỉ dùng khi user ít, B2B kiểm soát được, hoặc yêu cầu bảo mật bắt buộc.

OAuth identity

Với Google/GitHub/Apple, cần giữ provider subject. Email không đủ. Email có thể đổi. Provider ID ổn hơn.

7. Đồng bộ dữ liệu trong giai đoạn chuyển

Nếu downtime chấp nhận được, quy trình đơn giản:

1. bật maintenance mode
2. chặn write
3. dump lần cuối
4. restore
5. verify
6. đổi traffic

Nếu không muốn downtime dài, cần sync incremental.

Cách thực tế

– dùng logical replication nếu Postgres đích hỗ trợ
– dùng Debezium nếu có Kafka/CDC stack
– dùng trigger ghi changelog rồi replay
– dùng dual-write tạm thời trong app

Dual-write dễ nghe, khó đúng. Lỗi một bên gây lệch. Nếu dùng, cần outbox pattern.

Bảng outbox:

CREATE TABLE outbox_events (
  id bigserial PRIMARY KEY,
  aggregate_type text NOT NULL,
  aggregate_id text NOT NULL,
  event_type text NOT NULL,
  payload jsonb NOT NULL,
  created_at timestamptz DEFAULT now(),
  processed_at timestamptz
);

Worker đọc outbox, ghi sang hệ mới, retry idempotent.

8. Cutover: đổi hệ thống mà không đánh cược

Trước cutover, chạy rehearsal ít nhất một lần trên staging với production dump đã ẩn dữ liệu nhạy cảm nếu cần.

Checklist cutover

1. thông báo nội bộ
2. bật maintenance mode nếu cần
3. tắt worker ghi dữ liệu
4. chạy backup cuối
5. restore hoặc apply delta cuối
6. verify count, checksum, sample business flow
7. đổi env backend sang database mới
8. đổi storage endpoint
9. đổi auth issuer/JWKS
10. deploy app
11. monitor lỗi 5xx, auth fail, DB latency
12. giữ Supabase read-only trong thời gian rollback window

Verify dữ liệu

Không chỉ count. Dùng checksum theo bảng quan trọng:

SELECT md5(string_agg(id::text || updated_at::text, ',' ORDER BY id))
FROM public.orders;

Với bảng lớn, checksum theo batch:

SELECT min(id), max(id), count(*)
FROM public.orders
GROUP BY id / 10000
ORDER BY min(id);

9. Rollback plan: phải viết trước, không viết khi cháy

Rollback cần rõ:

– khi nào rollback
– ai quyết định
– rollback mất bao lâu
– dữ liệu ghi vào hệ mới xử lý sao
– DNS/env revert thế nào

Nếu sau cutover vẫn cho user ghi vào hệ mới, rollback về Supabase cũ sẽ mất phần ghi mới nếu không replay ngược. Vì vậy, trong vài giờ đầu, có thể:

– giữ maintenance ngắn
– bật write ở hệ mới
– ghi outbox để có thể replay ngược
– hoặc chấp nhận forward-only, không rollback database, chỉ rollback app

Rollback không miễn phí. Không thiết kế trước là không có rollback.

10. Dọn lock-in trong code

Sau khi chạy ổn, thay Supabase SDK bằng layer trung lập.

Trước:

const { data } = await supabase.from('orders').select('*')

Sau:

const orders = await orderRepository.findMany()

Lợi ích:

– đổi database dễ hơn
– test dễ hơn
– không rò query logic ra UI
– kiểm soát auth tốt hơn

Tạo abstraction cho:

– auth session
– object storage
– realtime event
– database query
– signed URL

Đừng để migration kết thúc bằng hệ mới nhưng lock-in kiểu mới.

Kết luận: migrate khỏi Supabase là dự án dữ liệu, không phải đổi connection string

Di chuyển khỏi Supabase không mất dữ liệu cần kỷ luật: kiểm kê kỹ, backup nhiều lớp, restore có kiểm chứng, auth có chiến lược, storage có đối soát, cutover có rehearsal, rollback có văn bản. Đội dev nên xem đây là dự án hạ tầng quan trọng, không phải task phụ cuối sprint.

Công thức thực tế:

Inventory trước
Backup trước khi sửa
Restore trên staging
Verify bằng script
Cutover có checklist
Giữ đường rollback
Dọn code khỏi phụ thuộc SDK

Làm đúng, rời Supabase không drama. Làm vội, lỗi không nằm ở Supabase hay Postgres. Lỗi nằm ở quy trình.

#khong #lieu #supabase #thuc #trinh
Chia sẻ:
← Trước
Checklist chọn Supabase alternative: 5 tiêu chí sống còn

Bài viết tương tự

Bình luận

Chưa có bình luận. Hãy là người đầu tiên!