{"id":19414,"date":"2020-08-24T08:33:21","date_gmt":"2020-08-24T06:33:21","guid":{"rendered":"https:\/\/www.inovex.de\/blog\/?p=19414"},"modified":"2022-12-02T08:47:30","modified_gmt":"2022-12-02T07:47:30","slug":"schedule-aws-lambda-invocations-how-slow-schedulers","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/","title":{"rendered":"Schedule AWS Lambda Invocations: How to Build Slow Schedulers"},"content":{"rendered":"<p>In an operating system an efficient scheduler can jump between tasks in nanoseconds. But as the title says that is not always necessary. Today, instead, we will take a look at how to design <em>very slow<\/em> schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We will borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.<\/p>\n<p><!--more--><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_83 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Motivation\" >Motivation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Tech-Stack\" >Tech Stack<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Solution-Overview\" >Solution Overview<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#A-Detailed-Look\" >A Detailed Look<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#The-Editor\" >The Editor<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Data-Model\" >Data Model<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Task-Queue-Manipulation-DynamoDB\" >Task Queue Manipulation (DynamoDB)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Setting-the-scheduler-ticks-EventBridge\" >Setting the scheduler ticks (EventBridge)<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#The-Worker\" >The Worker<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Processing-Tasks-Workflow\" >Processing Tasks Workflow<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Executing-task-actions\" >Executing task actions<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Infrastructure\" >Infrastructure<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#DynamoDB\" >DynamoDB<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Cloudwatch-Rule\" >Cloudwatch Rule<\/a><\/li><\/ul><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#Parting-Words\" >Parting Words<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Motivation\"><\/span>Motivation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>But why would we ever want to do that? Well, imagine building a large-scale content-driven platform like Netflix, YouTube or Amazon Prime Video. A very important part of providing content on platforms like these is content management. And at least when it comes to media, managing the publishing and un-publishing of content specifically plays a big role. This is either due to license agreements with the rights holders of the content or (in the case of YouTube for example) because you want to allow your content-providing users to plan when to publish their videos.<\/p>\n<p>This is one of the use cases where a solution similar to the one in this article comes into play. Another use case would be larger e-commerce applications, where a scheduler like this allows you to automatically apply discounts to specific products or to provide other limited-time offers.<\/p>\n<p>Finally, let\u2019s assume we want to do all of this on a somewhat classical AWS Serverless Stack. This may not be the case in most systems as large as the ones mentioned above. But it&#8217;s a viable option for many small or medium sized platforms with an MVP spirit that still need access to these features.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Tech-Stack\"><\/span>Tech Stack<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Throughout the article and in the proposed solution, we reference or use the following services provided by AWS:<\/p>\n<ul>\n<li><a href=\"https:\/\/aws.amazon.com\/lambda\/\">Lambda<\/a><\/li>\n<li><a href=\"https:\/\/aws.amazon.com\/eventbridge\/\">EventBridge<\/a> \/ Cloudwatch Events<\/li>\n<li><a href=\"https:\/\/aws.amazon.com\/dynamodb\/\">DynamoDB<\/a><\/li>\n<li><a href=\"https:\/\/aws.amazon.com\/cloudwatch\/\">Cloudwatch<\/a> (just for logging)<\/li>\n<\/ul>\n<p>If you don\u2019t know what they do, I recommend checking out their respective documentations. In order to keep this article short, I will not go into too much detail about them and assume a basic understanding of how they work.<\/p>\n<p>The demo applications are written in Golang. Go is an excellent choice for serverless due to very good SDK and runtime support by AWS, easy deployments and extremely fast startup times. However, the concepts I describe here should be easily applicable to any system using the aforementioned AWS tools and could even be adapted to fit a slightly different tech stack or implementations of other cloud providers.<\/p>\n<p>Even if your tech stack is exactly the same, you should view this article only as a starting point. There are still minor inconveniences, bugs, and at least one or two <del>glaring oversights<\/del> compromises that needed to be made, which is why the implementation is a proof of concept more than anything else.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Solution-Overview\"><\/span>Solution Overview<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>So let\u2019s see how our scheduler works. We will go into detail about the individual components and why they are built the way they are in the next section, but let\u2019s first create a rough overview. It helps to visualize the scheduler as four basic components:<\/p>\n<ul>\n<li>A Cloudwatch rule triggers our Lambda function. It is more or less the heart of the scheduler, so let\u2019s call a Lambda invocation a scheduler tick.<\/li>\n<li>Our worker process runs as a Lambda function.<\/li>\n<li>We have a DynamoDB table that is (mis-)used as a task priority queue.<\/li>\n<li>The task editor provides CRUD functionality for the task queue.<\/li>\n<\/ul>\n<p>The following diagram illustrates the relationship between these components:<\/p>\n<figure id=\"attachment_19432\" aria-describedby=\"caption-attachment-19432\" style=\"width: 3015px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-19432 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduler-1.png\" alt=\"scheduler overview\" width=\"3015\" height=\"1915\" \/><figcaption id=\"caption-attachment-19432\" class=\"wp-caption-text\">Scheduler Overview<\/figcaption><\/figure>\n<p>An entrypoint to the system is the submission of a new task using the editor. If the task queue was previously empty or the new task is due before all other tasks, the editor also sets the next scheduler tick to the due date of the new task by transforming it into a cron-like expression and saving that in the rule. The Cloudwatch rule, which has previously been set up to invoke the worker lambda, then triggers at the specified time.<\/p>\n<p>The worker reads all tasks from the queue and processes only those that are due at the time of execution. Finally, it sets the next scheduler tick to the due date of the first item that is not due yet, if there are any items left. If there aren\u2019t, it preferably sets the scheduler tick to the epoch, 1970-01-01T00:00Z. (If anyone working on the AWS team is reading this, thank you for making it possible to not only set individual years, but also past dates as schedule expressions.) This way we can be sure that the scheduler triggered and that there are no scheduled tasks at the moment just by looking at our Cloudwatch rule. We could possibly enable or disable the rule on demand instead, but in a more complex system, disabling a rule could have a different semantic meaning.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"A-Detailed-Look\"><\/span>A Detailed Look<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>With our solution draft completed, let\u2019s dive into the most important aspects of the implementation. All following code snippets are taken from the POC implementation <a href=\"https:\/\/github.com\/inovex\/cloudwatch-scheduler\">on github<\/a>. The whole source code for all components resides in a single repository to keep everything nice and compact. We will start with the editor, continue with the worker and finally go into detail on the remaining components and take a short look at the AWS infrastructure we need.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"The-Editor\"><\/span>The Editor<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The main task of the editor is to interact with both the queue and the Cloudwatch rule to manage existing tasks and the creation of new tasks. In the provided implementation, the editor is not actually executed within AWS. Instead, it uses a set of <a href=\"https:\/\/docs.aws.amazon.com\/general\/latest\/gr\/aws-security-credentials.html\">credentials<\/a> to authenticate as a client and provides a minimalistic HTTP API locally. The reasoning behind this is that in practice, the relevant parts of its code should be integrated into whichever service needs access to the task scheduling feature. Hiding the functionality behind an HTTP API in your production systems not only imposes an additional communication overhead, but also introduces a boundary along technical concerns instead of functional ones, which is usually regarded as outdated (for many good reasons).<\/p>\n<p>So let\u2019s focus on the key concepts here: The task data model, manipulating the task queue and setting the next scheduler tick.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Data-Model\"><\/span>Data Model<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Written as JSON, our data model looks like this:<\/p>\n<pre class=\"lang:default decode:true\" title=\"Data Model as JSON\">{\r\n\r\n  \"id\": \"task_123\",\r\n\r\n  \"due\": \"2020-10-02T00:00:00Z\",\r\n\r\n  \"action\": \"APPLY_SALE\",\r\n\r\n  \"payload\": {\r\n\r\n    \"itemID\": \"item_xyz\",\r\n\r\n    \"newPrice\": 29.99,\r\n\r\n    \"salePercent\": 50\r\n\r\n  }\r\n\r\n}<\/pre>\n<p>The ID is needed internally for DynamoDB, the relevant fields are <span class=\"lang:default decode:true crayon-inline \">due<\/span>\u00a0, <span class=\"lang:default decode:true crayon-inline \">action<\/span>\u00a0 and <span class=\"lang:default decode:true crayon-inline \">payload<\/span>\u00a0. In the code and within DynamoDB, the capitalization of the fields is slightly different only for practical reasons. The date (and time) when the task is to be processed is written as an ISO-8601 date. Go\u2019s <span class=\"lang:default decode:true crayon-inline \">json<\/span>\u00a0 package (and the DynamoDB client we\u2019re using) will automatically parse this into the correct usable data type. The <span class=\"lang:default decode:true crayon-inline \">action<\/span>\u00a0 field identifies what to do in this task and the <span class=\"lang:default decode:true crayon-inline \">payload<\/span>\u00a0 field contains additional parameters that are needed later for the worker to do its job.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Task-Queue-Manipulation-DynamoDB\"><\/span>Task Queue Manipulation (DynamoDB)<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>To keep this article as short as possible, let\u2019s only look at the example of adding a task here.<\/p>\n<pre class=\"lang:go decode:true\" title=\"Scheduling Service\">func isFirstTask(task scheduling.Task, allTasks []scheduling.Task) bool {\r\n\r\n    for _, existing := range allTasks {\r\n\r\n        if existing.Due.Unix() &lt; task.Due.Unix() {\r\n\r\n            return false\r\n\r\n        }\r\n\r\n    }\r\n\r\n    return true\r\n\r\n}\r\n\r\nfunc (s Service) AddTask(task scheduling.Task) error {\r\n\r\n    \/\/ get all tasks or return error\r\n\r\n    tasks, err := s.repo.GetTasks()\r\n\r\n    if err != nil {\r\n\r\n        return err\r\n\r\n    }\r\n\r\n    \/\/ schedule task if new task is the first one to be executed\r\n\r\n    if isFirstTask(task, tasks) {\r\n\r\n        err = s.scheduler.Schedule(task.Due)\r\n\r\n        \/\/ return error if scheduling fails\r\n\r\n        if err != nil {\r\n\r\n            return err\r\n\r\n        }\r\n\r\n    }\r\n\r\n    \/\/ add new task to queue\r\n\r\n    return s.repo.AddTask(task)\r\n\r\n}<\/pre>\n<p>When adding a new task, we first read all the remaining tasks from our DynamoDB Table using the repository and see if the new task\u2019s due date is earlier than that of any other task. I disregarded task queue performance for the sake of simplicity in this demo, but ideally, we would use another product than DynamoDB or do some bookkeeping, so we wouldn\u2019t have to scan the whole table every time we want to add a new task.<\/p>\n<p>The repository implementation is quite simple, thanks to <a href=\"https:\/\/github.com\/guregu\/dynamo\">this DynamoDB client<\/a>.<\/p>\n<pre class=\"lang:go decode:true \" title=\"Task Queue Repository Implementation\">func (r TaskRepository) GetTasks() ([]Task, error) {\r\n\r\n    var out []Task\r\n\r\n    err := r.table.\r\n\r\n        Scan().\r\n\r\n        All(&amp;out)\r\n\r\n    return out, err\r\n\r\n}\r\n\r\nfunc (r TaskRepository) AddTask(task Task) error {\r\n\r\n    return r.table.\r\n\r\n        Put(task).\r\n\r\n        Run()\r\n\r\n}<\/pre>\n<p>The table object on the repository is obtained by calling <span class=\"lang:default decode:true crayon-inline\">.Table(&#8222;table-name&#8220;)<\/span>\u00a0 on the dynamo client instance during repository initialization. We don\u2019t really need to do anything here other than just wiring up the correct table and SDK functions. The attribute mappings are done via struct tags on the Go type:<\/p>\n<pre class=\"lang:go decode:true\" title=\"Typdefinition Task\">type Task struct {\r\n\r\n    ID      string                 `dynamo:\"ID\" json:\"id\"`\r\n\r\n    Due     time.Time              `dynamo:\"Due\" json:\"due\"`\r\n\r\n    Action  string                 `dynamo:\"Action\" json:\"action\"`\r\n\r\n    Payload map[string]interface{} `dynamo:\"Payload\" json:\"payload\"`\r\n\r\n}<\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Setting-the-scheduler-ticks-EventBridge\"><\/span>Setting the scheduler ticks (EventBridge)<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Let\u2019s focus on the Cloudwatch rule manipulation next. Let\u2019s assume we have <a href=\"https:\/\/docs.aws.amazon.com\/eventbridge\/latest\/userguide\/create-eventbridge-scheduled-rule.html\">created a rule<\/a> that triggers our worker lambda on a schedule. I used terraform for this, but more on that later. We can use the <span class=\"lang:default decode:true crayon-inline\">cloudwatchevents<\/span>\u00a0 package from the official Go SDK to manipulate rules. But since that is a bit uncomfortable, let\u2019s create a small wrapper around it. This also enables us to test our <span class=\"lang:default decode:true crayon-inline\">AddTask()<\/span>\u00a0 method above more easily (which is what we would do if this wasn&#8217;t a simple demo).<\/p>\n<pre class=\"lang:go decode:true\" title=\"Cloudwatch Client Wrapper\">type Scheduler struct {\r\n\r\n    ruleName string\r\n\r\n    cw       *cloudwatchevents.CloudWatchEvents\r\n\r\n}\r\n\r\nfunc Client(ruleName string) Scheduler {\r\n\r\n    return Scheduler{\r\n\r\n        ruleName: ruleName,\r\n\r\n        cw:       cloudwatchevents.New(session.Must(session.NewSession())),\r\n\r\n    }\r\n\r\n}\r\n\r\n\/\/ Schedule creates a scheduler tick at the specified time t.\r\n\r\n\/\/ It takes the year, month, day, hour and minutes fields into account.\r\n\r\n\/\/ Cloudwatch will execute the worker lambda function at that time.\r\n\r\nfunc (client Scheduler) Schedule(t time.Time) error {\r\n\r\n    schedule := fmt.Sprintf(\"cron(%s)\", onlyAt(t))\r\n\r\n    input := cloudwatchevents.PutRuleInput{\r\n\r\n        Name:               aws.String(client.ruleName),\r\n\r\n        ScheduleExpression: aws.String(schedule),\r\n\r\n        State:              aws.String(cloudwatchevents.RuleStateEnabled),\r\n\r\n    }\r\n\r\n    _, err := client.cw.PutRule(&amp;input)\r\n\r\n    return err\r\n\r\n}<\/pre>\n<p>We define a type that holds the client instance. Since we only ever manipulate one rule, it can hold the rule name as well. The type provides a <span class=\"lang:default decode:true crayon-inline\">Schedule()<\/span>\u00a0 method that translates a given date into a cron-like expression (which is used within EventBridge) and modify the existing rule using the <span class=\"lang:default decode:true crayon-inline\">PutRule()<\/span>\u00a0 method from the SDK.<\/p>\n<p>The interesting part is the behaviour of <span class=\"lang:default decode:true crayon-inline\">PutRule()<\/span>: It will overwrite the schedule expression and the rule state, but leave the invocation target (our worker lambda) intact. In the SDK, invocation targets are managed via the <span class=\"lang:default decode:true crayon-inline \">PutTargets()<\/span>\u00a0 method.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"The-Worker\"><\/span>The Worker<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In our example, we use a monorepo approach and build the worker binary from the same source as the editor. It uses the same Cloudwatch and DynamoDB client implementations we have already seen, and it only requires some additional functionality. The additional methods can be looked up in the source, but let&#8217;s focus on the interesting parts for the sake of brevity.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Processing-Tasks-Workflow\"><\/span>Processing Tasks Workflow<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>The general workflow of our task processor is the following:<\/p>\n<pre class=\"lang:go decode:true\" title=\"Task Processor\">type processor struct {\r\n\r\n    upTo      time.Time\r\n\r\n    items     ItemService\r\n\r\n    tasks     scheduling.TaskRepository\r\n\r\n    scheduler cloudwatch.Scheduler\r\n\r\n}\r\n\r\nfunc (p processor) processTasks() error {\r\n\r\n    \/\/ retrieve all tasks\r\n\r\n    tasks, err := p.tasks.GetTasks()\r\n\r\n    if err != nil {\r\n\r\n        return err\r\n\r\n    }\r\n\r\n    \/\/ sort tasks by due date\r\n\r\n    sort.Slice(tasks, func(i, j int) bool {\r\n\r\n        return tasks[i].Due.Unix() &lt; tasks[j].Due.Unix()\r\n\r\n    })\r\n\r\n    \/\/ process tasks\r\n\r\n    for _, task := range tasks {\r\n\r\n        \/\/ see if task is due\r\n\r\n        if task.Due.Unix() &gt; p.upTo.Unix() {\r\n\r\n            \/\/ if it isn't, schedule it and exit\r\n\r\n            return p.scheduler.Schedule(task.Due)\r\n\r\n        }\r\n\r\n        \/\/ if it is, process it\r\n\r\n        err = p.processTask(task)\r\n\r\n        if err != nil {\r\n\r\n            \/\/ log processing errors for single tasks\r\n\r\n            fmt.Println(\"error processing task \", task.ID, \": \", err)\r\n\r\n            continue\r\n\r\n        }\r\n\r\n        \/\/ delete task from task queue\r\n\r\n        err = p.tasks.Done(task)\r\n\r\n    }\r\n\r\n    \/\/ if all queued tasks have been processed, disable scheduler\r\n\r\n    return p.scheduler.Unschedule()\r\n\r\n}<\/pre>\n<p>The processor type contains a date (and time) up to which it needs to process the tasks. Usually, this would just be <span class=\"lang:go decode:true crayon-inline\">time.Now()<\/span>\u00a0, but the abstraction is there for testing purposes. It also contains instances of the clients we talked about before. In this demo, there is also an <span class=\"lang:default decode:true crayon-inline\">ItemService<\/span>\u00a0 which can put items on sale. Technically it can\u2019t even do that, it\u2019s just a fa\u00e7ade that logs the task data. But it exemplifies how tasks are processed and how we can separate the different concerns within the worker.<\/p>\n<p>The <span class=\"lang:default decode:true crayon-inline\">processTasks()<\/span>\u00a0 method is executed by the main function after the processor is instantiated. It first obtains a list of tasks (sorted by their due date) which it then iterates over. We pass every due task to the <span class=\"lang:default decode:true crayon-inline\">processTask()<\/span>\u00a0 method, which we will look at in a bit. After processing them, we check them off by simply deleting them from the table using the <span class=\"lang:default decode:true crayon-inline\">Done()<\/span>\u00a0 repository method. Once we encounter the first task that is not due yet, we set the next scheduler tick to its due date and exit. If we run out of tasks, we set the scheduler to the epoch as explained in the beginning before exiting.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Executing-task-actions\"><\/span>Executing task actions<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In order to process a task, we switch on its <span class=\"lang:default decode:true crayon-inline\">Action<\/span>\u00a0 field to find out what it actually is:<\/p>\n<pre class=\"lang:go decode:true\" title=\"Processing a Task\">func (p processor) processTask(t scheduling.Task) error {\r\n\r\n    switch t.Action {\r\n\r\n    case \"APPLY_SALE\":\r\n\r\n        var a SaleAction\r\n\r\n        err := mapstructure.Decode(t.Payload, &amp;a)\r\n\r\n        if err != nil {\r\n\r\n            return err\r\n\r\n        }\r\n\r\n        p.items.ApplySale(a)\r\n\r\n        return nil\r\n\r\n    default:\r\n\r\n        return fmt.Errorf(\"I don't know the action type %s\\n\", t.Action)\r\n\r\n    }\r\n\r\n}\r\n\r\n<\/pre>\n<p>Once we identify what should be done, we decode the previously unknown content of <span class=\"lang:default decode:true crayon-inline\">Payload<\/span>\u00a0 using <a href=\"https:\/\/github.com\/mitchellh\/mapstructure\">this useful library<\/a>. Finally, we can use the obtained payload to call the corresponding service, which executes the action. In this case, all the knowledge needed to process a specific type of task is contained within the worker. Alternatively, we could generate an event or call another service at this point.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Infrastructure\"><\/span>Infrastructure<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>There are three main AWS resources we need: our worker Lambda function, the DynamoDB table containing the tasks and the Cloudwatch rule. For the demo, <a href=\"https:\/\/www.inovex.de\/blog\/terraform-in-an-aws-multi-account-environment\/\">terraform<\/a> was the tool of choice to create and manage them. The whole infrastructure code can be found <a href=\"https:\/\/github.com\/inovex\/cloudwatch-scheduler\/tree\/master\/terraform\">here<\/a>. We will not look at everything in detail in an effort to keep this article as short and concise as possible. Feel free to check out the repository and see how everything comes together.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"DynamoDB\"><\/span>DynamoDB<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>For the task queue, we use a quite basic DynamoDB Table without a range key or indexes as a simple way to persist our task data. However, the implementation in the repository can easily be adjusted to fit any form of persistence available inside and outside of AWS. DynamoDB is meant for fast reads and writes of semi-structured data. Our tasks are indeed semi-structured but the access pattern of our application is very different from what DynamoDB is optimized for. Looking at other options is definitely recommended if you plan to store large amounts of tasks.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Cloudwatch-Rule\"><\/span>Cloudwatch Rule<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>The Cloudwatch rule is set up to invoke the Lambda, which can easily be achieved via the UI or terraform. It should be noted that using terraform in the way we do it here is not ideal: every time a new deployment is made using terraform apply, it changes the rule\u2019s schedule to <span class=\"lang:default decode:true crayon-inline \">cron(* * 1 1 ? 1970)<\/span>\u00a0 again. You can circumvent this by creating a separate terraform deployment, using the <a href=\"https:\/\/docs.aws.amazon.com\/sdk-for-go\/api\/service\/cloudwatchevents\/#CloudWatchEvents.PutTargets\">PutTargets SDK Function<\/a> or by just creating the rule manually in the AWS console and linking it to your lambda once that is deployed.<\/p>\n<h5>EventBridge Limits<\/h5>\n<p>That aside, there is one more thing to say about the rule. You might wonder why we\u2019re only using one rule. Couldn\u2019t we just create a new Cloudwatch rule for every task that should be executed in the future? The definite answer to this is: \u201cIt depends\u201c. There are <a href=\"https:\/\/docs.aws.amazon.com\/eventbridge\/latest\/userguide\/cloudwatch-limits-eventbridge.html\">hard limits<\/a> to the amount of rules per AWS account. Every rule belongs to an event bus. It doesn&#8217;t matter whether it actually triggers on events or at specific times defined by a cron expression. At the time of writing, one event bus is limited to 300 Rules and there is a maximum of 100 Event Buses per Account. While that puts the absolute limit at 30,000 planned tasks, it introduces the complexity of managing multiple event buses after the first 300 rules. And this is assuming we don\u2019t need Cloudwatch rules for anything else. If the absolute number of planned tasks is guaranteed to stay below 300, using a Cloudwatch rule for every task seems like a good alternative. But the actual task itself needs to be stored somewhere anyway, so the additional investment to implement this solution is comparatively small. Considering this, the use case of this scheduler is somewhere in the middle of where it&#8217;s worthwile running one or more workers 24\/7 and where the implementation may not fully be worth the additional effort because there are very few scheduled tasks at a given time.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Parting-Words\"><\/span>Parting Words<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>As stated before, this proof of concept merely provides a starting point for your own implementation. But the article shows the feasibility of the presented \u201csingle-rule\u201c invocation scheduling approach and includes valuable reflections on key concepts in a solution like this.<\/p>\n<p>There is an <a href=\"https:\/\/aws.amazon.com\/blogs\/database\/implementing-priority-queueing-with-amazon-dynamodb\/\">interesting article on the AWS Database Blog<\/a> on how to implement a priority queue within the DynamoDB tables related to the domain. It should be possible to combine those concepts with this scheduler implementation. This way, we can keep domain boundaries intact and perhaps improve task queue performance.<\/p>\n<p>Another way to tackle the problem of technical service boundaries could be to publish the tasks as events once they are due instead of processing them within the worker lambda. We would need more than one Cloudwatch rule for that, but that approach would still scale very well.<\/p>\n<p>With a Key\/Value database like DynamoDB, totally different approaches to implement the same features are also valid. If we take the \u201cputting items on sale\u201c use case as an example, we could simply attach the task payload to the item itself, combined with the time where the item should be on sale. So instead of actively changing data in our database, we calculate everything in somewhat of an event sourcing approach. This strategy doesn\u2019t play well together with indexes though and adds the possibility to increase overall complexity.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In an operating system an efficient scheduler can jump between tasks in nanoseconds. But as the title says that is not always necessary. Today, instead, we will take a look at how to design very slow schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We will borrow concepts [&hellip;]<\/p>\n","protected":false},"author":198,"featured_media":19590,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[71,298,335],"service":[414],"coauthors":[{"id":198,"display_name":"Danny Textores","user_nicename":"dtextores"}],"class_list":["post-19414","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-cloud","tag-microservices","tag-serverless","service-cloud"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Schedule AWS Lambda Invocations Dynamically - inovex GmbH<\/title>\n<meta name=\"description\" content=\"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Schedule AWS Lambda Invocations Dynamically - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2020-08-24T06:33:21+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-12-02T07:47:30+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Danny Textores\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Danny Textores\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"15\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Danny Textores\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/\"},\"author\":{\"name\":\"Danny Textores\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/bbb125230e97d3b91bae174150ff9d92\"},\"headline\":\"Schedule AWS Lambda Invocations: How to Build Slow Schedulers\",\"datePublished\":\"2020-08-24T06:33:21+00:00\",\"dateModified\":\"2022-12-02T07:47:30+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/\"},\"wordCount\":2633,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/08\\\/scheduling-lambdas.png\",\"keywords\":[\"Cloud\",\"Microservices\",\"Serverless\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/\",\"name\":\"Schedule AWS Lambda Invocations Dynamically - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/08\\\/scheduling-lambdas.png\",\"datePublished\":\"2020-08-24T06:33:21+00:00\",\"dateModified\":\"2022-12-02T07:47:30+00:00\",\"description\":\"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/08\\\/scheduling-lambdas.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/08\\\/scheduling-lambdas.png\",\"width\":1920,\"height\":1080,\"caption\":\"A clock on the sign lambda\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/schedule-aws-lambda-invocations-how-slow-schedulers\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Schedule AWS Lambda Invocations: How to Build Slow Schedulers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/bbb125230e97d3b91bae174150ff9d92\",\"name\":\"Danny Textores\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g8153fc033361bad0fd28a5ddcb60b16b\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g\",\"caption\":\"Danny Textores\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/dtextores\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Schedule AWS Lambda Invocations Dynamically - inovex GmbH","description":"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/","og_locale":"de_DE","og_type":"article","og_title":"Schedule AWS Lambda Invocations Dynamically - inovex GmbH","og_description":"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.","og_url":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2020-08-24T06:33:21+00:00","article_modified_time":"2022-12-02T07:47:30+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png","type":"image\/png"}],"author":"Danny Textores","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Danny Textores","Gesch\u00e4tzte Lesezeit":"15\u00a0Minuten","Written by":"Danny Textores"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/"},"author":{"name":"Danny Textores","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/bbb125230e97d3b91bae174150ff9d92"},"headline":"Schedule AWS Lambda Invocations: How to Build Slow Schedulers","datePublished":"2020-08-24T06:33:21+00:00","dateModified":"2022-12-02T07:47:30+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/"},"wordCount":2633,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png","keywords":["Cloud","Microservices","Serverless"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/","url":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/","name":"Schedule AWS Lambda Invocations Dynamically - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png","datePublished":"2020-08-24T06:33:21+00:00","dateModified":"2022-12-02T07:47:30+00:00","description":"We take a look at how to design schedulers for infrequent tasks which are planned days, weeks, months or even years in advance. We borrow concepts from operating system schedulers and adapt them to the serverless world to efficiently schedule AWS Lambda invocations ahead of time.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/08\/scheduling-lambdas.png","width":1920,"height":1080,"caption":"A clock on the sign lambda"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/schedule-aws-lambda-invocations-how-slow-schedulers\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"Schedule AWS Lambda Invocations: How to Build Slow Schedulers"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/bbb125230e97d3b91bae174150ff9d92","name":"Danny Textores","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g8153fc033361bad0fd28a5ddcb60b16b","url":"https:\/\/secure.gravatar.com\/avatar\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b16e76ed9f8752e05fe0393e0784a92d13fc2ed3e920dad4287660382cd3eecd?s=96&d=retro&r=g","caption":"Danny Textores"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/dtextores\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/19414","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/198"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=19414"}],"version-history":[{"count":1,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/19414\/revisions"}],"predecessor-version":[{"id":39794,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/19414\/revisions\/39794"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/19590"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=19414"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=19414"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=19414"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=19414"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}