Cycle burn rate heartbeat

Yeah creating a webworker on digital ocean for example also crossed my mind, but as @jzxchiang mentioned, i would rather do it al in a decentralized matter on the IC.

It would be nice if we could only trigger the heartbeat call every x amount of beats for example. That would drastically reduce the cycles spent. And for a functionality like checking and updating the cycles it isnt really bound to precise timing.

so something like
#[heartbeat(trigger_at = 60)]

Tbh I see no issue with “not going the IC way” for this specific thing as it does not, in any way, defeat the purpose of the blockchain in the first place. The things that should be decentralized should be added into the blockchain, but things like these, imo are not that important as they are not the source of truth or data itself that needs to live and be 100% legit and validated by nodes.

There is a way to trigger something only on a specific amount of beats in a way… You can find the example that does that exact thing in the documentation. I will paste the code here for reference.

import Debug "mo:base/Debug";

actor Alarm {

  let n = 5;
  var count = 0;

  public shared func ring() : async () {
    Debug.print("Ring!");
  };

  system func heartbeat() : async () {
    if (count % n == 0) {
      await ring();
    };
    count += 1;
  }
}

This code would run on every 5 heartbeats. So on 5th, 10th, 15th etc

2 Likes

yeah your example is exactly the same as in the message that started this thread, only i used time instead of a counter to prevent updating the state. But this still triggers the heartbeat and burns cycles.

So to take your example

system func heartbeat() : async () {}

This empty function still burns cycles every time the heartbeat is triggered

So the counter should be on higher level then the heartbeat itself.

1 Like

Oh I see :slight_smile: Makes no sense to burn it every time if it’s not used at all :slight_smile: Yeah… I guess the only way around this rn would be to do what I mentioned earlier perhaps and go around heartbeat completely.

Just waking up your canister every second is work, actually a lot of work. It is not heartbeat that is charging more, but try sending your canister 1 message every block, not cheap either!

For more flexible scheduling, you could use something like IC Cron. I’m not sure if @senior.joinu (author of IC Cron) is offering a deployed IC Cron canister as a public service for everyone. But as a community, if enough people find it useful, you can collectively fund such a public service (e.g. using tip jar) in an open and transparent way that benefits everyone.

4 Likes

If you have occasional update calls to your canister, you could also check your balance during these calls, and call your management canister to top up if your balance is below a threshold.

This could be a useful mixed, i.e. legacy and IC, infrastructure service that canisters on the IC could use.

The service consists of a canister that can be paid to watch the balance and top up other canisters and a simple cloud function that checks the cycles of the registered canisters.

It definitely takes some work, but the proportions just seem off.

0.5 TC a day works out to almost $0.70 per day, which is roughly $21 per month or $252 per year.

That means heartbeat (a simple version) is 50x more expensive than storing 1 GB of data for a year on the IC.

I definitely agree that a public heartbeat canister that anyone can subscribe to and whose cycle costs are split among its subscribers would be very useful, but I don’t think the community will be able to organize around such a service in the near future…

4 Likes

Yeah also thought of this, but issue is that my implementation is for a self-scaling storage solution, so for example;
there is a storage canister, once it reaches a certain memory threshold it locks up for any further update calls and spins up a “brother” canister. So eventually the update calls will stop once the canister is full.

I think the only way your solution would work is to make every query call an update call, or can a query call internally call an update call? :thinking:

so eventually the update calls will stop once the canister is full.

Ah, I see.

or can a query call internally call an update call?

No, it can’t do any inter-canister call.

Since inter-canister update calls take awhile, I wonder if there’s a hacky solution in here where you can set up a canister to recursively call other canisters, and only repeat the inter-canister call once the calling canister has received a response (2+ sec).

This “heartbeat” wouldn’t be as predictable in terms of timing, but would most likely be less expensive.

haha this sound like worth trying, but my assumption is that it would be pretty expensive.

If i understand you correctly it would be something like

--storage canister--
fn get_cycles() {
  storage_management_canister_request_cycles().await
} 

--storage management canister--
fn request_cycles() {
   storage_canister_get_cycles().await // based on caller
}

And then maybe work with some kind of delay solution

Yes, or even self-calls although that is less safe - IC Cron - let's schedule some tasks bois - #3 by nomeata

1 Like

It’s way cheaper to do this instead of using heartbeat. I moved to running a script on a server that calls an upgrade method on my canister every 2 seconds because of costs. It would be nice if we could reduce heartbeat costs to make it as cheap as calling the canister from the “outside”.

5 Likes

I have this hacky system I’m my ICDevs bounty roadmap. A dao that owns this “scheduler” public utility would be a fun project.

Maybe I’ll code it up for supernova.

Interesting problem and needed data:

How many fire and forget intercanister calls can fit into one round of consensus?

Do we need guaranteed delivery? Rouge canisters probably break in line processing of returns….would need to use @nomeata ’s upgrade pattern.

How do the economic play out? How much for 10,000 notifications, etc.

It’s sounds like what you really want is a hook into when a canister reaches the freezing threshold so that you can provide a method that will get called where you could top up the cycles.

Do you have any numbers, e.g. average cost per call? At a 2s interval, it could be 1/2 of the cost, since heartbeat runs roughly every 1s.

Or help us implament ICDevs.org - Bounty #17 - A DAO for Cycles - $10,000 - ht: cycle_dao

Yeah if there was a way to trigger a function once the canister doenst have enough cycles to process the call, that would be a great addition