Visitar URL original
feat: interval schedules for functions by ChiragAgg5k · Pull Request #14231 · appwrite/appwrite · GitHub
Skip to content

feat: interval schedules for functions - #14231

Merged
ChiragAgg5k merged 7 commits into
mainfrom
feat/function-interval-schedules
Oct 9, 2026
Merged

ChiragAgg5k merged 7 commits into
mainfrom
feat/function-interval-schedules

Conversation

@ChiragAgg5k

@ChiragAgg5k ChiragAgg5k commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Adds interval schedules for functions, the first phase of #14170. A function can now run every N minutes instead of on a cron expression, and Appwrite picks when inside each interval it runs.

The scheduler builds Shifted(new Interval($minutes * 60), crc32($functionId) % ($minutes * 60)) for these rows, the same pattern the stats scheduler already uses. Each function gets a fixed slot derived from its ID, so a thousand hourly functions spread across the hour instead of all firing at :00. The slot is computed rather than stored, so it survives restarts and redeploys.

API

  • New optional interval param on POST /v1/functions and PUT /v1/functions/:functionId: whole minutes between runs. 0 (the default) disables it. Any value from 1 up to the integer column limit is accepted, so 45 or 90 work as well as 60.
  • schedule and interval are mutually exclusive: sending both returns 400 general_argument_invalid.
  • interval is returned on the Function and Schedule models as an integer.
  • The console-only POST /v1/projects/:projectId/schedules accepts interval for function schedules. schedule is now optional there, and exactly one of the two is required.

Minutes rather than seconds because one minute is also the finest cron resolution, and both kinds of schedule share the same lower bound.

SDK constants such as Interval.Hour * 6 need integer enums, which the SDK generator does not emit yet: every enum template writes string values (StatusCode.Found = '302'). The wire type is already an integer, so those constants can be added later without changing the API.

Storage and scheduling

  • interval integer attribute (default 0) on project functions and platform schedules, added to existing installs by V25.
  • A schedule is active when the function has a deployment and either a cron or an interval. Updated everywhere active is computed: function create/update, deployment activation, and the jobs worker.
  • Rows with a negative or non-integer interval (written outside the API) are reported inactive by Source\Functions::runnable(), so the scheduler drops any previously loaded entry instead of keeping the old cadence.
  • New schedule.interval span attribute next to schedule.cron.

Not in this PR

  • Cloud columns: appwrite-labs/cloud#6399 adds functions.interval and schedules.interval on Cloud and runs before this ships there.
  • Cloud plan gate (free plan: no new crons, interval of at least 60 minutes): parked in appwrite-labs/cloud#6383. The initial rollout leaves cron unchanged on every plan.
  • Console interval picker with the Recommended tag.
  • Integer enum support in the SDK generator, for Interval.Minute / Interval.Hour / Interval.Day constants.
  • Carrying interval through utopia-php/migration exports and the CLI's appwrite.config.json.

SDK usage

Node.js

const sdk = require('node-appwrite');

const functions = new sdk.Functions(client);

// Run once an hour; Appwrite picks the minute
await functions.create({
    functionId: sdk.ID.unique(),
    name: 'Report',
    runtime: sdk.Runtime.Node22,
    interval: 60,
});

// Switch to every 90 minutes
await functions.update({
    functionId: '<FUNCTION_ID>',
    name: 'Report',
    interval: 90,
});

// Back to a fixed cron time (interval defaults to 0, which clears it)
await functions.update({
    functionId: '<FUNCTION_ID>',
    name: 'Report',
    schedule: '0 9 * * *',
});

Python

from appwrite.services.functions import Functions
from appwrite.enums import Runtime
from appwrite.id import ID

functions = Functions(client)

functions.create(
    function_id = ID.unique(),
    name = 'Report',
    runtime = Runtime.NODE_22,
    interval = 6 * 60,  # every 6 hours
)

function = functions.get(function_id = '<FUNCTION_ID>')
print(function.interval)  # 360
print(function.schedule)  # ''

REST

POST /v1/functions
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <API_KEY>

{ "functionId": "unique()", "name": "Report", "runtime": "node-22", "interval": 60 }

Rejected requests:

{ "schedule": "0 * * * *", "interval": 60 }  // 400: set either "schedule" or "interval", not both
{ "interval": -1 }                            // 400: must be between 0 and 2147483647
{ "interval": 1.5 }                           // 400: whole minutes only

Test Plan

  • Unit, only for what e2e cannot observe: Schedule/Source/FunctionsTest pins the hourly phase to crc32(id) % 3600 and checks that rows with an invalid interval (written outside the API) are reported inactive. The ScheduleDatabase fake moved to its own file so both source tests share it.
  • E2E FunctionsSchedule: interval validation (negative, fractional, text, above the column limit), create/get, null means no interval, switching cron ↔ interval on update (including an unlisted value, 45), both-set rejected, and a real run: interval: 1 with a deployment produces an execution with trigger: schedule.
  • E2E Projects/Schedules: interval-only schedule without a schedule field, interval rejected for executions, with cron, and when negative; neither field is still a 400.
  • Regression: FunctionsCustomServerTest create/get/list/update/deployment, GraphQL/FunctionsServerTest, and migrate on an existing install adds the interval columns. These ran against the previous string-enum revision; CI runs them for the minutes revision.
  • PHPStan (host, phpstan.neon), Pint and Rector are clean on the changed files.

Related PRs and Issues

Related: #14170

@tenki-reviewer

tenki-reviewer Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 1 nitpick below.

🧹 Nitpicks (1) — 🟢 1 low
  • 🟢 Integer interval param documented with string example '3600' (Create.php:94) — All three changed endpoints declare interval as an integer param validated by Interval but pass the example as a string (src/Appwrite/Platform/Modules/Projects/Http/Schedules/Create.php:94).

This PR introduces interval-based (fixed-second) triggers for function schedules as an alternative to cron expressions, with mutual exclusion between the two modes enforced across all mutating endpoints.

Files Change
src/Appwrite/Task/Validator/Interval.php, tests/unit/Task/Validator/IntervalTest.php New integer interval validator with an allow-list of values (0 = disabled) plus unit coverage.
src/Appwrite/Platform/Modules/Functions/Http/Functions/*.php, src/Appwrite/Platform/Modules/Projects/Http/Schedules/Create.php Add the interval param, schedule/interval mutual-exclusion guards, and propagation of interval to schedule documents on create, update, and deployment activation.
src/Appwrite/Schedule/Source/*.php, src/Appwrite/Platform/Modules/Functions/Workers/Jobs.php, src/Appwrite/Platform/Tasks/ScheduleFunctions.php Scheduler sources read interval, build deterministic Shifted(Interval) triggers phased by crc32, and compute the active flag from schedule-or-interval.
app/config/collections/*.php, src/Appwrite/Migration/Version/V25.php Add the interval integer attribute (default 0) to the schedules and functions collections via the V25 migration.
src/Appwrite/Utopia/Response/Model/*.php, src/Appwrite/GraphQL/Types/Mapper.php, src/Appwrite/SDK/Specification/Format/OpenAPI3.php Expose interval in response models, GraphQL mapping, and OpenAPI specs.
tests/e2e/**, tests/unit/Schedule/** E2E coverage for interval schedule create/execution and unit coverage for trigger phasing, with the shared ScheduleDatabase fixture extracted.

Reviewed commit: e3b08d6

@ChiragAgg5k ChiragAgg5k self-assigned this Oct 7, 2026
@hansi-codes

hansi-codes Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Tier S · Ready to merge

The incremental changes only add a safe zero default for missing interval attributes; no actionable defects were found in the new diff.

Adds minute-based interval schedules for functions and console-created function schedules, using deterministic per-function offsets alongside cron schedules. The API and response models expose interval values, migrations add storage support, and deployment/job flows synchronize schedule activation. Scheduler tracing and source behavior are updated, with e2e and unit coverage for validation, persistence, mode changes, and execution.

Latest changes: The newest commits default missing function interval attributes to 0 when creating or synchronizing platform schedule rows.

Verdict New comments Fixed Still open
✅ Approved 0 0 0
📂 Walkthrough · 13
File Change
app/config/collections/platform.php; app/config/collections/projects.php; src/Appwrite/Migration/Version/V25.php Add integer interval storage to schedule and function collections and migrate existing installations.
src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php; src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php Accept and persist function interval settings, keep scheduler records synchronized, and default missing intervals to zero.
src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php; src/Appwrite/Platform/Modules/Functions/Workers/Jobs.php Update schedule state when deployments activate and when jobs complete.
src/Appwrite/Platform/Modules/Projects/Http/Schedules/Create.php Support interval-only function schedules and validate mutually exclusive scheduling modes.
src/Appwrite/Platform/Tasks/ScheduleFunctions.php; src/Appwrite/Schedule/Source/Database.php; src/Appwrite/Schedule/Source/Functions.php; src/Appwrite/Schedule/Interval.php Load interval schedules, construct stable phased triggers, and add interval schedule tracing.
src/Appwrite/Utopia/Response/Model/Func.php; src/Appwrite/Utopia/Response/Model/Schedule.php Expose interval values in function and schedule response models.
tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php; tests/e2e/Services/Projects/Schedules/SchedulesConsoleClientTest.php Cover interval API validation, persistence, scheduling mode changes, and scheduled executions.
tests/unit/Schedule/Source/DatabaseTest.php; tests/unit/Schedule/Source/FunctionsTest.php; tests/unit/Schedule/Source/ScheduleDatabase.php Test schedule source behavior, interval phasing, invalid intervals, and shared database test support.
src/Appwrite/Migration/Version/V25.php Add interval attributes to existing installations.
src/Appwrite/Platform/Tasks/ScheduleFunctions.php Include intervals in scheduling spans.
tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php Cover interval validation, persistence, mode switches, and scheduled execution.
tests/e2e/Services/Projects/Schedules/SchedulesConsoleClientTest.php Cover interval-only schedules and invalid scheduling combinations.
tests/unit/Schedule/Source/FunctionsTest.php Test stable interval phases and unsupported intervals; simplify fixture helpers.

Reviewed the commits since 29855bf · Details · Comment @hansi-codes review to re-run, or mention @hansi-codes with a question.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Tier A · See the inline comments. Summary

Comment thread src/Appwrite/Platform/Modules/Projects/Http/Schedules/Create.php Outdated
Comment thread src/Appwrite/Schedule/Source/Functions.php Outdated
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Security rules

No new WARNING or ERROR findings from security rules.

32 existing findings tracked in .semgrep/baseline.json
  • php.appwrite.guest-write-without-abuse-limit (17)
  • php.appwrite.permissive-write-permission (8)
  • php.appwrite.secret-compare-timing (5)
  • php.appwrite.weak-secret-env-default (2)

Posted by Checks / Rules. Re-runs update this comment in place. Rule details and baseline: .semgrep/README.md.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → feat/function-interval-schedules (after).

Metric Before After Change
🚀 Requests/sec 183.71 185.4 ⚪ +0.9%
⏱️ Latency P50 92.37 ms 93.31 ms ⚪ +1%
⏱️ Latency P95 225.98 ms 217.7 ms ⚪ -3.7%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 93.31 217.7 11,571 185.4 -8.28
Account 176.01 353.35 609 10.09 +13.8
TablesDB 90.23 169.93 6,293 102.51 -23.23
Storage 87.1 185.78 3,045 51.86 +2.21
Functions 129.11 274.63 1,624 28.33 +15.25

Top API waits (after)

API request Max wait (ms)
account.name.update 493.58
functions.variables.update 426.13
account.prefs.update 404.4
tablesdb.rows.update 393.11
functions.delete 384.12

Integer enums are generated as string enums in the SDKs, so a string enum gives SDK users named values (Interval.OneHour) with a matching wire type. Rows with an unknown interval are now reported inactive so the scheduler drops any entry loaded before, and the console schedules API no longer requires an empty cron for interval schedules.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Tier S · Looks good to merge. Summary

Any whole number of minutes is a valid interval, so callers are not limited to a fixed list. The value stays an integer on the wire, which leaves room for SDK constants (Interval.Hour * 6) once the generator can emit integer enums.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Tier B · 1 blocking finding to address. Summary

Comment thread src/Appwrite/Schedule/Source/Functions.php
@ChiragAgg5k

Copy link
Copy Markdown
Member Author

@hansi-codes review

@hansi-codes

hansi-codes Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Already reviewed 29855bf. No new commits since then, so I didn't post another review.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Tier S · Looks good to merge. Summary

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant