The N+1 Query You Ship Every Sprint
Nine out of ten performance regressions I have diagnosed in production were the same bug wearing different clothes: an unloaded relation inside a loop that nobody noticed because the staging database has 40 rows.
Open to full stack developer roles
Full Stack Developer
I design and build web applications end to end — backends that stay fast and interfaces that stay clear — and I write about how.
Scroll
A Laravel release is a chain of small, provable steps: style, types, tests, assets — and then one atomic switch. This is the pipeline I run, and why each gate is where it is.
shamimwhois/shamim Public
01 Commit step
Everything downstream is derived from one immutable identifier, so "what did we ship" is never a question about branches or a developer's laptop. The commit is the unit of work; the rest of the pipeline only reads it.
git push origin main
02 Prepare step
Composer and npm both key their caches on the lockfile, so a fresh runner is not a cold one. The step does two installs in parallel because they are independent dependency trees.
composer install --prefer-dist
03 Static analysis gate
Pint runs first because it is cheap and loud; a formatting failure never reaches PHPStan. PHPStan then guards the types the next three stages will build on.
pint --test · phpstan analyse
04 Tests gate
03–05 run concurrently: Static analysis, Tests, and Build fan out from one trigger and rejoin at Release.
php artisan test --parallel
05 Build gate
The asset build is a separate lane because it is the one stage that can legitimately fail on a frontend-only change. It produces the fingerprinted files the deploy will serve.
npm run build
06 Release step
The release is a directory, not a tarball on the way to production — it is the thing the deploy will point at. optimize runs here so the config and route caches belong to this release.
releases/<sha>/ · optimize
07 Deploy step
Deploy does two things that must happen in order: the schema moves forward, then the workers are told to pick up the new code. Nothing here re-copies the release — the symlink already did that.
migrate --force · queue:restart
08 Verify check
The health check is the first thing the verifier hits, because it is the cheapest proof that the new code is actually serving. A failing smoke test rolls the symlink back without re-running any other stage.
GET /up · smoke tests
01 — No maintenance page
The release is assembled beside the live one and switched in a single rename, so the site is never serving a half-copied tree. The down page is reserved for schema work that cannot be done in place.
02 — No stale caches
optimize runs inside the deploy, after the code lands and before traffic moves. A config cache built against the previous release is a bug wearing a speed-up's clothes.
03 — No orphaned workers
queue:restart lets Horizon finish the job in hand and then pick up the new code, so a queue worker never serves a release that no longer exists on disk.
Nine out of ten performance regressions I have diagnosed in production were the same bug wearing different clothes: an unloaded relation inside a loop that nobody noticed because the staging database has 40 rows.
Sorting by published_at looks correct until two posts share a timestamp, and then pagination starts silently duplicating and dropping rows.
The reflex to reach for a SPA has a cost most teams never price in: a duplicated domain model and a second place for authorisation to be wrong.
Retrying a non-idempotent job turns a transient network blip into a duplicate charge. The fix is not more retries; it is making the job safe to run twice.
A down() method that drops the column it just created is not reversible. It is a rollback that loses data, which is worse than no rollback at all.
Adding an index makes one read faster and every write slower. Most teams only ever measure the first half of that trade.
Service boundaries, transactional integrity, and domain modelling that survives its second year of feature requests.
Index design, query plans, and eliminating N+1s before they reach production rather than after the pager goes off.
Server-driven UI with Livewire where a SPA would be over-engineering, and knowing which is which.
Test suites that catch regressions rather than assert implementation details, backed by static analysis that runs in CI.
Structured logging, tracing, and metrics that answer "what is slow" without a debugging session.
Documentation and architecture decision records that make the reasoning recoverable a year later.
01
Lead engineer
Double-entry accounting engine handling multi-currency settlement for a payments platform.
Cut month-end reconciliation from 6 hours to 11 minutes by replacing a cron-driven batch with an event-sourced projection built on database-backed queues.
02
Architect
Public REST API serving 40M requests a month across three product teams.
Reduced p95 latency from 840ms to 190ms and eliminated a recurring incident class by introducing a query-budget layer that fails builds, not requests.
03
Author
Internal developer CLI that scaffolds services with migrations, CI, and observability pre-wired.
Brought new-service bootstrap time from two weeks of copy-paste to under an hour, and made the platform team's conventions the path of least resistance.
04
Maintainer
Open-source metrics exporter that turns Horizon queue telemetry into Prometheus dashboards.
Gave platform teams a queue-depth SLO out of the box; adopted by four internal teams in its first quarter and now shipped with the default platform image.
I am most useful when a system has outgrown its first design and nobody agrees on what to do next.