Linux performance / Intermediate: mechanism and scoped diagnosis / Lesson 12
Connect Linux evidence to application waits
Correlate request stages, connection pools, database waits, and host observations before blaming a generic system resource.
On this page
Lesson goals
- Build a shared timeline across application and system layers.
- Separate connection-pool waiting from database execution.
- Test workload-aware hypotheses without changing correctness.
Start with the request’s critical path
A report can wait for admission, a worker, a connection pool, a query, result transfer, serialization, and response delivery. The slowest visible span is not necessarily the original cause: a connection pool may fill because queries became slower downstream. Record timestamps, request class, outcome, and correlation identifiers without copying sensitive payloads. Align clocks and retain uncertainty when machines differ. Aggregate Linux metrics show resource context, while application spans show where a request waited; neither alone provides the complete causal path. Compare a healthy request population with the affected one.
A pool is a queue and a concurrency limit
Connection-pool wait is distinct from database execution time. Increasing the pool can reduce local waiting while overwhelming the database with more concurrent work. A small pool can also underuse a healthy dependency, so the correct answer requires matched measurements rather than a universal size. Inspect queue age, active connections, timeout count, transaction lifetime, and downstream saturation. Connections retained during unrelated application work can exhaust the pool without expensive queries. Separate offered request rate from completed transactions and preserve the same isolation and durability requirements in any comparison.
Use the database’s own wait vocabulary
A database can wait on locks, log durability, storage reads, buffer misses, CPU, or client consumption. Low host CPU does not exclude a serialized transaction or a blocked lock owner. Query shape and input cardinality can change demand even when the SQL text looks unchanged. Use engine-supported statistics and plans with appropriate permissions, and know which diagnostic statements execute a query. PostgreSQL EXPLAIN without ANALYZE can inspect a plan, while ANALYZE changes the observation contract by running the statement. Never run real production queries or engine-specific tuning from this lesson.
EXPLAIN SELECT report_id FROM lab_reports WHERE state = 'ready';
This SQL is for a prepared PostgreSQL lab and intentionally omits ANALYZE. Planning still uses server resources and requires authorization; no plan output or performance conclusion is invented.
Correlate evidence at matching intervals
If database wait time rises with block latency and I/O pressure, inspect the storage path and request mix. If lock waits rise while device metrics stay healthy, find the transaction holding the lock. If application CPU rises during result serialization, a database index change may not address the bottleneck. Scope host observations to the responsible process and its cgroup, recognizing that a shared database host includes other tenants. Preserve errors and retries because they can amplify resource demand after an initial failure. A correlation suggests a mechanism to test, not proof of causation.
pidstat -u -r -d -p 4242 1 5
cat /proc/pressure/cpu
cat /proc/pressure/io
The PID is synthetic and the commands are non-runnable. Relate interval metrics to application timestamps and actual engine waits; do not equate process I/O bytes with every storage operation.
Test a change at the layer that explains the delay
A bounded query result, corrected index, shorter transaction, smaller batch, or admission change can reduce demand more effectively than global kernel settings. Test on a representative disposable dataset and preserve query semantics. A fast empty-table plan does not prove performance at real cardinality, and a warm buffer pool does not represent every recovery condition. Compare end-to-end latency, successful transaction rate, lock behavior, errors, and resource use. Record plan and application versions so a later statistics refresh or deployment does not silently invalidate the conclusion.
Common errors and fixes
Increasing every pool after timeouts
- Why it happens
- Downstream capacity and transaction lifetime were ignored.
- How to fix it
- Separate pool wait from engine wait and test bounded demand.
- Prevent it next time
- Model pools as admission boundaries.
Running ANALYZE unintentionally
- Why it happens
- A diagnostic plan request was assumed not to execute work.
- How to fix it
- Use the engine’s documented non-executing inspection mode.
- Prevent it next time
- Review statement side effects before diagnostics.
Blaming Linux for a lock bottleneck
- Why it happens
- Aggregate CPU and I/O replaced transaction evidence.
- How to fix it
- Find the owner and the application transaction path.
- Prevent it next time
- Collect stage and engine waits with host metrics.
Check your understanding
1. Pool waiting grows after query duration doubles. Why is a bigger pool not an automatic fix?
Show worked solution
More connections can feed additional work into the already slower stage and worsen queueing. Identify why query service time grew, inspect dependency capacity, and compare a controlled admission or query correction using the same transaction semantics.
2. Host CPU is low but one transaction blocks many others. What should the investigation preserve?
Show worked solution
Capture transaction age, lock relationships, the owner’s current wait, request correlation, and relevant storage or application evidence. Low CPU does not make a lock holder harmless. Preserve the first blocking mechanism before any authorized recovery changes its state.
Key takeaways
- Follow the request critical path across pools and engine waits.
- Reduce justified demand while preserving transaction correctness.