Case study
Hardening a Self-Service Booking Engine
Concurrency guarantees went from zero executed lock tests to a Postgres-backed suite that proves the locks work, including cross-plan races.
- Project
- Online tutoring platform
- Industry
- EdTech / SaaS
- Role
- Audit, backend rules, concurrency tests, booking UI
- Python
- FastAPI
- SQLModel / SQLAlchemy
- Alembic
- PostgreSQL
- Next.js
- TypeScript
- SWR
- dayjs
- Vitest
The context
A self-service booking engine that lets parents and tutors book, move, and undo lessons against an hours balance without admin involvement, on a multi-country online tutoring platform.
The problem
Booking looked finished: it had 36 passing tests. An audit showed otherwise.
- The core guarantee, "an instructor can't be double-booked", was never executed by any test. The suite ran on in-memory SQLite, which silently ignores SELECT … FOR UPDATE.
- A student could be booked with two different tutors at the same time.
- Failures reported only the first bad slot, so a parent retried slot by slot.
- Undoing a booking sent one "cancelled" email per lesson and counted as a cancellation.
- A repeat-weekly series drifted an hour across a daylight-saving boundary.
My responsibility
- The audit
- Backend booking rules
- The concurrency test harness
- The parent booking UI
Architecture
- Parent / tutor UI → POST /self-scheduleslots[], atomic, capped at 20
- Lock instructor rowFOR UPDATE, shared with the confirm and reschedule paths
- Lock plan rowFOR UPDATE, for stale-plan revalidation
- Check conflictsThe instructor's calendar and the student's own, across any tutor
- Check availability, notice, bufferAll failures returned together as failed_slots[]
- Insert sessions and debit hoursOne transaction
- Undo within 10 minutesAtomic, one "withdrawn" digest, not a cancellation
- Late cancel → admin review queueWaive releases the hour, no-show charges it
Key decisions
Two locks on purpose
The instructor lock serialises against the admin confirm and reschedule paths. The plan lock exists for stale-plan revalidation and also serialises same-plan races. They were added for different defects, and the redundancy is defense in depth.
The student's own calendar joins the conflict check
It returns a distinct student_already_booked reason.
Undo is not a cancellation
It has its own endpoint, a time-bounded grace window, and a single digest email.
Late cancels never block
They open a review case for an admin to rule on, because the business rule was "cancel any time, adjudicate afterwards".
Series dates rebuilt from local date strings
dayjs.add(7, 'day') is instant-based and drifts across DST, so each week is rebuilt from its own local date and time.
Technical challenges
1.Proving a lock works
A Postgres-backed suite with two real connections and transactions. Same-plan races pass even with the instructor lock removed, because the plan lock catches them; only cross-plan, same-instructor tests exercise it. A meta-test neutralises the lock and asserts the race appears.
2.Reporting every bad slot at once
Done backwards-compatibly: the first failure stays in the old top-level fields and the full list goes in failed_slots[].
3.A latent shadowing bug
A timezone: str parameter shadowed the datetime module and crashed when re-posting an existing exception date.
4.Deploy ordering
The frontend's new calls 404 on an old API, so the backend ships first.
Results
- Concurrency guarantees went from 0 executed lock tests to [X] Postgres-backed ones, including cross-plan cases
- Double-bookings in production: [X → 0]
- Failed-booking retries per parent session: [X → Y]
- Support tickets about vanished bookings or duplicate cancellation emails: [X% drop]
- Parent time-to-book: [X taps / seconds]
What I learned
- "Tests pass" says nothing about a lock. Check that the test engine can express the behaviour before trusting the test.
- Removing one of two redundant locks looks safe until a cross-plan test says otherwise. Mutation-test your safety claims.
- Model undo as its own domain operation, not as a cancellation with an exception.