A UK FinTech processing £2B in monthly transactions was seeing API response times above 2 seconds during peak load. Here is how we diagnosed the root causes and reduced p95 response time to under 180ms.
The Problem: 2-Second API Responses in a Real-Time Finance Product
Our client — a UK-based embedded finance platform processing over £2 billion in monthly transactions — had a critical problem. Their core transaction query API was returning responses in 2,100ms at p95 during London market hours. For a product where traders and portfolio managers expect sub-200ms responses, this was causing active churn from institutional clients.
The engineering team had spent three months attempting optimisations without meaningful results. They brought us in to conduct a systematic performance audit and deliver a solution within 6 weeks.
Phase 1: Profiling and Diagnosis (Week 1–2)
APM Deep Dive
We instrumented the API with Datadog APM and traced 10,000 real production requests through the full execution path. The findings were revealing: 67% of total response time was consumed by database queries. Of those database queries, three specific query patterns accounted for 89% of the database time.
The Root Causes
N+1 query problem: The transaction history endpoint fetched a list of transactions, then for each transaction made an additional query to fetch the associated account details. A request returning 50 transactions generated 51 database queries. With 200ms average query time and no parallelisation, this alone created 10,000ms of sequential database time — and the ORM was "helpfully" hiding this from developers.
Missing composite indexes: The most frequent query — fetch transactions by account ID, date range, and status — was performing a full table scan on a 400 million row transactions table. The existing index on account_id alone was insufficient; the query optimiser chose a table scan over an index range scan given the query's selectivity profile.
Synchronous external calls: Three external API calls (fraud check, FX rate fetch, and compliance verification) were made sequentially within the request handler, adding 600–800ms of external latency that was unavoidable but serial and blocking.
Phase 2: Solutions Implementation (Weeks 2–5)
Solving the N+1 Problem
We rewrote the ORM query to use eager loading with a single JOIN query, fetching transaction and account data in one database round-trip. The query count dropped from 51 to 1 for a 50-transaction request. Immediate latency reduction for this endpoint: 780ms at p95.
Index Strategy Overhaul
We added a composite index on (account_id, created_at DESC, status). The query plan changed from a full table scan to an index range scan. Query time for the primary endpoint dropped from 1,400ms to 45ms. For a 400 million row table, the right index transformed the query from seconds to milliseconds.
CREATE INDEX idx_transactions_account_date_status
ON transactions (account_id, created_at DESC, status)
INCLUDE (amount, currency, reference);
Parallelising External Calls
We refactored the external API calls from sequential to parallel using Promise.all(). The fraud check, FX rate fetch, and compliance verification now run concurrently. Total external API time dropped from 720ms to 250ms (limited by the slowest of the three, not their sum).
Caching Strategy
We introduced Redis caching for FX rates (refreshed every 30 seconds — acceptable staleness for the product) and account metadata (invalidated on account update events). Cache hit rates reached 94% within 48 hours, effectively eliminating the database cost for these lookups in the hot path.
Results
| Metric | Before | After | Change |
|---|---|---|---|
| p95 API Response Time | 2,100ms | 178ms | -92% |
| p50 API Response Time | 890ms | 62ms | -93% |
| Database Queries per Request | 51 | 1–3 | -94% |
| Monthly Infrastructure Cost | £12,400 | £8,200 | -34% |
Key Lessons
Profile before optimising — the team's initial assumptions about where time was spent were wrong in every case. Instrument properly and let data guide the work. The highest-impact changes were not clever algorithms or architectural redesigns — they were fixing fundamental database access patterns that should have been caught in code review. And finally: caching is powerful, but only after you have fixed the underlying queries. Caching a slow query still means cache misses are slow.
Expert insights on AI, software engineering, and digital transformation from the TechGeneses team of engineers and strategists.