The optimiser chose a plan based on an estimate of 200 rows where the real answer was 8,000, and only the actual execution shows the difference.
EXPLAIN ANALYZE
SELECT o.* FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE c.segment = 'enterprise' AND o.created_at > '2023-01-01';
-> Nested loop inner join (cost=412 rows=200)
(actual time=0.4..2841 rows=8104 loops=1)
-> Index scan on c using idx_segment (rows=200)
(actual rows=8104 loops=1)
The gap between rows= and actual rows= is the whole diagnostic, and a factor of forty is enough to make a nested loop the wrong choice. The cause here was stale statistics on a column whose distribution had changed after a data import — ANALYZE TABLE fixed it in seconds, and the histogram on that column stopped it recurring.