3
14 Comments

Managing time-based workflow in code

Hey guys,

Happy new year to all of you! I wish you the best for 2020 💪

DISCLAIMER: This post is mainly aimed at a developer audience, so don't worry if you don't understand it completely.

Let's dive in, I will start with a simple question that I'm really curious about: How do you manage to write and run in code this scenario? (👇)

(start) TASKA => Wait for 7 days => TASKB (end)

In the real world, you do this kind of thing for example when a user registers on your website, you send him a welcome email, then you wait few days, and you send him a follow up to give him deeper details about your platform, Right?

Again I'm pretty sure there are multiple answers possible here, and every backend programming language can be used to do this.

My point here is: Managing lengthy duration in programming is hard!

What's your opinion on it?

I will come with an idea I have to tackle this problem, but first I would like to confirm that I'm not the only one to think it's hard and cumbersome.

Thank you for your feedback.

on January 16, 2020
  1. 1

    Usually for a task like this, don't over engineer it

    TASK-A stores some sort of row (in an SQL table) with a date
    TASK-B is triggered once every 24 hours via cloud cron solution (https://cloud.google.com/scheduler/, kubernetes, https://www.easycron.com/, etc.)
    TASK-B searches through all the rows that haven't been marked completed, and are at least 7 days old, and for each of those rows executes its task, and marks the row as completed (maybe even delete the row if you don't need it)

    A simple solution like this can scale to 100k, maybe even 1M rows on a nice db server

  2. 1

    In the Ruby land - which is where I write most of my code, we have something called Sidekiq which supports this. So, you'd simply schedule your future job as:

    MyWorker.perform_at(7.days.from_now, arg1, arg2)
    

    See: https://github.com/mperham/sidekiq/wiki/Scheduled-Jobs

    I'm sure there's similar functionality available in other languages too.

    If not, one way of achieving the same functionality might be to store a DB timestamp record along with the job details/arguments with a execute_at timestamp and have a periodic polling cronjob that monitors the records say every minute and executes all jobs for which the execute_at timestamp is in the past. Not the most versatile and ideal solution - but simple and good enough for a side project.

  3. 1

    If you're using AWS you can, for instance, set a TTL of 7 days on a DynamoDB row. Then you can trigger a Lambda function when that TTL expires.

    1. 1

      Yes indeed,

      But this is not really appropriate due to the following reasons:

      1. In AWS documentation (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html), they said DynamoDB typically deletes expired items within 48 hours of expiration. The exact duration within which an item truly gets deleted after expiration is specific to the nature of the workload and the size of the table. Items that have expired and have not been deleted still appear in reads, queries, and scans. These items can still be updated, and successful updates to change or remove the expiration attribute are honored.

      So it means that if you want to wait for a couple of hours, it cannot work sometimes. So for me it's the main blocker here 😼

      1. You're not AWS customer. And often you're not going to be AWS customer for one service but for your whole infra.
      2. If you're not a lambda user, most tech stacks are a monolith and people don't want to overcomplicate their infra.
      3. Visibility and monitoring are non-existent with this solution.
      1. 1

        Yes, absolutely this will not work if you are not an AWS customer (hence the caveat).

        I would strongly argue that most tech stacks are not monoliths, but that's just my experience.

  4. 1

    If I'm understanding you correctly this sounds like https://www.hangfire.io/ for the .net ecosystem (although I haven't used it myself) - might be worth a look.

    1. 1

      Thanks for answering.

      A bit, yes except my solution is language agnostic and much simpler.

  5. 1

    Cool little service but honestly I wouldn’t pay for it. Rather just run a cronjob and deal with the minor inaccuracies. Do you have an example of a feature that needs to wait 7 days and be executed perfectly to the millisecond?

    1. 1

      Thanks for answering :)

      Not really for the millisecond.

      I agree that cron job works well when you have one or two. But you start having a lot of business logic, then you need to have a lot of queries in DB every minute to check if things have changed it becomes a real mess and you lack monitoring.

      With my solution, you're not using cron except if you really want something recurrent, for example cleaning a cache every night (and again you can use my service if you want, it's possible, it's just a recurrent wait).

      You're using waits, and so you are not going to query your DB for anything, you only push the wait on my service, and it will call your backend back when ready. It seems like a much better solution, don't you think? On a technical aspects?

      1. 1

        Well a cronjob doesn't necessarily need to call a DB every minute - All jobs can be stored in memory with a timestamp:job_id mapping. Doesn't even need to be a cronjob, just a server that is running continuously (or a cluster of servers, if you're worried about failure)
        The mapping can be stored in a distributed fashion using Redis.

        IF I just needed a handful of these events and your service was free, then yes I'd use it out of convenience. But if I needed a large scale version, then I could just build it myself with the above solution ^ no?

  6. 1

    Yes, thanks for your answers!

    @Earth256
    Yes, you do it asynchronously and yes you'll probably use a background job scheduler.

    @peterj1994
    Yes, most of the people will use a cron job.

    But to be honest, cron jobs have a lot of cons:

    • Cron does not offer tight scheduling constraints (neither sub-second nor near-real-time)
    • Cron is often not distributed and is managed on one server, so if this one crashes, it does not work anymore.
    • When you used cron for the example I gave above, it will poll your DB every minute to check if it has been more than 7 days than you executed the taskA (to be able to start the taskB). So it generates queries that could be avoided and again it will be more and more complicated when you'll have to scale.
    • If you encountered any errors, you don't have the possibility to retry it

    These are the major ones, I guess.

    My idea is to provide a service that will solve this (not talking about a cron as a service. I like to call it a Wait as a Service.

    If I take the example above

    In your TaskA implementation, you'll add an HTTP call to my service, where you'll give as a body:

    • a callback URL
    • a timestamp to wait until
    • a tag
    • some more options.

    What this service will do is that it will, wait until the timestamp you gave and when it's ready, it will ping your backend on the callback URL you've provided, with the options you gave.

    So in terms of executions:

    In your code, you dispatch the taskA using any background manager you want (Celery, Sideqik, Laravel Jobs, RabbitMQ, etc), the taskA is executed, a wait is registered on my service, 7 days later you receive a webhook on your backend and then you know that you can dispatch the taskB.

    The service will provide an API to create / update / delete those wait programmatically and also you'll have a dashboard to see everything.

    What do you think? Would you want to try this solution?

    1. 1

      Hey Louis,

      Want to offer some constructive feedback.

      To provide value, you must understand what alternative you are competing against, and does your solution provide 10x improvement.

      You are competing against a decent powered SQL database, and https://cloud.google.com/scheduler/ (a reliable cloud scheduler, operated by some of the best engineer in the world ). Your target customers are coders, so chances are they already have an SQL database running.

      Cron does not offer tight scheduling constraints (neither sub-second nor near-real-time)

      You would have to give an example of a business case where you need to execute a task EXACTLY 7 days to the dot after TASK A. For most buisness cases, if you are willing to wait 7 days for the follow up task, latency is not an issue

      Cron is often not distributed and is managed on one server, so if this one crashes, it does not work anymore.

      This is a solved problem -> https://cloud.google.com/scheduler/ (and many more options in the market right now)

      When you used cron for the example I gave above, it will poll your DB every minute to check if it has been more than 7 days than you executed the taskA (to be able to start the taskB). So it generates queries that could be avoided and again it will be more and more complicated when you'll have to scale.

      A cron scheduled every 24hr, or 12hr, or even 4hrs, will be a good enough solution in most buisness cases.

      If you encountered any errors, you don't have the possibility to retry it

      An SQL table with date, and boolean status field will let you easily retry tasks. Further postgres has conditional indexes, so you index only rows that haven't completed yet.

      Your solution also introduces other risks:

      • trusting a young company to operate a reliable cron solution (not a trivial task)
      • Introducing more points of failure: communication between my API and your service, communication between your service and back to my API. (more fault edge cases to monitor, and mitigate)

      I am not saying there isn't a business use case for your service, but you must really figure out who your target customer is, and why they wouldn't just chose an SQL db with a cron.

  7. 1

    If I understand your use case of "Wait for 7 days " correctly, this sounds like something that would be done offline asynchronously. So not inline within your running program but scheduled by your program to be executed by another dedicated program at a desired time (such as sidekiq, Resque, Delayed Job, etc.)

  8. 1

    Most servers have the concept of a cron job (or something similar), which are basically a way to run a command in the future or on a set schedule. From there you just point that command to execute whatever code you need to run, which will be different depending on how you deploy your code.

  9. 1

    This comment was deleted 6 years ago