Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Schedulers

A scheduler is a function which runs on a schedule, written in any language of API Maker, : every few seconds, every night at two, on the first of the month, or on any cron expression, in the time zone you pick. On a cluster of servers each run happens once, on one server, and moves to another one when a server goes down.

Schedulers on a cluster : every interval runs once, on one server, spread over the servers, with failover schedulers of the account nightly-report 0 0 2 * * * · Asia/Kolkata sync-stock every 5 minutes · UTC send-reminders Mon-Fri 11:30 · Europe/Paris Redis knows which server runs each scheduler API Maker servers server 1 runs nightly-report server 2 runs sync-stock server 3 down · send-reminders moves Rules • each tick runs once, never on every server • spread over the servers • a server which goes down hands its jobs over • a deploy never breaks a run in progress • start, stop, run now from the admin panel

Create a scheduler

APIs & Logic → Schedulers, then Add New.

Field Meaning
Name Unique in the account. It names the folder in Git ; a rename is followed by the log profiles.
Timeout in minutes The run is stopped after this long, in the sandbox and on the native process alike. Empty : 10 minutes.
Code The function, with the global object g.
Intervals One or more, each with a time interval, a time zone and an active switch.
Active The master switch of the scheduler.
Run Native Run in API Maker itself instead of the sandbox.

The code

import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const before = Date.now() - 7 * 86400000;   // a week ago, in milliseconds since 1970 : what updated_at holds
    const removed = await g.sys.db.removeByQuery({
        instance: 'mongodb', database: 'shop', collection: 'carts',
        find: { updated_at: { $lt: before } },
    });
    g.logger.log(`${removed.deletedRowsCount} carts removed`);
    return { removed: removed.deletedRowsCount };
}
module.exports = main;
import time

from am import G


async def main(g: G):
    before = int(time.time() * 1000) - 7 * 86400000   # a week ago, in milliseconds since 1970 : what updated_at holds
    removed = await g.sys.db.remove_by_query('mongodb', 'shop', 'carts', find={'updated_at': {'$lt': before}})
    g.logger.log(f'{removed.deleted_rows_count} carts removed')
    return {'removed': removed.deleted_rows_count}
1
2
3
4
5
6
7
8
<?php
function main(AmG $g)
{
    $before = (int) (microtime(true) * 1000) - 7 * 86400000;   // a week ago, in milliseconds since 1970 : what updated_at holds
    $removed = $g->sys->db->removeByQuery('mongodb', 'shop', 'carts', find: ['updated_at' => ['$lt' => $before]]);
    $g->logger->log("{$removed->deletedRowsCount} carts removed");
    return ['removed' => $removed->deletedRowsCount];
}
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var before = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - 7 * 86400000L;   // a week ago, in milliseconds since 1970 : what updated_at holds
        var removed = await g.Sys.Db.RemoveByQuery(new RemoveByQuery("mongodb", "shop", "carts")
        {
            Find = new JsonObject { ["updated_at"] = new JsonObject { ["$lt"] = before } },
        });
        g.Logger.Log($"{removed.DeletedRowsCount} carts removed");
        return new { removed = removed.DeletedRowsCount };
    }
}
package code

import (
    "fmt"
    "time"

    "apimaker/am"
)

func Main(g *am.G) (any, error) {
    before := time.Now().UnixMilli() - 7*86400000 // a week ago, in milliseconds since 1970 : what updated_at holds
    removed, err := g.Sys.Db.RemoveByQuery(am.RemoveByQuery{
        Instance: "mongodb", Database: "shop", Collection: "carts",
        Find: am.M{"updated_at": am.M{"$lt": before}},
    })
    if err != nil {
        return nil, err
    }
    g.Logger.Log(fmt.Sprintf("%d carts removed", removed.DeletedRowsCount))
    return am.M{"removed": removed.DeletedRowsCount}, nil
}
use std::time::{SystemTime, UNIX_EPOCH};

use am::{Errors, G, RemoveByQuery, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |since| since.as_millis() as i64);
    let before = now - 7 * 86_400_000; // a week ago, in milliseconds since 1970 : what updated_at holds
    let removed = g.sys.db.remove_by_query(RemoveByQuery::new("mongodb", "shop", "carts").find(json!({ "updated_at": { "$lt": before } })))?;
    g.logger.log(format!("{} carts removed", removed.deleted_rows_count));
    Ok(json!({ "removed": removed.deleted_rows_count }))
}
import am.G;
import am.RemoveByQuery;
import am.RemoveByQueryResult;
import java.util.Map;

class Api {
    static Object main(G g) {
        long before = System.currentTimeMillis() - 7 * 86400000L;   // a week ago, in milliseconds since 1970 : what updated_at holds
        RemoveByQueryResult removed = g.sys.db.removeByQuery(new RemoveByQuery("mongodb", "shop", "carts")
            .find(Map.of("updated_at", Map.of("$lt", before))));
        g.logger.log(removed.getDeletedRowsCount() + " carts removed");
        return Map.of("removed", removed.getDeletedRowsCount());
    }
}
1
2
3
4
5
6
7
8
9
import am.G
import am.RemoveByQuery

fun main(g: G): Any? {
    val before = System.currentTimeMillis() - 7 * 86400000L   // a week ago, in milliseconds since 1970 : what updated_at holds
    val removed = g.sys.db.removeByQuery(RemoveByQuery("mongodb", "shop", "carts").find(mapOf("updated_at" to mapOf("\$lt" to before))))
    g.logger.log("${removed.deletedRowsCount} carts removed")
    return mapOf("removed" to removed.deletedRowsCount)
}
  • The same g as a custom API, without a request : g.req.body and g.req.query are empty. Keep state between runs in the cache : the next run may be on another server.
  • Pick the language in the title bar of the editor.

Intervals

Group Choices
Seconds every second, 5, 10, 15, 30 seconds
Minutes every minute, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 minutes
Hours every hour, 2, 3, 4, 6, 8, 12 hours
Days every day, 2, 3, 5, 10, 15 days
Working days Monday to Friday at 11:30
Months every month, 2, 3, 4, 6 months
Year every year
Custom a cron expression
  • Each interval has its time zone : Asia/Kolkata, Europe/Paris, UTC… A scheduler with two intervals runs both.
  • Each interval has its own active switch, so one can be paused without touching the other.

Custom cron expressions

Six fields, seconds first : second minute hour day-of-month month day-of-week. Five fields work too (the seconds default to 0).

Field Values
second, minute 0-59
hour 0-23
day of the month 1-31
month 1-12, or jan-dec
day of the week 0-7 (0 and 7 are Sunday), or sun-sat

Each field takes *, a value, a range 1-5, a list 1,15 and a step */10. The presets @yearly, @monthly, @weekly, @daily, @hourly, @minutely, @secondly, @weekdays and @weekends stand for a whole expression. The panel checks an expression as you type it, by the same rules.

Expression Runs
0 0 22 * * 1-5 at 22:00, Monday to Friday
0 15 14 1 * * at 14:15 on the first of every month
*/30 * * * * * every 30 seconds
0 0 */6 * * * every 6 hours

On a cluster

  • Once per tick. Whatever the number of API Maker servers, an interval runs once : each scheduler belongs to one server, which holds it in Redis, and a scheduler already running is not started again.
  • Spread out. Every server knows the servers which are up (each one says so every few seconds), and the schedulers are spread over them evenly. A server which joins takes its share; the others keep theirs.
  • At once. A server starts its schedulers as soon as it starts.
  • Failover. When a server stops cleanly, it gives its schedulers back and the others take them at their next look, within five seconds ; when it dies, within about twenty seconds, when its hold runs out.
  • Which server. The Server column of the Schedulers page shows the server running each scheduler.
  • Safe deployments. A Git pull or a deployment does not interrupt a run in progress ; the next run uses the new code.

Start, stop, run now

  • The Active switch starts and stops a scheduler right away, on every server : stop a misbehaving job in production in one click.
  • Run now from the API testing page (pick the scheduler, send) runs it once, outside its schedule, and shows the result and the logs.
  • The Schedulers page marks a scheduler running now on a node, and one whose intervals are all switched off : it never runs. Clone copies a scheduler.
  • Versions : keep several versions of the code, one active.

Good to know

  • A log profile records every run with its duration, and what it returned when Log Response is ticked for the scheduler.
  • Schedulers are files in Git (src/schedulers/<name>/) and deploy with a pull. Their active state and intervals travel with them.
  • A scheduler which needs more than its timeout should split the work : page through the rows and keep a cursor in the cache.
  • Groups grant schedulers to API users for the testing page and the run API.