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

🐱 Tomcat vs ⚡ Netty – Which One Should You Use?

🐱 Tomcat vs ⚡ Netty – Which One Should You Use? So recently I got curious about this too πŸ€”. Everywhere in Spring Boot tutorials we see Tomcat . Then suddenly while exploring Spring WebFlux , the name Netty pops up. And I was like – “Wait, who’s this Netty guy trying to replace Tomcat?” πŸ˜… Let’s break it down with real-time examples , icons , and fun comparisons . 🐱 Tomcat – The Traditional Web Server Type: Servlet Container (blocking I/O) World: Used with Spring MVC Style: Thread-per-request model πŸ‘©‍πŸ’» Pros: Stable, widely used, battle-tested Cons: Struggles with huge concurrent connections πŸ‘‰ Example in real life: Tomcat is like a restaurant with fixed waiters 🍴. - Each customer = one thread/waiter - If too many customers come in at once → waiters run out → customers wait outside πŸšͺ ⚡ Netty – The Reactive Rockstar Type: Asynchronous Event-Driven Network Framework World: Default for Spring WebFlux Style: Event-lo...

🎭 Spring’s Secret: Why @Transactional & Friends Betray You Silently

πŸ’‘ Lesson Learned — Not a Prod Bug, But a Real Pain No, this wasn’t a production outage. Nobody screamed at me. But I sat for 3 hours wondering: “Why the heck is my @Transactional not rolling back!?” 😡‍πŸ’« “Why is Redis cache not working?” 🀯 Turned out, the issue was one silent villain: 🧱 Self-invocation 🀷 What Is @Transactional ? If you're new: @Transactional = Tells Spring to start a DB transaction when a method is called. It’ll commit if everything’s okay. It’ll rollback if something fails. 🧠 Think of it like wrapping your code in: try { beginTransaction(); // your logic commit(); } catch(Exception e) { rollback(); } πŸ•΅️ Real-Life Analogy — The Gateway Community 🏘️ Let me tell you about my society — it has a strict watchman at the gate. Here’s how it works: πŸ›‚ Watchman = Spring Proxy 🏠 Your apartment = Your service class πŸšͺ Your room = A method inside that class πŸƒ Scenario 1: Outsider Visits Your friend from outside...

🌟 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...