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_fee ledger 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)

DimWhat it isWhy bundled
computeYour share of the per-pool ECS instance hoursAlways-on; can’t bill per-call
databaseSelf-hosted Postgres slice on the same ECSSame; baseline = your tier’s RAM/disk slot
cacheSelf-hosted Redis/KeyDB slot on the same ECSSame; 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)

DimUnitQuota includedOverage rateCap
storageGB-month (OSS bytes)per tier (table below)¥0.15/GB-月per tier
networkGB outbound (egress)per tier¥0.60/GBper tier
functionCPU-min on FaaSper tier¥0.05/CPU-minper 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.

PlanFeeInstance sizes availableNotes
Free¥0Nano only1 project; upgrade plan to unlock bigger sizes (the free-tier size gate)
Prov0 draft 待定all fivebigger included quotas; numbers land with first paying cohort
Teamv0 draft 待定all fivecollaboration / support; Supabase-Team analog
Enterpriseinvoiceall fiveplatform 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.

SizeMonthly fee(定额包月)DensityDB RAMDB diskConnCacheFunction (quota → cap)Storage (quota → cap)Egress (quota → cap)
Nano免费(不单卖)80 / pool512 MB5 GB6064 MB50 → 200 CPU-min0.5 → 10 GB2 → 25 GB
Micro¥5840 / pool1 GB20 GB90256 MB250 → 1k3 → 50 GB15 → 100 GB
Small¥15820 / pool2 GB50 GB1201 GB1.5k → 5k20 → 100 GB75 → 250 GB
Medium¥9985 / pool8 GB200 GB1604 GB8k → 20k80 → 250 GB200 → 500 GB
Large¥29981 dedicated (8c32G + 500 GB ESSD)16 GB500 GB2404 GB20k → 30k250 → 500 GB500 → 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):

TierConcurrent connectionsEstimated MAUSuits
Nano601k - 5kHobby / dev / early demo
Micro905k - 20kSide projects / small production
Small12020k - 100kMid-size production
Medium160100k - 500kScaling production
Large240500k - 2MEnterprise / 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):

KnobDefaultWhat it does
SubscriptionRolloverMonths3How many months unused tier quota carries forward
PurchasedNeverExpirestrueTop-up credit doesn’t expire
MaxBalanceMultipleAccumulated 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:

ResourceUnit priceNotes
Storage¥0.15 / GB-月OSS bytes-at-rest, billed monthly. Same as Supabase ($0.0213/GB ≈ ¥0.15).
Network egress¥0.60 / GBPublic 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-minAliyun 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 / computecovered by tier baselineNo 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:

ModelExampleMargin profileUX
A: Flat per-tier subscription$25/月 covers everythingLock-in revenue, predictableOpaque (“what did I get?”); heavy users subsidised by light ones
B: Pure metered, from byte 1Vercel-style usage billsHeavy users pay moreBills unpredictable; “spend cap” needed
C: Free base size + flat size fee + bundled quota + overage + hard cap (chosen)Nano 免费, paid sizes ¥58-2998/月 定额, with included quotasPredictable within quota, capped aboveBest 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:

  1. $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
  2. No hard cap by default — Supabase Spend Cap is opt-in; Mortar’s 507 is the default
  3. Always-on compute add-on — Supabase bills compute 24×7 per chosen instance size; Mortar’s function is 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:

ResourceAt 80%At 95%At 100%
DB diskwarnwarn+notifywrites return 507 tier_capacity_exhausted {primitive:"database_disk"}
DB connwarnreject 503(already at 100% — Postgres rejects new conns, Mortar wraps as 503 database_busy)
Storagewarnwarn+notifyuploads return 507
Network egresswarnwarn+notifydownloads return 507; reads NEVER blocked
Cache RAMwarnwarn+notifyLRU evict (Redis-native; no 507)
Function CPU-minwarnwarn+notifyfunction 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:

  1. Enable pg_stat_statements; per-query records carry the tenant_id via current_setting('mortar.tenant_id').
  2. Background goroutine: every 60 s, compute per-tenant CPU% and slow-query count over the last 5 min sliding window.
  3. Escalating actions:
LevelTriggerAction
L1tenant CPU% > 15% for 5 minmortar warnings entry + email
L2tenant CPU% > 30% for 5 minTenant’s PgBouncer pool size temporarily ÷2
L3single query running > 30 spg_terminate_backend(pid) on the query
L4L3 fired > 3 times in 1 hourForce 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
SizeDensityPool share of ~¥650/月PriceMargin
Nano80 / pool~¥8.1¥28~71%
Micro40 / pool~¥16.3¥58~72%
Small20 / pool~¥32.5¥158~79%
Medium5 / pool~¥130¥998~87%
Large1 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.

PrimitiveHosted backendSelf-host backend
ComputePool ECS instance hoursBare-metal / VM hours
DatabaseSelf-hosted Postgres on ECSPostgres 16+ (any deployment)
CacheSelf-hosted Redis on ECSRedis / KeyDB / Dragonfly
Storage阿里云 OSSS3-compatible (MinIO / OSS / S3)
NetworkECS 公网 + (planned) CDNDirect egress; CDN optional
Function阿里云 函数计算 FCHTTP-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:

PhaseScopeArtifacts
W7.aPlan registry: 5-tier × 7-dim table as Go constants; per-unit prices; mode enuminternal/plan/registry.go + tests
W7.bReal feature/usage Recorder (replace NoopRecorder); credit_ledger table append-onlyfeature/usage/recorder.go, feature/usage/credit.go, model migration
W7.cplan.Gate middleware on write paths; capacity 507/503 mapping; mode-dependent credit-exhaustion handler; noisy-neighbor monitorinternal/plan/gate.go, feature/usage/alerts.go, feature/usage/noisy_neighbor.go
W7.dDashboard endpoint: GET /v1/{tenant}/usage/me returns itemised breakdown + projected month-endapi/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

  1. 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.)
  2. Annual discount — Supabase and other clouds commonly offer 20% off. Defer until ≥ 50 paying projects.
  3. 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.
  4. Reserved capacity savings — a planned lever, expected ~30% RDS / Tair discount with 1-year commit. Margins improve to ~85% across all tiers.
  5. 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.
  6. 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.