π When I first learned Spring's @Scheduled
, it felt too easy…
Done, right?
Well… almost. π
But then came the million-dollar question πΈπ£:
π¬ “What happens if I deploy this in a cloud setup like GCP or OpenShift with minimum 2 instances running?”
Will it execute twice? Once on each instance? Or will the cloud gods be kind and run it just once? π©️
Let’s take a deep dive — not from a bug I faced, but something I researched deeply before implementing my first scheduler in the cloud.
π΅ Wait, Will It Run Twice?!?
Let’s say your Spring Boot app is deployed in GCP with 2 pods (or OpenShift with 2 replicas).
Your app starts... and both instances boot up this code:
⚠️ Unless you take extra steps — YES, both instances will happily execute the same job at 9 AM.
Yup, double mail… double expense… double trouble. π§¨πΌ
π€― But Wait — Why Doesn’t GCP/OpenShift Prevent That?
Well, because it's not their job. π
They run containers. They load balance requests. But unless you configure external job orchestration (like Cloud Scheduler, Kubernetes CronJob, or custom logic), they won’t stop your app's scheduler from firing.
π‘ Each instance is isolated. Spring doesn't know it's running twice.
π§ The Crazy-Dumb Questions I Asked (So You Don’t Have To π)
❓ “Will Spring Boot detect duplicate schedulers across instances?”
→ Nope.
❓ “Will Kubernetes stop second pod’s scheduler automatically?”
→ Still nope.
❓ “Can I just pray only one fires?”
→ You can… but your boss may not approve of "faith-based scheduling" π
π Enter: ShedLock — The Lock That Saves the Day!
I discovered a hero called ShedLock, and here’s what it does:
π‘️ What is ShedLock?
ShedLock is a Java library that ensures only one instance executes the scheduled job at any given time — by using a distributed lock.
Imagine this:
“Before starting, I’ll check a DB row to see if the job is already running somewhere else.”
If yes → back off.
If no → mark it as locked, and go ahead!
π§ It supports:
-
JDBC (Postgres, MySQL, etc.)
-
MongoDB
-
Redis
-
Hazelcast
π§ͺ So How Does It Work Internally?
Here’s the sequence:
-
You annotate your method with
@SchedulerLock
. -
Before executing, ShedLock:
-
Connects to your configured lock provider (DB, Redis, etc.)
-
Tries to acquire a lock on a named key (like
dailyJob
)
-
-
If lock acquired → execute the job.
-
If not → skip it. Someone else is already running it.
-
After job finishes → it releases the lock.
π¦ Lock info is stored in a table like:
name | lock_until | locked_at | locked_by |
---|---|---|---|
dailyJob | 2025-07-28 09:05 | 2025-07-28 09:00 | pod-xyz-123 |
Even if one instance dies, the lock can timeout after a configured TTL.
✅ Sample Code: JDBC + ShedLock + Spring Boot
1️⃣ Add dependency:
2️⃣ Create lock table:
3️⃣ Enable ShedLock:
4️⃣ Annotate your job:
π¬ What does it all mean?
Annotation | Purpose |
---|---|
@EnableSchedulerLock | Activates ShedLock globally |
defaultLockAtMostFor | Fallback lock timeout if not specified on method |
@SchedulerLock | Defines lock for that method |
lockAtMostFor | Max time the lock will be held |
lockAtLeastFor | Ensures the task won't run again until this period ends |
π Bonus Tip
In production, prefer lock store outside your app — like a DB or Redis shared between pods. So even if one dies, others can read lock status.
π¨ Wrapping Up
This post isn’t about a bug I faced — it’s one of those “Aha! I better understand this before someone calls me at 9 AM” moments. π€
π Recap:
✅ Spring Boot by default triggers schedulers on all instances
π¨ GCP/OpenShift won't stop that — you need locking
π Use ShedLock for distributed locking
π Understand its internal flow and how annotations help
π ️ Always test your schedulers in a cloud-mimicking setup
So next time someone says: “Hey bro, you just need a cron job, right?”
Just smile and ask:
“Yeah, but do you want it triggered twice at 9 AM every day?” π
π§‘ Hope this saved you from getting spammed by your own code!
Stay curious, stay nerdy,
– Anand π
Comments
Post a Comment