Mortar pricing — Cloud credit + 5 capacity tiers
Status: draft, awaiting numerical sign-off. Engineering can start
on internal/plan/, real-impl feature/usage Recorder, and the
credit ledger once the dimensions here are locked.
See also: Fabric subscription pricing (the other axis) at
fabric/docs/pricing-strategy.md. The two-layer billing model is explained from both sides — that doc’s “second-layer cloud bill” section compares Mortar’s ¥99/mo credit positioning against a leading full-stack app cloud / Bolt+Supabase / v0+Vercel etc.
TL;DR
Mortar Cloud is paired with Fabric, and follows the built-in full-stack cloud model:
- Two independent axes. Fabric subscription
(Free / Pro / Max / Ultra — see
fabric/docs/pricing-strategy.md) is one axis; Mortar Cloud capacity is the other. - 5 Cloud capacity tiers — Tiny / Mini / Small / Medium / Large. Each tier sets a capacity ceiling (DB RAM / disk / connections). No flat per-tier subscription fee.
- ¥99/mo Cloud credit per workspace — flat, regardless of editor tier or Cloud capacity tier. Resets on the 1st of each month; doesn’t roll over.
- All Cloud usage metered against credit: instance baseline (per hour the tier is active), variable usage (storage / egress / compute / cache) — all itemized on the dashboard.
- 3 modes when credit runs out: Hard budget (default — pause writes until next month or manual top-up), Manual top-up (user adds funds), Auto top-up (card charged with monthly cap).
- Free editor users → forced Tiny tier (the free-tier size gate). Pro / Max / Ultra users → free pick of all 5 tiers.
Capacity tiers
Each tier sets the per-project capacity ceiling. Picking a tier is not a flat-fee subscription — it controls (a) the maximum your DB instance can reach, (b) how fast your credit drains on baseline.
| Tier | DB RAM | DB disk | DB conn | Storage cap | Egress cap | Cache RAM cap | Compute cap (CPU-min) | Hourly baseline | Monthly baseline (full month on) |
|---|---|---|---|---|---|---|---|---|---|
| Tiny | 1 GB | 10 GB | 60 | 10 GB | 25 GB | 64 MB | 200 | ¥0.026 | ¥19 |
| Mini | 2 GB | 50 GB | 90 | 50 GB | 100 GB | 256 MB | 1 000 | ¥0.068 | ¥49 |
| Small | 4 GB | 100 GB | 120 | 100 GB | 250 GB | 1 GB | 5 000 | ¥0.207 | ¥149 |
| Medium | 8 GB | 200 GB | 160 | 250 GB | 500 GB | 4 GB | 20 000 | ¥0.693 | ¥499 |
| Large | 16 GB | 500 GB | 240 | 500 GB | 1 TB | 4 GB | 30 000 | ¥1.110 | ¥799 |
Capacity caps are hard limits — exceeding them blocks new writes or rejects new resources (see Enforcement).
Baseline is the cost of keeping the tier’s DB instance slice provisioned on the shared RDS pool, billed hourly against credit. A project paused for the full month pays no baseline.
Cloud credit
Every workspace receives ¥99/mo Cloud credit, automatically, on the 1st of each calendar month. It doesn’t roll over.
The credit covers all metered usage on Mortar Cloud:
- Instance baseline (per the tier table above)
- Variable usage (per the table below)
There is no separate per-tier subscription fee — the ¥99 credit is the only “subscription” for the Cloud half. Heavy users either upgrade their editor plan (which doesn’t change the Cloud credit; Appunvs’s pricing is orthogonal) or use one of the three top-up modes below.
Variable per-unit prices
When usage exceeds the credit, the same per-unit prices continue to apply (now drawn from top-up funds instead of monthly credit):
| Resource | Unit price | Notes |
|---|---|---|
| Storage | ¥0.15 / GB-month | OSS-equivalent, billed monthly |
| Network egress | ¥0.40 / GB | Public outbound only; ingress free |
| Compute (CPU-min) | ¥0.05 / CPU-min | Aliyun FC equivalent units |
| Email (per message) | ¥0.001 / message | Aliyun DirectMail equivalent (placeholder; aliyun email adapter TODO) |
| SMS (per message) | ¥0.05 / message | Aliyun 短信 equivalent (~¥0.045 + thin margin); requires pre-registered SignName + TemplateCode (模板报备) |
| Cache RAM (overage) | ¥3 / GB-month | Above the tier’s bundled cap |
| DB instance baseline | per tier above | Always-on; pause the project to stop |
These prices are the same across every tier — what differs by tier is the capacity ceiling and the baseline rate.
Three credit modes
The workspace owner picks one of these three, defaulting to the first. Switchable any time from the dashboard.
1. Hard budget (default)
Credit hits ¥0 → project enters read-only mode (writes return
402 credit_exhausted; reads OK). Resumes automatically on the
1st of next month when fresh ¥99 credit is granted.
User can also manually top up at any time to resume early.
2. Manual top-up
User explicitly adds funds (¥99 / ¥299 / custom) to the workspace’s Cloud balance from the dashboard. Credit deducts as usual; the 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 is charged automatically. Project pauses only when the monthly cap is 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 not a flat per-tier subscription?
Three options were considered:
| Model | Example | Margin profile | UX |
|---|---|---|---|
| A: Flat per-tier (drafted in v3) | Tiny ¥99/mo, Mini ¥249/mo, … | Lock-in revenue, predictable | Account opaque (“what did I get for ¥99?”) |
| B: Pure metered (Supabase default) | Subscription + per-unit overage | Heavy users pay more | Bills unpredictable; “spend cap” needed |
| C: Credit + capacity tiers (this doc) | ¥99/mo credit, pick tier for capacity | Simpler bills than B, transparent vs A | ”I have ¥99 to spend, here’s how I’m spending it” |
We picked C because:
- Aligned with the earlier pivot decision — users pay for what they actually consume, not a tier-bundled fixed fee
- Naturally matches our hosted architecture (one shared RDS pool — baseline cost per tenant is hourly anyway)
- Lower marketing friction in mainland China than B (Spend Cap is a weird concept; “credit” / “balance” is intuitive)
- Open-source self-hosters on RDS see the same itemized breakdown in their own dashboard — useful regardless of who’s paying
What we lose vs A: the “feels like a subscription” framing. Mitigated by always displaying the ¥99/mo monthly grant, which is psychologically the same as “I’m subscribed”.
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 | banner+email | writes return 507 tier_capacity_exhausted {primitive:"database_disk"} | |
| DB conn | banner | reject 503 | (already at 100% — Postgres rejects new conns, Mortar wraps as 503 database_busy) |
| Storage | banner+email | uploads return 507 | |
| Network egress | banner+email | downloads continue but flag overflow events | |
| Cache RAM | banner | banner+email | LRU evict (Redis-native) |
| Compute | banner+email | function invocations return 503 tier_capacity_exhausted |
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 | Dashboard banner + 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 neighbors, 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 Tiny (10 GB cap). API/dashboard refuses with a 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 Tiny (¥0.026/hr) to Small (¥0.207/hr) on day 15 of a 30-day month → days 1–15 charged at ¥0.026/hr, days 16–30 at ¥0.207/hr, all drawn from the same monthly credit balance.
No “credit refund” for downgrade: monthly credit is already granted at month start; using a smaller tier just means slower burn.
COGS analysis (Aliyun RDS-only)
Numbers at expected 50% utilization. Worst case (all dimensions maxed) margin drops ~10 points but is structurally bounded.
Shared-pool assumptions (mainland production deploy):
- 1 Aliyun RDS PG HA 4-core / 16 GB / 200 GB (~¥1500/mo) hosts ~150 Tiny + ~50 Mini + ~20 Small projects via RLS
- 1 Tair in-memory 16 GB instance (~¥1600/mo) shared across ~500 small-tier cache allocations
- OSS / network egress are linear pass-through with markup
- Aliyun FC: ~¥0.005/CPU-min retail, ~10× markup on CU resource pack
| Tier | DB COGS | Storage | Egress | Cache | Compute | Total COGS @50% util | Effective price (baseline + ~50% credit + ~50% overage) | Margin |
|---|---|---|---|---|---|---|---|---|
| Tiny | ¥10 | ¥0.5 | ¥4 | ¥3.5 | ¥0.5 | ¥18.5 | ¥85 (within credit, baseline+light variable) | 78% |
| Mini | ¥18 | ¥2.5 | ¥17 | ¥13 | ¥3 | ¥53.5 | ¥285 (some overage) | 81% |
| Small | ¥45 | ¥5 | ¥42 | ¥50 | ¥14 | ¥156 | ¥780 (significant overage) | 80% |
| Medium | ¥165 | ¥12 | ¥85 | ¥200 | ¥55 | ¥517 | ¥2 600 | 80% |
| Large | ¥385 | ¥25 | ¥170 | ¥200 | ¥85 | ¥865 | ¥4 300 | 80% |
RDS-managed premium absorbed: figures already include the ~30% RDS markup over self-managed Postgres on ECS. We pay the markup; the value is removing ops burden (no DBA needed at current scale).
¥99 monthly credit math:
- Tiny baseline ¥19/mo alone is ~19% of credit — light user lives comfortably inside the credit
- Large baseline ¥799/mo exceeds credit alone — user pays ¥700+ baseline overage even at zero variable use; matches the common pattern where Large users effectively buy capacity
Architecture note
Mortar’s hosted Cloud runs entirely on managed cloud services:
| Primitive | Hosted backend | Self-host backend |
|---|---|---|
| Database | Aliyun RDS PostgreSQL HA | Any Postgres 16+ (RDS / Aurora / self) |
| Storage | Aliyun OSS | Any S3-compatible (MinIO / OSS / S3) |
| Cache | Aliyun Tair | Any Redis-compatible |
| Compute | Aliyun Function Compute 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 the 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 behavior 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 itemized breakdown + projected month-end | api/usage_endpoints.go |
Billing card-charging integration (Alipay / WeChat Pay) and auto-top-up logic are W8+, downstream of this contract.
Open questions
- ¥99 credit number — placeholder. Anchor to a competitive point ($25 ≈ ¥175 USD-converted; ¥99 is the iconic “hundred-yuan” tier in mainland-China SaaS pricing). Pick after the first 50 paying customers give us conversion data.
- Annual discount — Supabase and other clouds commonly offer 20% off. Defer until ≥ 50 paying projects.
- Free editor user → forced Tiny — should they get the same ¥99 credit? Some full-stack clouds do ($25 regardless of plan). Default yes; monitor for Free abuse (one user spinning 100 free projects).
- 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 Tiny 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 the first real workload runs.