Start with detection and measurement- Instrument: enable SQL logging (slow query log), use APM (NewRelic, Datadog) and DB stats to find endpoints with high query count / latency.- Measure baseline: record average requests/sec, p95 latency, and "queries per request" for suspect endpoints. Use EXPLAIN for slow statements.Code-level fixes- Eager loading (Hibernate example):java
// Hibernate HQL with fetch join to avoid N+1
String hql = "select o from Order o join fetch o.items where o.id = :id";
Order o = session.createQuery(hql, Order.class).setParameter("id", id).uniqueResult();
- ActiveRecord (Rails) example:ruby
# Instead of Order.all.each { |o| o.items } (N+1)
orders = Order.includes(:items).where(status: 'open')
- Batch fetching / pagination:java
// Hibernate batch fetch size
sessionFactory.getSessionFactoryOptions().setProperty("hibernate.default_batch_fetch_size","50");
- Replace per-row updates with set-based operations:sql
UPDATE users SET status='active' WHERE last_login > '2025-01-01';
Database-level fixes- Add indexes on join/filter columns (use EXPLAIN to justify).- Use composite indexes for multi-column WHERE clauses.- For massive joins/updates, use temporary tables or staging tables and a single JOIN-based UPDATE/INSERT to reduce round trips.- Consider materialized views for expensive aggregations.Regression testing and CI- Add automated tests that assert query counts: e.g., in Rails use ActiveSupport::Notifications to assert <= N queries; in Java use datasource proxy to count statements.- Add performance tests to CI: run representative scenarios and fail on regressions for queries-per-request or p95 latency.- Code review checklist: require scrutiny for lazy access patterns (entity.getChildren() inside loops).- Monitor post-deploy via APM and compare to baseline; roll back if regressions occur.Key trade-offs: eager-loading reduces queries but increases memory and payload size—limit with projection, pagination, or selective fetch. Use metrics-driven changes and iterate.