indeedeng / iwf

iWF is a WorkflowAsCode microservice orchestration platform offering an orchestration coding framework and service for building resilient, fault-tolerant, scalable long-running processes
MIT License
540 stars 55 forks source link

Optimize timer started for saving cost #465

Open longquanzheng opened 1 month ago

longquanzheng commented 1 month ago

It's a common pattern using iwf workflow like this:

class UpdatableTimerState implements WorkflowState<Void> {

    @Override
    public CommandRequest waitUntil(Context context, ...) {
        return CommandRequest.forAnyCommandCompleted(
                TimerCommand.createByDuration(TIMEOUT_DURATION)),
                InternalChannelCommand.create(CHANNEL_RESET_TIMER)
        );
    }

    @Override
    public StateDecision execute(Context context, Void input, CommandResults commandResults,...) {
        TimerCommandResult timer = commandResults.getTimerResult(0);
        if (timer.getStatus() == FIRED) {
              // timer fires, call API to send an email
              svc.sendEmail(...);
              // assuming we want to complete workflow here
              return StateDecision.completeWorkflow();
        }
        // otherwise the channel receives a message to reset the timer
        return StateDecision.singleNextState(UpdatableTimerState.class);
    }

When doing this, a new timer would always created when receiving a REST TIMER message. We could potentially lazily create it when needed. For example -- if the first timer is 30 days, and after one day, it tries to create another 30 day timer, after another day, it will create another 30 day timer again, so there are 3 timers created in history.

Instead, we could keep the first timer, and only create a new timer when the first timer is fired, and check if there is another timer needed.

Note that this optimization is now possible to implement relatively easy because iwf has a dedicated component to manage all the timers -- TimerProcessor:

So technically this optimization can be done within this component, without changing anything else.

It would be safer to enable by config, then later on when it's stable, use versioning to enable for all.