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.
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
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.
Oh I see Makes no sense to burn it every time if it’s not used at all 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.
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…
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?
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.
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”.
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.