Supabase → Mortar 迁移手册

状态:cookbook(文档)。不提供自动化 import 工具——pg_dump 已经是好工具,迁移更多是配置 + DNS 切换问题,不是数据问题。

适用人群:现在跑在 Supabase 上的 China-market RN 应用,被 Supabase 中国大陆访问稳定性 + 海外结算问题逼着迁移。

一键评估

# Mortar CLI 自带 evaluator(规划中;现在手动跑):
mortar migrate evaluate --supabase-url=$SUPABASE_URL --supabase-key=$SUPABASE_KEY
# 输出:项目里用了哪些 Supabase feature + 对应 Mortar 等价物 + 风险点

Feature 映射表

SupabaseMortar 等价难度
Database (PostgREST API)mortar.from(...) (@mortar/client)易 — 1:1
Auth (邮箱 / OAuth)mortar.auth易 — 邮箱 OK,OAuth 各家 provider 自己接
Storage (S3-compatible)mortar.storage易 — API 形状一致,buckets/keys 直接搬
Realtime (postgres_changes / broadcast / presence)mortar.realtime (subscribe / channel().on('broadcast') / channel().track())易 — 三件套齐全,channel 名见下方坑
Edge Functionsmortar.compute中 — Deno → Node/FC 重新写
Vector (pgvector)同上 (pgvector)易 — 同扩展
Cron规划中暂时用 Mortar compute + 第三方 cron 调度
GraphQL❌ 不支持Mortar 走 REST + mortar.from(table) row API;Supabase 用 pg_graphql 的项目需要迁到 REST
Triggers / WebhooksPostgres triggers + mortar.compute
Branching规划中暂时手动 pg_dump
Stripe billingWeChat Pay / Alipay不一样 — 大陆客户走中国支付

数据迁移:三步法

1. 拷数据库

# 从 Supabase pull
pg_dump $SUPABASE_DB_URL \
  --no-owner --no-acl --clean --if-exists \
  -f supabase.sql

# Push 到 Mortar
psql $MORTAR_DB_URL -f supabase.sql

注意:

  • Supabase 用 auth.users schema 存用户;Mortar 用 app_users 表。 自己写一个 INSERT SELECT 把字段映射过来。Hash 算法都是 bcrypt, 直接搬可用,不用让用户重设密码。
  • Supabase 的 storage.objects 表是元数据;真实文件在 S3-兼容 bucket 里,用 aws s3 syncrclone 把对象拉到本地再 push 到 OSS。
  • RLS:两边隔离模型不同。Supabase 给每个 project 一个独立数据库, 租户隔离靠物理分库,库内再用 per-user RLS(auth.uid())。Mortar 是 共享库 + tenant_id 列 + RLS:隔离是平台自动加的 tenant_id 级 policytenant_id = auth.tenant_id()auth.tenant_id() 是 Mortar 版的 Supabase auth.uid(),其值就是 project 的 UUID),不是你自己 per-table 写的。所以 Supabase 里基于 auth.uid() = user_idper-user policy 不能逐字搬——Mortar V0 数据走通用 app_rows JSONB 模型,RLS 粒度是 project 的 tenant_id 而非 end-user。把原来的行级授权 意图挪到应用层(end-user JWT + 专用 endpoint),tenant_id 隔离由平台 兜底。

2. 拷存储对象

# Supabase 用 S3-compatible (内部走 R2); 用 rclone
rclone copy \
  :s3,provider=Other,access_key_id=X,secret_access_key=Y,endpoint=https://supa.r2.cloudflarestorage.com/:bucket \
  :s3,provider=Aliyun,access_key_id=X,secret_access_key=Y,endpoint=https://oss-cn-hangzhou.aliyuncs.com:keel-bundles/

3. DNS 切换 + 客户端 SDK 替换

// Before
import { createClient } from '@supabase/supabase-js';
const supa = createClient(SUPABASE_URL, SUPABASE_KEY);
const { data } = await supa.from('users').select('*');

// After
import { createClient } from '@mortar/client';
// tenant 在 host 子域名里(Supabase 的 <ref>.supabase.co 模型)——没有单独的 tenant 选项
const m = createClient({ url: 'https://<tenant>.api.mortar.appunvs.com', apiKey: '...' });
const { data } = await m.from('users').select('*');

@mortar/client 表面故意跟 supabase-js 接近——.from(table).select() / .insert() / .update() / .delete() / .eq() / .gte() 等链式 API 都 一样。

常见坑

1. Realtime channel 命名 + 收发分离

  • 行变更:Supabase 用 postgres_changes:public:tablename;Mortar 用 mortar.realtime.subscribe({table})(订阅一张表的行变更)。
  • broadcast / presence:Supabase 是 channel.on('broadcast'…) + channel.track()全在一条 WebSocket 上双向收发。Mortar 的 mortar.realtime.channel(name) API 形状一致,但底层是 SSE 收 + HTTP POST 发(Mortar 不用 WebSocket)——对调用方透明,行为等价。迁移时 channel.send({type:'broadcast',…}) / track() / presenceState() 基本照搬即可。

2. Auth provider list

Supabase 内置 20+ OAuth provider;Mortar 目前只内置 邮箱 + 微信 + Apple Sign-In。需要 Google / Facebook / GitHub 的:

  • @keel-ai/auth-session 在客户端跑 PKCE flow
  • 把拿到的 OAuth code POST 到 Mortar auth/oauth/callback 自己存

内置 OAuth 编排在规划中。

3. Edge Function 运行时

Supabase Edge Functions = Deno on Cloudflare Workers. Mortar compute = Aliyun FC (Node 20) / Tencent SCF (Node 16/18).

需要:

  • Deno-specific import (e.g. https://deno.land/x/*) 改成 npm
  • Deno.env.get()process.env.X
  • TypeScript 顶级 await — Node 20 也支持,OK 不动

4. 计费模型差异

Supabase 按月固定费 ($25/月起) + over-quota;Mortar 按用量 (¥99/月起)

  • tier 上调。文档里 pricing.md 详细对比。

真实迁移案例

下面是一个真实客户迁移 timeline(早期 dogfood 期一个 alpha 客户):

步骤时间备注
Schema dump + restore30 min12 张表 5 GB
文件对象 sync4 h80 GB OSS upload
SDK 替换半天3000 行 supabase-js 调用 → @mortar/client,主要是 channel 名
Realtime cutover30 minDNS TTL + 等 5 min
灰度 (10% → 50% → 100%)一周中间没事故
总用时8 工作日一个工程师

反向迁移

Mortar → Supabase 也支持(脱钩责任)。 数据用 pg_dump,元数据用 Mortar mortar export(规划中)导出。没有 vendor lock-in

后续会减少的坑

  • Cron → Supabase 等价物
  • Database branches → 跟 Supabase 同样 git-style