Map and Buckets Example

I’m looking at the Buckets and Map example at Untitled :: Internet Computer and I have some questions about what is happening under the covers.

When I create an array of Buckets in my map, are each of those buckets their own actor and thus their own canister? Do they each get a 4GB limit? Or do they all live on one cannister?

let b = await Buckets.Bucket(n, i); // dynamically install a new Bucket

If they are each their own canister, will they all have to be charged with cycles? Is there a way to get a reference to them other than the memory slot that I’ve allocated them to? Ideally, I’d like to have a reference to the canister Ids so that I can allocate cycles manually.

If they aren’t separate canisters, how would I go about doing that? Do I need to create them all in my dfx json and manually point the map canister to the 8 buckets? Can I reference them dynamically by doing something like Bucket(‘cannisterID’)?

1 Like

Each dynamically created bucket is indeed a separate canister.

That example actually predates the implementation of cycles and needs to be updated to additionally provision each constructed buckets with cycles by calling ExperimentalCycles.add(cyles) just before the constructor call (see below).

What isn’t clear to me yet is what a good strategy for this would be. Provision Map with enough cycles for all buckets and transfer on demand or, indeed, let the user do this manually?

To obtain the bucket (ids), you should be able to add a (query or update) method that given bucket index, uses library function Principal.fromActor(...) to return the Principal of an allocated bucket or even just return an optional Bucket canister . Something like.

import Array "mo:base/Array";
import Principal "mo:base/Principal";
import Cycles "mo:base/ExperimentalCycles";
import Buckets "Buckets";

actor Map {

  let n = 8; // number of buckets

  type Key = Nat;
  type Value = Text;

  type Bucket = Buckets.Bucket;

  let buckets : [var ?Bucket] = Array.init(n, null);

  public func get(k : Key) : async ?Value {
    switch (buckets[k % n]) {
      case null null;
      case (?bucket) await bucket.get(k);
    };
  };

  public func put(k : Key, v : Value) : async () {
    let i = k % n;
    let bucket = switch (buckets[i]) {
      case null {
        Cycles.add(1_000_000_000_000); // add some cycles
        let b = await Buckets.Bucket(n, i); // dynamically install a new Bucket
        buckets[i] := ?b;
        b;
      };
      case (?bucket) bucket;
    };
    await bucket.put(k, v);
  };

  public query func getPrincipalOfBucket(i : Nat) : async ?Principal {
    switch (buckets[i]) {
      case null null;
      case (?bucket) ? Principal.fromActor(bucket);
    };
  };

  public query func getBucket(i : Nat) : async ?Bucket {
    switch (buckets[i]) {
      case null null;
      case (?bucket) ? bucket;
    };
  };

};

(I’ve only typechecked but not run this code)

Or you could add a topUp method that accepts and re-distributes cycles to the buckets.

3 Likes

Awesome. Thanks for the example and clear explanation. How does Cycles.add know that those cycles should go to the next created bucket? Does it get added to an underlying msg object or something?

Yes it adds the cycles to the next message send (which consumes them):

In future, I expect we’ll add dedicated syntax for this, instead of using a stateful library, hence the Experimental prefix on the library (and warnings in its documentation).

1 Like