# Schedulers

> Run code on a schedule in API Maker, in TypeScript, Python, PHP, C#, Go, Rust, Java or Kotlin - intervals from every second to every year or a cron expression, a time zone per interval, one run per cluster spread over the servers with failover, a timeout, versions, and start, stop or run now from the admin panel.

Source: https://docs.apimaker.dev/v4/docs/apis-all/schedulers/user-created-schedulers-api.html

A scheduler is a function which runs on a schedule, written in [any language](https://docs.apimaker.dev/v4/docs/languages/index.html) 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.

> Diagram : Schedulers on a cluster : every interval runs once, on one server, spread over the servers, with failover
>
> Schedulers on a cluster of three API Maker servers : each scheduler has intervals with a time zone ; at each tick exactly one server of the cluster runs it, the schedulers are spread over the servers, and when a server goes down another one picks its schedulers up.

## 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

**TypeScript**

```typescript
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;
```

**Python**

```python
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}
```

**PHP**

```php
<?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];
}
```

**C#**

```csharp
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 };
    }
}
```

**Go**

```go
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
}
```

**Rust**

```rust
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 }))
}
```

**Java**

```java
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());
    }
}
```

**Kotlin**

```kotlin
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](https://docs.apimaker.dev/v4/docs/apis-all/system-apis/system-generated-set-redis-key-api.html#from-code) : 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](https://docs.apimaker.dev/v4/docs/features/developer-tools.html#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](https://docs.apimaker.dev/v4/docs/logs/logs-and-dashboards.html#log-profiles) 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.
