Add the scheduling module
Three replicas of one application against one PostgreSQL database, proving duplicate @Scheduled execution and then removing it with ShedLock: 24 executions where 8 were due, then 7 for 7 ticks. Also measured: @SchedulerLock without @EnableSchedulerLock does nothing and warns about nothing; spring.task.scheduling.pool.size=1 does not starve a fixedRate job but delays it and fires 35 of 40 executions in a burst; and a node whose clock is 40 seconds fast takes a live lock unless the provider uses usingDbTime().
This commit is contained in:
70
scheduling/README.md
Normal file
70
scheduling/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# scheduling
|
||||
|
||||
Companion project for **@Scheduled, ShedLock and Distributed Cron: Scheduling That Survives Three
|
||||
Replicas** on [ankurm.com](https://ankurm.com).
|
||||
|
||||
Three replicas of one Spring Boot application run against one PostgreSQL database, and the
|
||||
`job_execution` table records who ran what and when. Counting rows is the measurement.
|
||||
|
||||
## Verified stack
|
||||
|
||||
| Component | Version | Source of the number |
|
||||
|---|---|---|
|
||||
| JDK | 25.0.4.1+1 (Temurin) | `java -version` |
|
||||
| Spring Boot | 4.1.1 | `maven-metadata.xml` on Maven Central |
|
||||
| Spring Framework | 7.0.9 | `spring-boot-dependencies-4.1.1.pom` |
|
||||
| ShedLock | 7.9.0 | `maven-metadata.xml`; **not managed by Boot**, so it is pinned in `pom.xml` |
|
||||
| PostgreSQL | 14.24 | the broker the transcripts ran against; `docker-compose.yml` uses 17 |
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
scripts/postgres.sh start # throwaway PostgreSQL 14 on :55432, no Docker, no root
|
||||
scripts/three-replicas.sh unlocked # 24 executions where 8 were due
|
||||
scripts/three-replicas.sh locked # 7 executions for 7 ticks
|
||||
mvn test # scheduler-pool and clock-skew evidence
|
||||
scripts/run-all.sh # regenerates every docs/output/ file
|
||||
```
|
||||
|
||||
With Docker instead:
|
||||
|
||||
```bash
|
||||
docker compose up --build # unlocked
|
||||
PROFILE=locked docker compose up --build # locked
|
||||
```
|
||||
|
||||
## Profiles
|
||||
|
||||
| Profile | What is active | Result |
|
||||
|---|---|---|
|
||||
| `unlocked` | `@SchedulerLock` on the job, and nothing else | every replica runs every tick |
|
||||
| `locked` | `LockConfiguration`: `@EnableSchedulerLock` + a `LockProvider` with `usingDbTime()` | one execution per tick |
|
||||
| `appclock` | the same, with a `LockProvider` that has no `usingDbTime()` | used by the clock-skew experiment |
|
||||
| `poolprobe` | three competing `@Scheduled` methods | used by the scheduler-pool tests |
|
||||
|
||||
## Documentation
|
||||
|
||||
| Chapter | What it settles |
|
||||
|---|---|
|
||||
| [01 Three replicas, three executions](docs/01-three-replicas-three-executions.md) | The failure, and why `@SchedulerLock` alone does nothing |
|
||||
| [02 The lock](docs/02-the-lock.md) | How one row and one conditional `UPDATE` fix it, and what the two durations mean |
|
||||
| [03 One scheduler thread](docs/03-one-scheduler-thread.md) | `pool.size=1`: the same execution count, arriving in a burst |
|
||||
| [04 Clock skew](docs/04-clock-skew.md) | A 40-second-fast node taking a live lock, and `usingDbTime()` refusing it |
|
||||
| [05 When not to use a lock](docs/05-when-not-to-use-a-lock.md) | Idempotence, Kubernetes `CronJob`, Quartz, and a pre-ship checklist |
|
||||
|
||||
## Captured output
|
||||
|
||||
| File | What it shows |
|
||||
|---|---|
|
||||
| [`three-replicas-unlocked.txt`](docs/output/three-replicas-unlocked.txt) | 8 + 8 + 8 executions, 9 overlapping pairs, an empty lock table |
|
||||
| [`three-replicas-locked.txt`](docs/output/three-replicas-locked.txt) | 7 executions, 0 overlaps, one lock row |
|
||||
| [`scheduler-pool-1.txt`](docs/output/scheduler-pool-1.txt) | 40 executions, longest gap 1995 ms, 35 of them in a burst |
|
||||
| [`scheduler-pool-4.txt`](docs/output/scheduler-pool-4.txt) | 41 executions, longest gap 201 ms, no burst |
|
||||
| [`clock-skew.txt`](docs/output/clock-skew.txt) | Two holders of one lock, and `usingDbTime()` preventing it |
|
||||
| [`tests.txt`](docs/output/tests.txt) | The test run behind the last three |
|
||||
|
||||
## A note on the database
|
||||
|
||||
`scripts/postgres.sh` downloads the PostgreSQL 14 `.deb` packages and unpacks them into
|
||||
`target/pg` — no Docker, no root, no system-wide install. It exists because the transcripts had to
|
||||
be reproducible on a machine with neither. If you have Docker, `docker-compose.yml` is simpler.
|
||||
Reference in New Issue
Block a user