Performance Engineering
Optimisation without measurement is decoration. This skill measures, finds the
one thing that dominates, fixes it, and measures again.
1. The rule
No optimisation without a baseline.
No claim of improvement without a delta.
No delta without the same conditions on both sides.
A change that makes a page feel faster and cannot be shown to be faster was
not a performance change.
2. Protocol
- Define the symptom. What is slow, for whom, on what data, at what
percentile. A page that is slow for one user with fifty thousand rows is a
different problem from one slow for everyone.
- Baseline. Measure the current state with a repeatable method and record
the conditions: data volume, network, hardware, cold or warm.
- Find the dominant cost. One thing usually accounts for most of it. Find
it with a profile, a trace, a query log or a waterfall, not by reading code
and guessing.
- Set a target. A number, from a product requirement or a budget. Without
one, optimisation has no stopping point.
- Fix the dominant cost only. One change at a time, so the delta is
attributable.
- Measure again, same conditions.
- Stop when the target is met. Continuing past it trades readability for
nothing.
- Record the baseline, the change and the delta, so the next person does
not redo the analysis.
3. Frontend
Ordered by how often each dominates.
| Cost |
Symptom |
Check |
| network waterfall |
slow first paint |
requests that could be parallel, or removed |
| bundle size |
slow load on real networks |
build analysis, largest modules |
| unnecessary requests |
busy network panel |
duplicate fetches, refetch on every render |
| images |
large transfer, layout shift |
dimensions, format, responsive sources |
| render count |
interaction lag |
profile, count renders per interaction |
| list rendering |
scroll stutter |
number of nodes, virtualisation threshold |
| expensive computation |
frozen interaction |
profile the long task |
| layout thrash |
jank |
reads and writes to layout interleaved |
| fonts |
text flash |
loading strategy, subset, preload |
Rules:
- measure with the profiler before adding memoisation; most memoisation added
by reflex is cost without benefit;
- split code at route boundaries and for large rarely used components;
- import single functions, not whole libraries;
- give images explicit dimensions and use the project's pipeline;
- virtualise only past the point where the measurement shows a problem.
4. Backend
| Cost |
Symptom |
Check |
| N plus 1 |
latency grows with row count |
query log, count queries per request |
| missing index |
one slow query dominates |
query plan, sequential scan on a large table |
| unbounded query |
memory and latency grow with data |
absent limit or pagination |
| oversized payload |
large response, slow serialisation |
field list, included relations |
| serial external calls |
latency is the sum of dependencies |
calls that could be concurrent |
| no caching |
repeated identical work |
hit rate on a hot read |
| blocking work |
throughput collapses under load |
synchronous work on the request path |
| connection pressure |
timeouts under load |
pool size, transaction duration |
Rules:
- count queries per request; the number is a better early signal than any
timing;
- select the fields the caller needs, not the entity;
- paginate every list, with a maximum;
- make independent external calls concurrent, dependent ones sequential;
- move work that the response does not need off the request path;
- keep transactions short, and never hold one across a network call.
5. Database
Plan read it, do not assume the index is used
Index on filter, join and order columns; equality before range in a
composite
Selectivity an index on a low cardinality column often does nothing
Write cost every index slows writes; count them before adding another
Joins check the join order and the row estimates against reality
Statistics stale statistics produce a plan that made sense last month
Locks long transactions and lock waits look like slow queries
An index added without reading the plan before and after is a guess with a
maintenance cost.
6. Measurement discipline
- Same data volume, same environment, same cache state on both sides.
- Multiple runs; report the median and the tail, not the best run.
- The tail is what users complain about. A median improvement with a worse
tail is usually a regression.
- Measure with realistic data. A table with a hundred seeded rows proves
nothing about the table with two million.
- Report the conditions with the number. A number without conditions cannot be
reproduced or challenged.
7. Prohibitions
- No optimisation of code that was never measured.
- No caching added before the cost is understood; a cache is a correctness
risk traded against a cost that may not exist.
- No micro optimisation of code that runs once.
- No readability sacrificed for an unmeasured gain.
- No index added without reading the plan.
- No claim of a percentage improvement without both numbers.
- No optimisation that changes behaviour, unless the behaviour change is the
point and is stated.
8. Report format
Symptom the team dashboard takes 4 to 6 seconds to become interactive
Conditions 1,200 orders, seeded, warm cache, local network throttled to Fast 3G
Baseline LCP 4.8s median over 5 runs, 3.9 to 6.1 range
Dominant 142 queries per request, from a loop over orders loading customers
Target under 2s LCP, from the product requirement
Change single query with a join, one index on orders.customer_id
After LCP 1.4s median, 1.2 to 1.7 range, 3 queries per request
Delta queries 142 to 3, LCP 4.8s to 1.4s
Not done bundle is 480 kB, above the 400 kB budget. Separate task, recorded.
9. Auto-critique
Score from 0 to 5: symptom defined, baseline recorded with conditions,
dominant cost identified by measurement, one change at a time, delta measured
under identical conditions, tail reported, stopped at the target, no behaviour
changed silently.
Threshold: no axis below 3, average at least 4. A report with an after number
and no before number is not a report.
10. Interfaces
- Upstream:
project-exploration, debugging when the symptom is a defect.
- Lateral:
backend-engineering, frontend-engineering,
dependency-selection for size impact.
- Downstream:
code-review-protocol, release-readiness,
project-continuity.
1---2name: performance-engineering3description: Performance Engineering4---56# Performance Engineering78Optimisation without measurement is decoration. This skill measures, finds the9one thing that dominates, fixes it, and measures again.1011## 1. The rule1213```14No optimisation without a baseline.15No claim of improvement without a delta.16No delta without the same conditions on both sides.17```1819A change that makes a page feel faster and cannot be shown to be faster was20not a performance change.2122## 2. Protocol23241. **Define the symptom.** What is slow, for whom, on what data, at what25 percentile. A page that is slow for one user with fifty thousand rows is a26 different problem from one slow for everyone.272. **Baseline.** Measure the current state with a repeatable method and record28 the conditions: data volume, network, hardware, cold or warm.293. **Find the dominant cost.** One thing usually accounts for most of it. Find30 it with a profile, a trace, a query log or a waterfall, not by reading code31 and guessing.324. **Set a target.** A number, from a product requirement or a budget. Without33 one, optimisation has no stopping point.345. **Fix the dominant cost only.** One change at a time, so the delta is35 attributable.366. **Measure again**, same conditions.377. **Stop** when the target is met. Continuing past it trades readability for38 nothing.398. **Record** the baseline, the change and the delta, so the next person does40 not redo the analysis.4142## 3. Frontend4344Ordered by how often each dominates.4546| Cost | Symptom | Check |47|---|---|---|48| network waterfall | slow first paint | requests that could be parallel, or removed |49| bundle size | slow load on real networks | build analysis, largest modules |50| unnecessary requests | busy network panel | duplicate fetches, refetch on every render |51| images | large transfer, layout shift | dimensions, format, responsive sources |52| render count | interaction lag | profile, count renders per interaction |53| list rendering | scroll stutter | number of nodes, virtualisation threshold |54| expensive computation | frozen interaction | profile the long task |55| layout thrash | jank | reads and writes to layout interleaved |56| fonts | text flash | loading strategy, subset, preload |5758Rules:5960- measure with the profiler before adding memoisation; most memoisation added61 by reflex is cost without benefit;62- split code at route boundaries and for large rarely used components;63- import single functions, not whole libraries;64- give images explicit dimensions and use the project's pipeline;65- virtualise only past the point where the measurement shows a problem.6667## 4. Backend6869| Cost | Symptom | Check |70|---|---|---|71| N plus 1 | latency grows with row count | query log, count queries per request |72| missing index | one slow query dominates | query plan, sequential scan on a large table |73| unbounded query | memory and latency grow with data | absent limit or pagination |74| oversized payload | large response, slow serialisation | field list, included relations |75| serial external calls | latency is the sum of dependencies | calls that could be concurrent |76| no caching | repeated identical work | hit rate on a hot read |77| blocking work | throughput collapses under load | synchronous work on the request path |78| connection pressure | timeouts under load | pool size, transaction duration |7980Rules:8182- count queries per request; the number is a better early signal than any83 timing;84- select the fields the caller needs, not the entity;85- paginate every list, with a maximum;86- make independent external calls concurrent, dependent ones sequential;87- move work that the response does not need off the request path;88- keep transactions short, and never hold one across a network call.8990## 5. Database9192```93Plan read it, do not assume the index is used94Index on filter, join and order columns; equality before range in a95 composite96Selectivity an index on a low cardinality column often does nothing97Write cost every index slows writes; count them before adding another98Joins check the join order and the row estimates against reality99Statistics stale statistics produce a plan that made sense last month100Locks long transactions and lock waits look like slow queries101```102103An index added without reading the plan before and after is a guess with a104maintenance cost.105106## 6. Measurement discipline107108- Same data volume, same environment, same cache state on both sides.109- Multiple runs; report the median and the tail, not the best run.110- The tail is what users complain about. A median improvement with a worse111 tail is usually a regression.112- Measure with realistic data. A table with a hundred seeded rows proves113 nothing about the table with two million.114- Report the conditions with the number. A number without conditions cannot be115 reproduced or challenged.116117## 7. Prohibitions118119- No optimisation of code that was never measured.120- No caching added before the cost is understood; a cache is a correctness121 risk traded against a cost that may not exist.122- No micro optimisation of code that runs once.123- No readability sacrificed for an unmeasured gain.124- No index added without reading the plan.125- No claim of a percentage improvement without both numbers.126- No optimisation that changes behaviour, unless the behaviour change is the127 point and is stated.128129## 8. Report format130131```132Symptom the team dashboard takes 4 to 6 seconds to become interactive133Conditions 1,200 orders, seeded, warm cache, local network throttled to Fast 3G134Baseline LCP 4.8s median over 5 runs, 3.9 to 6.1 range135Dominant 142 queries per request, from a loop over orders loading customers136Target under 2s LCP, from the product requirement137Change single query with a join, one index on orders.customer_id138After LCP 1.4s median, 1.2 to 1.7 range, 3 queries per request139Delta queries 142 to 3, LCP 4.8s to 1.4s140Not done bundle is 480 kB, above the 400 kB budget. Separate task, recorded.141```142143## 9. Auto-critique144145Score from 0 to 5: symptom defined, baseline recorded with conditions,146dominant cost identified by measurement, one change at a time, delta measured147under identical conditions, tail reported, stopped at the target, no behaviour148changed silently.149150Threshold: no axis below 3, average at least 4. A report with an after number151and no before number is not a report.152153## 10. Interfaces154155- Upstream: `project-exploration`, `debugging` when the symptom is a defect.156- Lateral: `backend-engineering`, `frontend-engineering`,157 `dependency-selection` for size impact.158- Downstream: `code-review-protocol`, `release-readiness`,159 `project-continuity`.