How do multi-level Canisters manage cycles recharge?

Hello, comrades?
We have developed a multi-level Canister smart contract system on IC, which is about to be released online, but we are still confused about the convenience of cycles management.
Suppose these Canisters have 3 levels, a first-level Main Canister, multiple second-level Canisters created by the first-level Canister, and multiple third-level Canisters created by the second-level Canister.
At the same time, most of these canisters are dynamically created according to data growth.

  1. Do these canisters need to be manually recharged for cycles each time? If so, it would be too troublesome.
  2. If not, how should it be handled reasonably?
  3. Or can the cycles amount be recharged to the Main Canister, and how to automatically allocate it according to needs?
  4. Or recharge to one account to share cycles? , how should it be done?

Thank you?

tagging @icme here from the CycleOps team who might want to jump in. other than that I recommend you to read through the following thread:

I am actually not sure if there is already a best practice for dynamically created canisters. I would also be very interested in whether somebody has a good solution for that.

For dynamically created canisters the first thing that springs to mind is that they could register themselves with a coordinator canister as part of the init steps. The coordinator could then make sure all canisters are added to monitoring solutions

1 Like

@haida Based on your reasoning for scaling being “data growth”, you may not actually need a dynamic canister autoscaling solution - in which case you wouldn’t need to build multi-level cycles recharge tooling. (See my recommendation at the bottom).

However, if you’re interested in how you might do this regardless, there isn’t exactly standard, but from what I’ve seen most applications with dynamic canister generation use some sort of a “cycles battery” or “cycles manager” to facilitate child canister topup when they get low. It’s up to the developer to determine what “low is”, but you can build your own cycles accounting system, or use a tool like CycleOps to profile the burn rate of different child canisters deployed to mainnet. That should give you a sense of what the right cycles topup threshold and amount would be. Additionally, then you need to instrument your canister with automated topups to ensure the battery canister stays topped up with cycles, or you can use a monitoring tool like CycleOps or CanisterGeek.

There are two ways that you can implement the dynamic child canister monitoring:

  1. The cycles manager pattern, where satellite canisters are aware of their own lower bound limits and when they get low, call back into the cycles manager to request more cycles. You can attach checks to update calls, timers, or, when Canister Lifecycle Hooks get released the canister_on_low_cycles hook.
  2. On a periodic basis, have the cycles battery check the balances on each of the child canister if necessary. This requires inter-canister calls for every cycles balance check, so in many cases option #1 may be preferred.

For multi-level canister apps, it depends. You can still just hook everything up to the same cycles manager/battery canister, which might be simpler.

Battery canister -> distributes cycles to all canisters

Some applications have lower level canister factories (factories spin up new canisters), and have chosen to distribute cycles from those factory canisters to the child canisters spun up by that factory

Battery canister -> distributes cycles to factory canisters -> distributes cycles to child canisters underneath each factory canister.



This all being said, I’d recommend keeping things simple if possible to start with.

  • Use a single cycles manager/battery canister to distribute cycles to all child canisters
  • Don’t dynamically spin up more canisters than you need (easier to split canister later than to combine canisters after they’ve split).

If data growth is the reason for scaling, you may not need to spin up new canisters for this.

  • Rust has stable-structures, which allow you to store data in canister stable memory (up to 400 GB available)
  • Motoko recently merged it’s orthogonal persistence feature, which once 64-bit heap memory is available to canisters (coming soon) should eventually allow Motoko developers to store up to the full canister memory in heap memory (~400GB).

Therefore, if data growth is your concern I’d potentially recommend starting out with a single canister or few canister architecture. For example, then instead of multiple account contracts start with one, and the same for your transaction ledger canister, credits canister, etc.

As an added benefit, then you don’t need to write a dynamic cycles management system and can focus your time in other areas!

1 Like

Thank you.
I have adopted and implemented a regular automatic detection and topup mode based on some code examples.
Thank you very much for your answers