Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/concepts/canisters.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Each canister has two storage regions:

| Region | Max size | Persisted across upgrades | Access |
|--------|----------|--------------------------|--------|
| **Heap (Wasm) memory** | 6 GiB | No (cleared on upgrade, unless using Motoko's orthogonal persistence) | Standard Wasm memory instructions |
| **Heap (Wasm) memory** | 4 GiB (wasm32) / 6 GiB (wasm64) | No (cleared on upgrade, unless using Motoko's orthogonal persistence) | Standard Wasm memory instructions |
| **Stable memory** | 500 GiB | Yes | System API calls |

**Heap memory** is standard Wasm linear memory. It holds your program's heap-allocated data — variables, data structures, and anything your code allocates at runtime. Both 32-bit and 64-bit Wasm memory are supported. Heap memory is cleared when you upgrade the canister's Wasm module.
Expand Down
1 change: 1 addition & 0 deletions docs/concepts/network-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ When you deploy a canister, it lands on one subnet and is replicated across ever
- **Cross-subnet calls.** Canisters on different subnets can call each other through the network's messaging layer. These calls are slightly slower than calls within the same subnet (they require an extra consensus round), but they work transparently — you don't need to know which subnet a canister lives on.
- **Subnet size and cost.** Subnets typically range from 13 to 40 nodes. Larger subnets provide stronger security guarantees (more nodes must collude to compromise state) but cost more cycles to run on. Most application canisters run on 13-node subnets.
- **Finality.** ICP achieves finality in 1–2 seconds. Once your update call returns, the state change is committed and replicated — there are no probabilistic confirmations or reorgs.
- **Shared storage budget.** All canisters on a subnet share a common storage budget. Each canister can use up to 500 GiB of stable memory, but the total available depends on the subnet's current utilization. Storage-heavy applications should consider subnet selection.
- **Geographic distribution.** Nodes within a subnet are distributed across data centers, operators, and jurisdictions to maximize decentralization. Localized subnets also exist for applications with data residency requirements.

For details on subnet types and how to choose one, see [Subnet types](../reference/subnet-types.md) and [Subnet selection](../guides/canister-management/subnet-selection.md).
Expand Down
104 changes: 93 additions & 11 deletions docs/concepts/orthogonal-persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,101 @@ title: "Orthogonal Persistence"
description: "How canister memory survives across executions and upgrades without databases"
sidebar:
order: 5
icskills: []
icskills: [stable-memory]
---

TODO: Write content for this page.
On traditional backends, application state lives in memory only while the process runs. To persist data across restarts, you need a database -- PostgreSQL, Redis, SQLite, or a file system. The application logic and the storage layer are separate concerns that developers must wire together.

<!-- Content Brief -->
Explain orthogonal persistence on ICP. Cover how canister memory (heap) persists between calls, stable memory as the upgrade-safe storage layer, heap persistence in Motoko (automatic with enhanced orthogonal persistence), stable structures in Rust (StableBTreeMap, etc.), and the trade-offs between heap and stable memory. Compare with traditional database-backed backends.
On the Internet Computer, persistence is built into the execution model. A canister's memory persists between calls automatically -- no database and no file system. In Motoko, this is fully transparent: you declare a variable, assign it a value, and that value is still there the next time the canister executes -- no explicit save or load. In Rust, you choose persistent data structures that write directly to stable memory, giving you full control over what survives upgrades. Either way, the canister IS its own storage. This property is called **orthogonal persistence**: persistence is orthogonal to (independent of) the programming model.

<!-- Source Material -->
- Portal: persistence sections (scattered)
- Learn Hub: https://learn.internetcomputer.org (orthogonal persistence)
There is no separate storage tier to configure, query, or maintain.

<!-- Cross-Links -->
- guides/backends/data-persistence -- practical implementation
- languages/rust/stable-structures -- Rust-specific patterns
- guides/canister-management/lifecycle -- persistence across upgrades
## Two memory regions

Every canister has two distinct memory regions, each with different characteristics:

### Heap (Wasm linear) memory

This is regular program memory -- the space where variables, data structures, and the call stack live during execution. It maps to the Wasm linear memory of the canister module.

- **Size limit:** 4 GiB for wasm32 canisters, 6 GiB for wasm64
- **Performance:** Fast, native Wasm memory access
- **Upgrade behavior:** Wiped on canister upgrade (Rust) -- use stable structures to persist data; automatically preserved in Motoko with `persistent actor`

### Stable memory

A separate, dedicated memory region provided by the Internet Computer runtime. Its sole purpose is to survive canister upgrades.

- **Size limit:** Up to 500 GiB per canister. The actual available capacity also depends on the subnet's total storage usage, since all canisters on a subnet share a common storage budget. For storage-heavy applications, consider [subnet selection](../guides/canister-management/subnet-selection.md).
- **Performance:** Slower than heap memory -- each access goes through system API calls rather than direct Wasm memory operations
- **Upgrade behavior:** Always survives upgrades

The distinction between these two regions is the foundation of all persistence strategies on ICP.

## How persistence differs by language

The two mainstream canister languages -- Motoko and Rust -- take fundamentally different approaches to persistence.

### Motoko: true orthogonal persistence

Motoko is the only ICP language that delivers true orthogonal persistence. With `persistent actor`, all variable declarations inside the actor body are automatically persisted across upgrades. Developers do not think about persistence at all -- they write normal code and data survives.

The runtime transparently manages the mapping between the program's heap and stable memory during upgrades. Fields marked `transient var` reset to their initial value on upgrade, giving developers explicit control over what is ephemeral (caches, counters) versus durable.

This is orthogonal persistence in its purest form: persistence is completely invisible to the programming model.

For implementation details and code examples, see the [Data persistence guide](../guides/backends/data-persistence.md).

### Rust: explicit stable structures

Rust canisters take an explicit approach. The `ic-stable-structures` crate provides data structures (`StableBTreeMap`, `StableCell`, `StableLog`) that are backed directly by stable memory. Data written to these structures survives upgrades without any serialization step.

This is not orthogonal persistence -- developers must consciously choose which data structures to use and how to partition stable memory. The tradeoff is full control: Rust developers decide exactly what persists, how it's stored, and how memory is allocated.

For implementation details and code examples, see the [Data persistence guide](../guides/backends/data-persistence.md).

## The dangerous pattern: heap serialization

Before stable structures existed, the standard approach in Rust was to store data in heap memory and serialize it to stable memory in `pre_upgrade`, then deserialize it back in `post_upgrade`.

This pattern has a critical failure mode: `pre_upgrade` runs with a fixed instruction limit. If the dataset grows large enough, serialization exceeds the limit and the hook traps. The upgrade fails, and recovery requires the `skip_pre_upgrade` flag, which bypasses the failing hook but may result in data loss.

Stable structures avoid this entirely by writing directly to stable memory during normal operation. There is nothing to serialize at upgrade time. New Rust canisters should always use stable structures rather than heap serialization.

## Heap vs. stable memory: trade-offs

| | Heap memory | Stable memory |
|---|---|---|
| **Size limit** | 4 GiB (wasm32) / 6 GiB (wasm64) | Up to 500 GiB |
| **Access speed** | Fast (native Wasm) | Slower (system API calls) |
| **Upgrade safety** | Automatic in Motoko `persistent actor`; wiped in Rust | Always survives upgrades |
| **API** | Native language constructs | `StableBTreeMap` etc. (Rust); automatic (Motoko) |
| **Use case** | All data in Motoko `persistent actor`; caches and temporary computation in Rust | All persistent application data (Rust) |

In Motoko with `persistent actor`, this trade-off is largely invisible -- the runtime manages the mapping between heap and stable memory during upgrades. In Rust, developers choose explicitly: heap data (fast but ephemeral) or stable structures (slightly slower but durable).

## Comparison with traditional backends

| Concern | Traditional backend | ICP canister |
|---|---|---|
| **State persistence** | External database (PostgreSQL, Redis) | Built into the runtime |
| **Configuration** | Connection strings, schemas, migrations | None (declare variables) |
| **Deployment** | App server + database server | Single canister |
| **Upgrade safety** | Database persists independently of app | Stable memory persists across upgrades |
| **Scaling storage** | Provision database storage separately | Stable memory grows with usage (up to 500 GiB per canister, subject to subnet storage budget) |

The mental model shift: instead of "my app talks to a database," think "my app IS the database." Canister state is the program's state, and the Internet Computer ensures it persists.

## Further reading

- [IC Internals: Orthogonal Persistence](https://medium.com/dfinity/ic-internals-orthogonal-persistence-9e0c094aac1a) -- deep dive into how orthogonal persistence works at the protocol level
- [A Journey into Stellarator (Part 2)](https://medium.com/dfinity/a-journey-into-stellarator-part-2-d4a83c631748) -- the Stellarator engine that powers Motoko's persistent actors
- [Orthogonal Persistence in 60 Seconds](https://www.youtube.com/shorts/g3sC2wjLzew) -- quick visual explainer

## Next steps

- [Data persistence guide](../guides/backends/data-persistence.md) -- practical implementation patterns for both languages
- [Rust stable structures](../languages/rust/stable-structures.md) -- detailed Rust patterns with `StableBTreeMap`, `StableCell`, and `StableLog`
- [Canister lifecycle](../guides/canister-management/lifecycle.md) -- how upgrades, reinstalls, and other lifecycle events interact with persistence

<!-- Upstream: informed by dfinity/portal persistence sections and stable-memory icskill -->
2 changes: 1 addition & 1 deletion docs/guides/backends/data-persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ icskills: [stable-memory]
TODO: Write content for this page.

<!-- Content Brief -->
Guide developers through storing data in canisters. Cover stable structures (StableBTreeMap in Rust), persistent actors (Motoko), MemoryManager for multiple data structures, and pre/post-upgrade hooks. Include idempotency patterns for safe data mutation. Show code examples for both Rust and Motoko.
Guide developers through storing data in canisters. This is the how-to companion to concepts/orthogonal-persistence (which explains what and why; this page shows how). Cover: Motoko persistent actors (persistent actor, transient var, let/var persistence, schema evolution rules), Rust stable structures (StableBTreeMap, StableCell, StableLog, MemoryManager, MemoryId partitioning, Storable trait implementations for custom types, #[init]/#[post_upgrade] hook patterns), and the dangerous pre_upgrade heap serialization anti-pattern. Include idempotency patterns for safe data mutation. Show complete code examples for both Rust and Motoko.

<!-- Source Material -->
- Portal: building-apps/canister-management/storage.mdx, best-practices/storage.mdx, best-practices/idempotency.mdx
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/cycles-costs.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Reserved cycles are non-transferable. Controllers can disable reservation by set
| Max same-subnet inter-canister request payload | 10 MiB |
| Max response size (replicated execution) | 2 MiB |
| Max response size (query) | 3 MiB |
| Wasm heap memory per canister | 4 GiB |
| Wasm heap memory per canister | 4 GiB (wasm32) / 6 GiB (wasm64) |
| Wasm stable memory per canister | 500 GiB |
| Subnet capacity (total memory) | 2 TiB |
| Wasm module total size | 100 MiB |
Expand Down
Loading