Wheels 4.2 adds recurring background jobs and one-at-a-time migrations

3 hours ago

Wheels 4.2 is out, and a lot of it is for apps that run on more than one server. Jobs run on a schedule, models get callbacks that wait for the commit, and migrations take turns. Then come the breaking changes, and the upgrade.

Ask about this presentation

Chapters

  1. 0:00Wheels 4.2
  2. 0:15Recurring jobs
  3. 0:34Jobs across servers
  4. 0:56afterCommit callbacks
  5. 1:14Migrations take turns
  6. 1:34returnAs structs is an array
  7. 1:54Proxy headers and deploy ports
  8. 2:16Behaviour changes to check
  9. 2:35Upgrade
Show transcript
  1. Wheels 4.2 is out, and a lot of it is for apps that run on more than one server. Jobs run on a schedule, models get callbacks that wait for the commit, and migrations take turns. Then come the breaking changes, and the upgrade.

    Slide text
    Wheels
    v4.2.0

    Wheels 4.2 adds recurring background jobs and one-at-a-time migrations

    config/schedules.cfm afterCommit migrate: true wheels jobs drain
  2. Recurring jobs are defined in config/schedules.cfm. Each schedule names a job, then a cron expression with a time zone, or an interval such as every 15 minutes. Each due slot is enqueued once, however many servers check, and after downtime only the newest missed slot runs.

    Slide text
    New Breaks Upgrade

    Define recurring jobs in config/schedules.cfm

    <cfscript>
    schedule("digest")
      .job("DigestJob")
      .cron("0 7 * * MON")
      .timezone("America/New_York");
    
    schedule("canary")
      .job("CanaryJob")
      .every(15, "minutes");
    </cfscript>
    Every worker poll enqueues due slots. No separate scheduler process.
    Each slot is enqueued once, however many servers check.
    After downtime, catchUp("latest") runs the newest missed slot.
  3. Jobs also get controls for running on several servers. With a unique key, a duplicate enqueue writes nothing. An exclusive job can't start a second run anywhere while one is running. A long job can call heartbeat, so it isn't reclaimed and run twice. And wheels jobs drain with wait holds a deploy until the running jobs finish.

    Slide text
    New Breaks Upgrade

    Background jobs now coordinate across servers

    enqueue(uniqueKey=…)
    One job per key, even when two enqueues race
    this.exclusive = true
    One run of the job class at a time, on any server
    heartbeat()
    A long job that keeps reporting isn't reaped and re-run
    wheels jobs drain --wait
    Running jobs finish before a deploy stops the server
  4. Models get two new callbacks. afterCommit runs only once the transaction commits, so it's where you enqueue a job or send mail. afterRollback runs when it rolls back. Both need a transaction that Wheels manages. And transaction savepoint lets a nested unit undo only its own writes.

    Slide text
    New Breaks Upgrade

    Side effects can wait for the commit

    in a model's config()

    afterCommit(methods="enqueueSearchIndex", on="create,update");
    afterRollback("releaseReservation");
    Nested work: transaction="savepoint" rolls back only its own writes.
  5. Instances that share a database now take turns migrating, under a lock. With wheels deploy, add migrate true to deploy.yml, and migrations run once per deploy, on one host. A failed one stops the deploy. If a lock gets stuck, wheels migrate unlock names the holder, and the force flag clears it.

    Slide text
    New Breaks Upgrade

    Servers that share a database now migrate one at a time

    config/deploy.yml

    migrate: true
    Runs once per deploy, on one host. A failed migration stops the deploy.

    a stuck lock

    $ wheels migrate unlock
    $ wheels migrate unlock --force
    unlock names the holder; --force removes the lock.
  6. The first breaking change: findAll with returnAs structs now returns an array, as documented, instead of a struct keyed by row number. StructCount, and loops over its keys, need to change. Use ArrayLen and loop over the rows. Indexing by position still works, and the result serializes to a JSON array.

    Slide text
    New Breaks Upgrade

    findAll(returnAs="structs") now returns an array

    4.1
    StructCount(rows)
    for (key in rows) { rows[key] }
    4.2
    ArrayLen(rows)
    for (row in rows) { row }
    rows[1] still works. The result serializes to a JSON array.
  7. Two more breaking changes involve the network. X-Forwarded-Proto now makes absolute URLs https only when trustProxyHeaders is on, so behind a proxy that handles TLS, set it before upgrading. And in wheels deploy, an accessory's port must name a bind address. A bare port now fails validation.

    Slide text
    New Breaks Upgrade

    Proxied apps need one setting, and deploy ports need an address

    Behind a TLS-terminating proxy
    set(trustProxyHeaders=true)
    wheels deploy accessory port:
    5432 → "127.0.0.1:5432:5432"
  8. These changes can also affect existing code. An empty string in a where condition now matches stored empty strings, in updateAll and deleteAll too. A failed enqueue throws. Job timestamps move to UTC. And the dev server listens only on your own machine, unless you pass a host.

    Slide text
    New Breaks Upgrade

    Check your code for these behaviour changes

    where="col = ''"
    matches '', not NULL
    enqueue() fails
    throws Wheels.Job.EnqueueFailed
    job timestamps
    stored in UTC
    wheels start
    listens on 127.0.0.1
  9. To upgrade, update the CLI first. Then wheels upgrade check, to 4.2.0, fails on the items that break at runtime on 4.2, and wheels upgrade apply swaps the framework, keeping a backup. App tests now need a separate test datasource. And if your job servers don't run on UTC, upgrade once the queue is empty.

    Slide text
    New Breaks Upgrade

    Update the CLI, check the app, then swap the framework

    $ brew upgrade wheels
    $ wheels upgrade check --to=4.2.0
    $ wheels upgrade apply
    Wait until the job queue is empty if your job servers don't run on UTC.
    guides.wheels.dev/v4-2-0/upgrading/4x-1-to-4x-2/

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️