Gavalas Engineering

Your Postgres got slow somewhere north of ten million rows.

I find out why, then I fix it.

Usually it is the query planner. Sometimes it is the Go service in front of it.

Taking one engagement at a time. Two to six weeks, fixed scope, fixed price.

EXPLAIN (ANALYZE)SELECT id, payload FROM events WHERE tenant_id = 4471;Seq Scan on events1.9 s3.7 s5.6 s7.4 s8.2 sRows Removed by Filter: 51,996,816Index Scan using events_tenant_created_idx340 msBuffers: shared hit=946 read=2588p99 latency, checkout query1.2s600ms009:0011:0013:00index deployed

What this usually looks like

A query that took 40ms last quarter takes 8 seconds now, and nobody changed it.

EXPLAIN says it uses the index. EXPLAIN ANALYZE says it reads the whole table.

RDS CPU sits at 90% and traffic is flat.

The connection pool exhausts under load and every request queues behind it.

These are planner problems, pooling problems, and access-pattern problems. They look like capacity problems, which is why the usual first response is a bigger instance. That buys a quarter and makes the next diagnosis harder.

Case studies

Why Postgres stops using your index at 52M rows

8.2s340ms

The index existed and the query used its leading column. The planner's row estimate was off by four orders of magnitude, so it costed the index scan higher than reading all 52 million rows.

Raising the statistics target on tenant_id and re-running ANALYZE brought the estimate back in line. No new index, no schema change, no bigger instance.

Reproducible on PostgreSQL 16.4, db.m6g.large, from the seed script in the writeup. Synthetic data, not a client engagement.

Before. The planner estimates one row, then reads all 52 million.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, payload FROM events
WHERE tenant_id = 4471
  AND created_at >= now() - interval '7 days';

Seq Scan on events  (cost=0.00..1984322.00 rows=1 width=126)
                    (actual time=1.204..8188.417 rows=3184 loops=1)
  Filter: ((tenant_id = 4471) AND (created_at >= now() - '7 days'::interval))
  Rows Removed by Filter: 51996816
  Buffers: shared hit=912 read=1443410
Planning Time: 0.214 ms
Execution Time: 8188.902 ms
After. Corrected statistics. Same query, same index, no schema change.
Index Scan using events_tenant_created_idx on events
                    (cost=0.56..3612.44 rows=3105 width=126)
                    (actual time=0.061..338.902 rows=3184 loops=1)
  Index Cond: ((tenant_id = 4471) AND (created_at >= now() - '7 days'::interval))
  Buffers: shared hit=946 read=2588
Planning Time: 0.188 ms
Execution Time: 339.612 ms

A pgx pool that exhausted at 240 concurrent requests

2.4s96ms

Every handler took a connection for the whole request, including the 300ms it spent waiting on an upstream HTTP call. Under load the pool drained and p99 became queue time, not query time.

Scoping the connection to the query rather than the request, and moving the upstream call outside the transaction, held p99 flat to the pool ceiling.

A GraphQL resolver issuing 1,412 queries per page

1,412 queries4 queries

A nested resolver ran one lookup per parent row, so a 200-item list turned into a fanout the database saw as unrelated traffic. The slow query log showed nothing wrong: every individual query was fast.

Batching the lookups behind a per-request dataloader collapsed the fanout to four statements and took the endpoint off the incident list.

What an engagement is

The diagnostic

Every engagement opens with a paid diagnostic against read access to a replica and topg_stat_statements. You get a written findings document: the plans, what the planner is getting wrong, and the specific change that fixes each one.

Query and schema work

Index design, statistics targets, partitioning, and rewriting the handful of queries that account for most of your database time. The deliverable is a migration you can review, plus the before and after plans that justify it.

Go service work

Connection pooling, transaction scope, request fanout, and the concurrency patterns that turn a healthy database into a queue. Usually this is where the latency actually lives once the queries are fixed.

How it runs

Scope
Agreed in writing before anything starts, derived from the diagnostic rather than guessed at. It does not move without both of us saying so.
Duration
Two to six weeks. Longer than that is a hiring problem, not a consulting one.
Price
Fixed scope, fixed price, paid in two milestones. No hourly billing, no retainer.

Who does the work

I am Michael Gavalas, a backend performance engineer. Gavalas Engineering is me, and the person who reads your query plans is the person who writes the migration.

What I do is narrow on purpose: backend performance consulting, PostgreSQL query optimization and Go service reliability. Ten years on backends where the database was the constraint, most of it in Go and Postgres, most of it under load that arrived faster than the schema was designed for.

The case studies above are reproducible from published seed scripts rather than anonymised client logos, because a benchmark you can run yourself is a stronger claim than one you have to take on trust.

Questions buyers ask

Where are you based, and how much US overlap do I get?

I work from Greece. That is four hours of daily overlap with US Eastern, every working day, roughly 09:00 to 13:00 Eastern. Standups, incident calls, and pairing all happen inside that window, and written handoff covers the rest.

Do you need access to our production database?

No. Read access to a replica and to pg_stat_statements covers the diagnostic. Write access is only needed if you want me to apply the fix rather than hand it over.

Can you do the work without any database access at all?

Partly. With EXPLAIN (ANALYZE, BUFFERS) output, the schema, and pg_stat_statements exports I can identify most planner and indexing problems. Access shortens the loop considerably, but its absence is not a blocker.

What happens if the diagnostic finds nothing?

You get the written findings either way, including the evidence that your database is not the bottleneck and where the time is actually going. That is a useful result, and it is the reason the diagnostic is priced separately from the fix.

How do contracts, NDAs, and invoicing work?

I sign your NDA and your contract if you have them, or supply a short one if you do not. Invoicing is in USD or EUR against a fixed price, in two milestones, net 14.

What does an engagement cost?

The diagnostic is a fixed fee agreed before it starts. Implementation work is quoted as a fixed price against a written scope once the diagnostic tells us what the work actually is. There is no hourly billing.

How soon can you start?

I take one engagement at a time, so the honest answer depends on what is in flight. Send the slow query and I will tell you the real date rather than a hopeful one.

Get in touch

[email protected]

Send the slow query and the output of EXPLAIN (ANALYZE, BUFFERS) if you have it. I read every one.

Based in Greece. Four hours of daily overlap with US Eastern, every working day.