Basic Memory Persistence

Every actor exposes a public interface. A deployed actor is sometimes referred to as a service. We interact with a service by calling the public shared functions of the underlying actor.

The state of the actor can not change during a query function call. When we call a query function, we could pass in some data as arguments. This data could be used for a computation, but no data could be stored in the actor during the query call.

The state of the actor may change during an update or oneway function call. Data provided as arguments (or data generated without arguments in the function itself) could be stored inside the actor.

Canister main memory

Every actor running in a canister has access to a 4GB main 'working' memory (in Feb 2023). This is like the RAM memory.

Code in actors directly acts on main memory:

  • The values of (top-level) mutable and immutable variables are stored in main memory.
  • Public and private functions in actors, that read and write data, do so from and to main memory.
  • Imported classes from modules are instantiated as objects in main memory.
  • Imported functions from modules operate on main memory.

The same applies for any other imported item that is used inside an actor.

Memory Persistence across function calls

Consider the actor from our previous example with only the mutable variable latestComment and the functions readComment and writeComment:

actor {
    private var latestComment = "";

    public shared query func readComment() : async Text {
        latestComment;
    };

    public shared func writeComment(comment : Text) : async () {
        latestComment := comment;
    };
};

The mutable variable latestComment is stored in main memory. Calling the update function writeComment mutates the state of the mutable variable latestComment in main memory.

For instance, we could call writeComment with an argument "Motoko is great!". The variable latestComment will be set to that value in main memory. The mutable variable now has a new state. Another call to readComment would return the new value.

Service upgrades and main memory

Now, suppose we would like to extend the functionality of our service by adding another public shared function (like the deleteComment function). We would need to write the function in Motoko, edit our original Motoko source file and go through the deployment process again.

The redeployment of our actor will wipe out the main memory of the actor!

There are two main ways to upgrade the functionality of a service without resetting the memory state of the underlying actor.

This chapter describes stable variables which are a way to persist the state of mutable variables across upgrades. Another way is to use stable memory, which will be discussed later in this book.