Mortar pricing — 套餐 × 规格 two-axis model
Status: v5 restructure, 2026-06 (unlocked from the 2026-05 freeze
by the two-axis redesign). Numbers in this doc are the contract;
internal/plan/registry.go
is the authoritative source. Marketing surfaces must derive from
this doc.
See also: Fabric subscription pricing (the other product’s axis) at
fabric/docs/pricing-strategy.md. Fabric is NOT a retail customer of this page — it buys wholesale as an enterprise account (see § Plans).
TL;DR
- Two axes, like Supabase and similar full-stack app clouds: a plan (套餐, account-level subscription: Free / Pro / Team / Enterprise) and a per-project instance size (规格: Nano / Micro / Small / Medium / Large — Supabase-aligned size names). Sizes are capacity shapes you attach to a project, not things you subscribe to. (Fabric Cloud, as a platform customer, defines its OWN product tiers on top — those are Fabric’s concepts, with no mapping defined here.)
- 6 cost dimensions, 1 bill: 3 BASELINE (compute / database / cache, bundled in the size’s monthly fee) + 3 METERED (storage / network / function, billed quota → overage → cap).
- 5 instance sizes: Nano 免费(不单卖 — 每个项目的起步规格)/
Micro ¥58 / Small ¥158 / Medium ¥998 / Large ¥2998 — 定额包月
(one flat
tier_feeledger deduction per billing period, idempotent; no hourly drip, no activity proration). - Existing costs nothing: a project on the free size (Nano) is never idle-billed — the Supabase model: free capacity is quota-capped, not balance-drained. Paid sizes pay the flat monthly fee whether busy or idle (包月语义), nothing else baseline-related.
- Three-stage metered billing (live since 2026-07): within size quota → ¥0 (usage still unit-accounted, so the dashboard stays truthful); quota → cap metered at per-unit rate; above cap → 507 hard cap (no surprise bills — AI agents can’t blow your budget).
- Quota rollover (default 3 months) — unused free-tier capacity carries forward; configurable per workspace.
- Caps are real: pool density is sized so the sum of DB RAM caps fits physical pool RAM with no oversubscription.
The 6 dimensions
Mortar maps directly to the cloud-bill categories you’d see on Aliyun / Tencent Cloud / AWS, split into two billing camps:
BASELINE (3 dims, bundled in tier price)
| Dim | What it is | Why bundled |
|---|---|---|
compute | Your share of the per-pool ECS instance hours | Always-on; can’t bill per-call |
database | Self-hosted Postgres slice on the same ECS | Same; baseline = your tier’s RAM/disk slot |
cache | Self-hosted Redis/KeyDB slot on the same ECS | Same; LRU evicts when full (no overage) |
The size’s monthly fee covers all three — they share the same ECS, so
per-dim splitting would be artificial. The fee is charged as ONE
idempotent tier_fee ledger row per (project, period)
(Ledger.EnsureTierFee, ticker-driven) — not an hourly drip; the
per-hour baseline rates in registry.go are the internal COGS basis
only. Nano’s fee is ¥0 (the free size).
METERED (3 dims, quota + overage + cap)
| Dim | Unit | Quota included | Overage rate | Cap |
|---|---|---|---|---|
storage | GB-month (OSS bytes) | per tier (table below) | ¥0.15/GB-月 | per tier |
network | GB outbound (egress) | per tier | ¥0.60/GB | per tier |
function | CPU-min on FaaS | per tier | ¥0.05/CPU-min | per tier |
OSS API requests (PUT/GET/LIST) are not billed separately — Mortar absorbs that pass-through cost. Typical request cost is <5% of storage GB-month, and the egress GB cap protects against abuse patterns indirectly (no single request pattern can run away without also hitting the egress cap).
Three states per metered dim:
- 0 → quota: free (covered by tier baseline)
- quota → cap: metered at per-unit rate above
- above cap: 507
tier_capacity_exhausted+ upgrade prompt
No spend cap to configure — Mortar’s hard cap is the default behavior, unlike Supabase Pro which is unlimited overage by default.
Plans(套餐)
The account-level subscription — what you SIGN UP for. Sizes below are what you ATTACH to each project. Mirrors Supabase’s plan/compute split and the plan-gated instance sizing common to full-stack app clouds.
| Plan | Fee | Instance sizes available | Notes |
|---|---|---|---|
| Free | ¥0 | Nano only | 1 project; upgrade plan to unlock bigger sizes (the free-tier size gate) |
| Pro | v0 draft 待定 | all five | bigger included quotas; numbers land with first paying cohort |
| Team | v0 draft 待定 | all five | collaboration / support; Supabase-Team analog |
| Enterprise | invoice | all five | platform integrators (e.g. Fabric): tenants are never credit-gated; usage metered as always, settled by aggregate invoice. mortar_accounts.plan = 'enterprise', live in plan.Gate |
Free 锁定最小规格、付费档五档自选 mirrors the common full-stack-cloud model (the free plan pins the smallest instance; upgrading the plan unlocks larger ones).
Every project starts on Nano regardless of plan (projects.plan
DB default; the create-project API defaults likewise) — the Supabase
shape where paid plans still start at the bottom size and sizing up is
an explicit per-project action. Since Nano is free, a fresh project
costs ¥0 until it either sizes up (fee) or exceeds a Nano quota
(overage).
Instance sizes(规格)— full table
Each size picks (a) the pool’s tenancy density, (b) the caps + quotas across all 6 dimensions. All shared sizes run on ONE pool SKU — 8 vCPU / 64 GB memory-optimized (u1-c1m8.2xlarge) + pooled thin-provisioned ESSD — only density varies, and Σ(DB RAM × density) ≤ ~56 GB usable, so caps are guaranteed, not oversubscribed.
| Size | Monthly fee(定额包月) | Density | DB RAM | DB disk | Conn | Cache | Function (quota → cap) | Storage (quota → cap) | Egress (quota → cap) |
|---|---|---|---|---|---|---|---|---|---|
| Nano | 免费(不单卖) | 80 / pool | 512 MB | 5 GB | 60 | 64 MB | 50 → 200 CPU-min | 0.5 → 10 GB | 2 → 25 GB |
| Micro | ¥58 | 40 / pool | 1 GB | 20 GB | 90 | 256 MB | 250 → 1k | 3 → 50 GB | 15 → 100 GB |
| Small | ¥158 | 20 / pool | 2 GB | 50 GB | 120 | 1 GB | 1.5k → 5k | 20 → 100 GB | 75 → 250 GB |
| Medium | ¥998 | 5 / pool | 8 GB | 200 GB | 160 | 4 GB | 8k → 20k | 80 → 250 GB | 200 → 500 GB |
| Large | ¥2998 | 1 dedicated (8c32G + 500 GB ESSD) | 16 GB | 500 GB | 240 | 4 GB | 20k → 30k | 250 → 500 GB | 500 → 1 TB |
The fee is MonthlyFeeYuan in
registry.go,
charged once per billing period (tier_fee row; idempotent; V0 has no
proration — a mid-period size change re-prices from the next period).
The per-hour rates also in registry.go (BaselineYuanPerHour: Nano
¥0.0389 · Micro ¥0.0806 · Small ¥0.2194 · Medium ¥1.386 · Large ¥4.164
≈ fee/720) are the internal COGS basis only — never billed. Nano
COGS ≈ ¥28/月 is absorbed as the free-size cost of acquisition.
Capacity caps are hard limits — exceeding them returns 507 or LRU-evicts (cache only; see Enforcement).
MAU capacity reference
Rough application-layer guidance — actual capacity depends heavily on your app type (social/chat is heavier than dashboard/SaaS). Numbers below assume typical SaaS workload (mostly idle users, sparse writes, modest caching):
| Tier | Concurrent connections | Estimated MAU | Suits |
|---|---|---|---|
| Nano | 60 | 1k - 5k | Hobby / dev / early demo |
| Micro | 90 | 5k - 20k | Side projects / small production |
| Small | 120 | 20k - 100k | Mid-size production |
| Medium | 160 | 100k - 500k | Scaling production |
| Large | 240 | 500k - 2M | Enterprise / high-traffic |
Variables that move these numbers ±5×: query complexity, cache hit rate, file-download ratio, AI agent burst patterns. For sustained high-CPU workloads, plan one tier larger than the MAU table suggests.
Quota rollover
Unused free-tier quota carries forward to next month, capped by
configurable rules. Default policy
(DefaultCreditRollover in registry.go):
| Knob | Default | What it does |
|---|---|---|
SubscriptionRolloverMonths | 3 | How many months unused tier quota carries forward |
PurchasedNeverExpires | true | Top-up credit doesn’t expire |
MaxBalanceMultiple | 5× | Accumulated balance ceiling, in multiples of monthly quota |
Per-workspace; admin API + CLI adjust at runtime. Set
SubscriptionRolloverMonths=0 for strict use-or-lose behavior.
Overage rates
When usage exceeds free quota but stays below tier cap, the same per-unit rates apply across every tier — what differs by tier is the quota and the cap, not the rate:
| Resource | Unit price | Notes |
|---|---|---|
| Storage | ¥0.15 / GB-月 | OSS bytes-at-rest, billed monthly. Same as Supabase ($0.0213/GB ≈ ¥0.15). |
| Network egress | ¥0.60 / GB | Public outbound only; ingress free. Priced against the real blended cost of both egress paths: files via OSS direct-link (busy ¥0.50 / idle ¥0.25 per GB) and API JSON via ECS public bandwidth (¥0.80/GB) — the old ¥0.50 was busy-hour cost-parity. Still under Supabase ($0.09/GB ≈ ¥0.63). Planned CDN fronting (~¥0.30 blended cost) lets ¥0.50 return as a price cut. OSS API requests are absorbed (not separately billed). |
| Function (CPU-min) | ¥0.05 / CPU-min | Aliyun FC equivalent; ~90% margin at typical 512 MB function size. |
| Email (per message) | ¥0.001 / 封 | Aliyun DirectMail equivalent; aliyun email adapter is TODO so this is the placeholder retail rate. |
| SMS (per message) | ¥0.05 / 条 | Aliyun 短信 equivalent (国内 ~¥0.045 + 薄利). Requires pre-registered 签名 + 模板 (模板报备). |
| DB instance / cache / compute | covered by tier baseline | No overage; pause to stop accrual. |
Three credit modes
The workspace owner picks one of these three, defaulting to the
first. Switchable any time via mortar billing credit-mode <hard|manual|auto>.
1. Hard budget (default)
Credit hits ¥0 → project enters read-only mode (writes return
402 credit_exhausted; reads OK). There is NO automatic monthly
credit grant — Mortar gives no free money (scrubbed 2026-05-24); the
user tops up manually to resume, or the owning account funds it.
Enterprise-account tenants never hit this gate at all.
Since the capacity-quota model (2026-07), the ONLY things that can
drain a balance are metered OVERAGE beyond size quotas and a paid
size’s monthly tier_fee — a project that stays on Nano and inside
its quotas sits at balance 0 forever and never 402s (the gate passes
at balance ≥ 0).
2. Manual top-up
User explicitly adds funds (¥99 / ¥299 / custom) to the workspace’s
Cloud balance via mortar billing top-up --amount=99. Credit deducts
as usual; project pauses only when both monthly credit + top-up balance
are zero.
3. Auto top-up
User configures: “when balance < ¥10, auto-charge card ¥X, with monthly cap ¥Y”. Card charged automatically. Project pauses only when monthly cap reached.
The monthly cap is the anti-runaway-bill safety: a standard explicit-cap design. Without it, a misbehaving cron job in October could rack up ¥10 000 by the 5th — the cap stops that at any predictable level the user chose.
Why this hybrid model (vs Supabase / pure-metered)
Three models considered:
| Model | Example | Margin profile | UX |
|---|---|---|---|
| A: Flat per-tier subscription | $25/月 covers everything | Lock-in revenue, predictable | Opaque (“what did I get?”); heavy users subsidised by light ones |
| B: Pure metered, from byte 1 | Vercel-style usage bills | Heavy users pay more | Bills unpredictable; “spend cap” needed |
| C: Free base size + flat size fee + bundled quota + overage + hard cap (chosen) | Nano 免费, paid sizes ¥58-2998/月 定额, with included quotas | Predictable within quota, capped above | Best of A + B + Mortar-specific anti-runaway safety |
We picked C because:
- Free entry, honest meter — every project starts on Nano at ¥0 (quota-capped, never balance-drained; no pause/wake lifecycle — Supabase Free pauses your project instead)
- Hard cap built in — no Spend Cap to configure; AI agents can’t blow your budget overnight
- Quota rollover — unused capacity doesn’t burn at month boundary (configurable: default 3 months carry)
- Matches cloud-bill structure — your real Aliyun/AWS/Tencent invoice also has “always-on instance” + “per-byte/per-call” lines
Supabase’s three weaknesses Mortar fixes:
- $25 Pro subscription gate — Mortar’s Free plan runs a real Nano project at ¥0 (the free size is zero-rated by construction), no subscription gate to start
- No hard cap by default — Supabase Spend Cap is opt-in; Mortar’s 507 is the default
- Always-on compute add-on — Supabase bills compute 24×7 per
chosen instance size; Mortar’s
functionis per-CPU-min (cheaper for spiky AI APP workloads)
Enforcement
Two distinct overflow paths, two different responses.
Capacity overflow — block writes, hint upgrade
Capacity caps (DB RAM / disk / conn / storage / egress / cache / compute) are hard limits. Exceeding any one returns 507 with a structured upgrade hint:
| Resource | At 80% | At 95% | At 100% |
|---|---|---|---|
| DB disk | warn | warn+notify | writes return 507 tier_capacity_exhausted {primitive:"database_disk"} |
| DB conn | warn | reject 503 | (already at 100% — Postgres rejects new conns, Mortar wraps as 503 database_busy) |
| Storage | warn | warn+notify | uploads return 507 |
| Network egress | warn | warn+notify | downloads return 507; reads NEVER blocked |
| Cache RAM | warn | warn+notify | LRU evict (Redis-native; no 507) |
| Function CPU-min | warn | warn+notify | function invocations return 507 |
Reads are NEVER blocked. Otherwise users can’t see their data to clean it down.
DB RAM and CPU saturation are continuous performance dimensions, not discrete events — handled by the noisy-neighbor monitor (see below) in our shared-pool architecture.
Credit overflow — mode-dependent
Per the chosen credit mode (Hard budget / Manual / Auto top-up). In all modes, the user gets clear in-app and email warnings well before credit is exhausted.
Noisy-neighbor (shared-pool only)
Mortar’s hosted Cloud runs all small/medium tenants on one shared RDS instance for COGS reasons. This means a single tenant’s runaway query can degrade other tenants — a concern Supabase avoids by giving each project its own VM.
Active mitigation:
- Enable
pg_stat_statements; per-query records carry the tenant_id viacurrent_setting('mortar.tenant_id'). - Background goroutine: every 60 s, compute per-tenant CPU% and slow-query count over the last 5 min sliding window.
- Escalating actions:
| Level | Trigger | Action |
|---|---|---|
| L1 | tenant CPU% > 15% for 5 min | mortar warnings entry + email |
| L2 | tenant CPU% > 30% for 5 min | Tenant’s PgBouncer pool size temporarily ÷2 |
| L3 | single query running > 30 s | pg_terminate_backend(pid) on the query |
| L4 | L3 fired > 3 times in 1 hour | Force tier upgrade prompt OR project pause 24 h |
This entire layer is off by default for self-host deployments —
single-tenant deploys don’t have noisy neighbours, and the monitor
just consumes CPU. Set MORTAR_NOISY_NEIGHBOR_MONITOR=on for our
hosted cloud only.
Self-initiated downgrade past current usage
User on Small (currently using 23 GB DB disk) tries to switch tier
to Nano (10 GB cap). API / mortar projects tier-set refuses with
structured error:
{
"error": "tier_change_would_overflow",
"blockers": [
{"resource": "database_disk", "current": 24696061952, "new_limit": 10737418240}
]
}
(current / new_limit are int64 byte counts, not human-readable strings.)
User must clean up first.
Payment failure (auto top-up mode)
Card declines / wire doesn’t clear:
- Day 0–3: full service, retry payment daily, alert user
- Day 4–7: full service, escalating alerts
- Day 8: project enters read-only mode (writes 402
credit_exhausted; reads OK) - Day 30: project paused (no reads, no writes; data preserved)
- Day 90: project + data deleted (final notice at day 60)
Tier upgrade / downgrade transitions
Tier change: takes effect immediately. No DB downtime — tier is a software cap on the shared pool, switching is a config flip. The new baseline rate begins applying from the moment of change.
Mid-month switching: baseline is metered hourly, so changes prorate naturally. Switch from Nano (¥0.025/hr) to Small (¥0.206/hr) on day 15 of a 30-day month → days 1–15 charged at ¥0.025/hr, days 16–30 at ¥0.206/hr.
No “credit refund” for downgrade: tier baseline is already accrued hour-by-hour; using a smaller tier just slows future burn.
COGS analysis (ECS-co-located Postgres)
Numbers at 2026-05 contract prices (Aliyun packyear discount, roughly sticker × 0.65). Postgres + Redis + Mortar server run co-located on a single ECS instance per pool — there’s no separate RDS bill.
Shared-pool assumptions (public list prices, pending API calibration via DescribePrice):
- ONE pool SKU for all shared sizes: 阿里云 u1-c1m8.2xlarge
8 vCPU / 64 GB (
¥550/月 包年 est.) + pooled thin-provisioned ESSD (¥100/月) hosts Mortar + self-hosted Postgres + Redis - Density varies by size; Σ(DB RAM caps) ≤ ~56 GB usable — caps are REAL, no RAM oversubscription
- OSS / egress are separate pass-through
| Size | Density | Pool share of ~¥650/月 | Price | Margin |
|---|---|---|---|---|
| Nano | 80 / pool | ~¥8.1 | ¥28 | ~71% |
| Micro | 40 / pool | ~¥16.3 | ¥58 | ~72% |
| Small | 20 / pool | ~¥32.5 | ¥158 | ~79% |
| Medium | 5 / pool | ~¥130 | ¥998 | ~87% |
| Large | 1 dedicated (8c32G + 500 GB ESSD ~¥676/月) | ~¥676 | ¥2998 | ~77% |
Margins are uniform-healthy (71-87%) and, with active-hour metering, improve further in practice: dormant tenants pay nothing AND cost ~nothing, so billed hours track real pool load.
Egress margin: ¥0.60/GB sale vs blended cost ¥0.50-0.80 (OSS busy-hour ¥0.50 for file bytes; ECS public bandwidth ¥0.80 for API JSON) — thin but positive, with big bytes steered to the OSS direct-link path. Planned CDN fronting (~¥0.30 blended cost) restores ¥0.50 as a price CUT with ~40% margin.
Architecture note
Mortar’s hosted Cloud co-locates Postgres + Redis + the Mortar server binary on each pool ECS — no separate managed RDS / Tair contract. This single-ECS architecture is what makes the “baseline = real cost” pricing work.
| Primitive | Hosted backend | Self-host backend |
|---|---|---|
| Compute | Pool ECS instance hours | Bare-metal / VM hours |
| Database | Self-hosted Postgres on ECS | Postgres 16+ (any deployment) |
| Cache | Self-hosted Redis on ECS | Redis / KeyDB / Dragonfly |
| Storage | 阿里云 OSS | S3-compatible (MinIO / OSS / S3) |
| Network | ECS 公网 + (planned) CDN | Direct egress; CDN optional |
| Function | 阿里云 函数计算 FC | HTTP-callable runner (Lambda / SCF) |
No self-managed Postgres. This was a major decision in v4: RDS-only across the board removes the “two-track architecture” problem (one for hosted, one for self-host). Cost premium (~30% over self-managed PG) absorbed by the COGS table above.
Mortar’s own binary is stateless — pure Go, runs on a small ECS or any VM. All state lives in the managed services. This means:
- Hosted SLA bounded by aliyun’s RDS / OSS / Tair SLAs
- Self-hosters can scale Mortar binary horizontally (load balancer in front) with zero changes
- Cross-region failover is RDS Multi-AZ + multi-region OSS — no Mortar-side replication code to maintain
Implementation phases
The tier table + credit math + 3-mode behaviour above is the contract. Code lands in three steps:
| Phase | Scope | Artifacts |
|---|---|---|
| W7.a | Plan registry: 5-tier × 7-dim table as Go constants; per-unit prices; mode enum | internal/plan/registry.go + tests |
| W7.b | Real feature/usage Recorder (replace NoopRecorder); credit_ledger table append-only | feature/usage/recorder.go, feature/usage/credit.go, model migration |
| W7.c | plan.Gate middleware on write paths; capacity 507/503 mapping; mode-dependent credit-exhaustion handler; noisy-neighbor monitor | internal/plan/gate.go, feature/usage/alerts.go, feature/usage/noisy_neighbor.go |
| W7.d | Dashboard endpoint: GET /v1/{tenant}/usage/me returns itemised breakdown + projected month-end | api/usage_endpoints.go |
| W7.e (2026-07, SHIPPED) | Capacity-quota model: Nano zero-rated; hourly baseline drip → one idempotent tier_fee/period; quota-aware overage recorder (within-quota = ¥0 unit rows; storage overage in ticker) | plan/registry.go (MonthlyFeeYuan), usage/ledger.go (EnsureTierFee, UnitsThisPeriod), usage/usage.go (quota-aware Record), usage/ticker.go |
Billing card-charging integration (Alipay / WeChat Pay) and auto-top-up logic are W8+, downstream of this contract.
Open questions
- Pro / Team plan fees — v0 draft pending. Anchor against Supabase Pro $25 / Team $599 with 中国大陆 price sensitivity; pick after the first paying cohort gives conversion data. (The old “¥99 monthly credit” question is RESOLVED: Mortar grants no free credit; integrators buy wholesale via enterprise accounts.)
- Annual discount — Supabase and other clouds commonly offer 20% off. Defer until ≥ 50 paying projects.
- Free-plan abuse surface — Free 锁 Nano + 1 project caps the exposure, and Nano is zero-rated by construction (quota-capped, not balance-drained), so dormant free projects cost only their ≈¥28/月 COGS share. Monitor for one identity spinning many accounts.
- Reserved capacity savings — a planned lever, expected ~30% RDS / Tair discount with 1-year commit. Margins improve to ~85% across all tiers.
- Per-tier RAM-aware monitoring thresholds — 15% CPU is OK for Nano noisy-neighbor flag, but Large tenants legitimately use more. Future: tier-aware thresholds.
- Compute overage pricing unit — CPU-min is the user-facing unit; FC bills in CU. Conversion factor pinned at “1 CPU-min ≈ 60 CU at 1 GB memory”, subject to refinement when first real workload runs.