Skip to main content

πŸ•°️ Cron Chaos in the Cloud? How I Learned to Outsmart Double Scheduler Execution! 🧠πŸ’₯

 πŸ” When I first learned Spring's @Scheduled, it felt too easy…


@Scheduled(cron = "0 0 9 * * ?") // Every day at 9 AM public void sendDailyReports() { System.out.println("Triggered!"); }

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:


@Scheduled(cron = "0 0 9 * * ?") public void emailBoss() { System.out.println("πŸ“§ Sent morning update!"); }

⚠️ 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:

  1. You annotate your method with @SchedulerLock.

  2. Before executing, ShedLock:

    • Connects to your configured lock provider (DB, Redis, etc.)

    • Tries to acquire a lock on a named key (like dailyJob)

  3. If lock acquired → execute the job.

  4. If not → skip it. Someone else is already running it.

  5. After job finishes → it releases the lock.

πŸ“¦ Lock info is stored in a table like:

namelock_untillocked_atlocked_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:

<dependency>
<groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-spring</artifactId> <version>5.12.0</version> </dependency> <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-jdbc-template</artifactId> <version>5.12.0</version> </dependency>

2️⃣ Create lock table:

CREATE TABLE shedlock (
name VARCHAR(64), lock_until TIMESTAMP, locked_at TIMESTAMP, locked_by VARCHAR(255), PRIMARY KEY (name) );

3️⃣ Enable ShedLock:

@Configuration
@EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class SchedulerConfig {}

4️⃣ Annotate your job:


@Component
public class MyJob { @Scheduled(cron = "0 0 9 * * ?") @SchedulerLock(name = "dailyReportJob", lockAtLeastFor = "5m", lockAtMostFor = "10m") public void sendReport() { System.out.println("Sending report..."); } }

πŸ’¬ What does it all mean?

AnnotationPurpose
@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

Popular posts from this blog

πŸ” Is final Really Final in Java? The Truth May Surprise You 😲

πŸ’¬ “When I was exploring what to do and what not to do in Java, one small keyword caught my eye — final . I thought it meant: locked, sealed, frozen — like my fridge when I forget to defrost it.”   But guess what? Java has its own meaning of final… and it’s not always what you expect! πŸ˜… Let’s break it down together — with code, questions, confusion, jokes, and everything in between. 🎯 The Confusing Case: You Said It's Final... Then It Changed?! 🫠 final List<String> names = new ArrayList <>(); names.add( "Anand" ); names.add( "Rahul" ); System.out.println(names); // [Anand, Rahul] 🀯 Hold on... that’s final , right?! So how on earth is it still changing ? Time to dive deeper... 🧠 Why Is It Designed Like This? Here’s the key secret: In Java, final applies to the reference , not the object it points to . Let’s decode this like a spy mission πŸ•΅️‍♂️: Imagine This: final List<String> names = new ArrayList <>(); Be...

🌟 My Journey – From Zero to Senior Java Tech Lead 🌟

 There’s one thing I truly believe… If I can become a Java developer, then anyone in the world can. πŸ’― Sounds crazy? Let me take you back. πŸ•“ Back in 2015… I had zero coding knowledge . Not just that — I had no interest in coding either. But life has its own plans. In 2016, I got a chance to move to Bangalore and joined a Java course at a training center. That’s where it all started — Every day, every session made me feel like: "Ohhh! Even I can be a developer!" That course didn’t just teach Java — it gave me confidence . πŸ§ͺ Two Life-Changing Incidents 1️⃣ The Interview That Wasn't Planned Halfway through my course, I had to urgently travel to Chennai to donate blood to a family member. After that emotional rollercoaster, I found myself reflecting on my skills and the future. The next day, as I was preparing for my move to Bangalore to complete the remaining four months of my course, I randomly thought — "Let me test my skills... let me just see...

🎒 Java Loops: Fun, Fear, and ForEach() Fails

πŸŒ€ Oops, I Looped It Again! — The Ultimate Java Loop Guide You Won't Forget “I remember this question from one of my early interviews — I was just 2 years into Java and the interviewer asked, ‘Which loop do you prefer and why?’” At first, I thought, “Duh! for-each is cleaner.” But then he grilled me with cases where it fails. 😡 That led me to explore all loop types, their powers, and their pitfalls. Let’s deep-dive into every major Java loop with examples &  real-world guidance so you'll never forget again. πŸ” Loop Type #1: Classic For Loop — “The Old Reliable” ✅ When to Use: You need an index You want to iterate in reverse You want full control over loop mechanics ✅ Good Example: List<String> names = List.of("A", "B", "C"); for (int i = 0; i < names.size(); i++) { System.out.println(i + ": " + names.get(i)); } πŸ”₯ Reverse + Removal Example: List<String> item...