Lock step
Stop two Glue job runs from doing the same work at once.
The Lock step makes sure two runs don't do the same work at the same time. A run takes a lock on a key, and any other run that asks for the same key while it's held waits, skips or fails, depending on how you set it up.
When to use it
- The same webhook arrives twice at once, and both copies would create the same customer or order.
- A running total in the Cache would lose an update if two sales read and write it at the same moment.
- A scheduled job sometimes runs longer than its interval, and the next run shouldn't start until it's done.
Set it up
| In the app | data field | What it does |
|---|---|---|
| Key | key | What to lock. The same key is shared by every run, so customer:{{ email }} locks one customer and nightly-export locks the whole job. Liquid works. |
| Scope | scope | Job (the default) or Block. See below. |
| Time to live (seconds) | ttl_seconds | A safety expiry in case a run crashes. Default 300, up to 3600. |
| Wait for lock (seconds) | wait_seconds | How long to wait for a busy lock before giving up. Default 0, up to 60. |
| When busy | on_busy | Error (the default), Skip or Continue. See below. |
| Response variable | response_variable | Saves true when this run holds the lock, and false when it was busy. |
Scope
- Job holds the lock until the run ends, whether it succeeds, fails with an error, is stopped by a Stop Job step, or is cleaned up after a crash.
- Block holds the lock only while the steps nested under it run, and releases it straight after, even if one of them fails. Drag the steps you want protected into the Lock's LOCK branch.
If the same run asks for a lock it already holds, it gets it again straight away (the response variable is true).
When busy
| When busy | Scope Job | Scope Block |
|---|---|---|
| Error | The step fails and the job is retried later. | The step fails and the job is retried later. |
| Skip | The rest of the run ends quietly, as a success. | The nested steps are skipped, and the job carries on after the Lock. |
| Continue | The job carries on without the lock. | The same as Skip: the nested steps are skipped, then the job carries on. |
With Error, the step fails with a message like lock "customer:[email protected]" is busy (waited 10s); the job will be retried.
Set the time to live above the job's longest runTime to live is only a safety net for a run that crashes. It isn't refreshed while the job runs, so if a run takes longer than its time to live, the lock expires and another run can take it. Set it comfortably above the worst-case runtime of the steps it protects.
Example 1: Create a customer once, even when the webhook arrives twice
Trigger: Webhook.
- Script: Read the payload, which sets
$.email. - Lock: Lock the customer, Key
customer:{{ email }}, Scope Job, Wait for lock10, When busy Error. - Cache: Read the cached customer, Operation Get, Key
customer:{{ email }}, Response variablecached_id. - If Condition: If the customer is new, Condition
$.cached_id === null. Under THEN:- API Request: Create the customer, Response variable
customer_res. - Cache: Store the customer, Operation Set, Key
customer:{{ email }}, Value set from the new customer's ID.
- API Request: Create the customer, Response variable
The second copy of the webhook waits up to 10 seconds. By the time it gets the lock, the first copy has cached the customer, so the second one finds it and creates nothing. If the first copy is still running after 10 seconds, the second fails and is retried later.
Example 2: Keep a running total safe
Trigger: System Hook, Create Sale.
- Script: Read the sale, which sets
$.saleand$.sale_total. - Lock: Lock this sale, Key
daily-total, Scope Block, Wait for lock30, When busy Error. In its LOCK branch:- Cache: Read the running total, Operation Get, Key
sales:total, Default value0, Response variablerunning. - Script: Add this sale:
$.new_total = Number($.running) + Number($.sale_total); - Cache: Store the total, Operation Set, Key
sales:total, Value{{ new_total }}, Value type Number.
- Cache: Read the running total, Operation Get, Key
- API Request: Post the sale, at the top level, after the lock is released.
Only the read-add-write is locked, so other sales wait a moment rather than for the whole job. (For a simple counter, the Cache step's Increment is already safe without a lock.)
Tips
- Lock as little as you can. Use Block around just the steps that need it, so other runs aren't held up.
- Use Skip for "only one at a time" schedules. A scheduled job with a Job lock and Skip quietly lets a run go if the previous one is still working.
- Check the response variable if you use Continue, to know whether you really have the lock.
Updated about 23 hours ago
