Follow-ups to #5 (which removed the post-mutation full reload and the users.list() N+1). Root cause of perceived slowness: chatty sequential queries × network RTT to the remote DB (Supabase pooler, Tokyo, ~70–90 ms/query). These are optimizations, not ship-blockers.
1. Collapse INSERT+SELECT in ensureX helpers (POST create)
EmployeeRepository.create() runs ~20 sequential round trips; the ensureTitle/Division/Position/Level/Location/Status helpers each do an INSERT ... ON CONFLICT then a separate SELECT to read the id back.
- Fix: add
RETURNING <id> to the upsert (or INSERT ... ON CONFLICT DO UPDATE ... RETURNING) so each is one round trip.
- Impact: removes ~6 round trips from every employee create (~0.5 s on remote DB).
- Files:
backend/.../employee/EmployeeRepository.java
2. Server-side pagination + push filters down
GET /api/employees returns the whole table (16 LEFT JOINs, no LIMIT); the frontend filters/paginates client-side.
- Fix: add
LIMIT/OFFSET (or keyset) + use the existing filter params server-side; add a pg_trgm GIN index for the ILIKE '%x%' search.
- Impact: keeps list fast and payload small as the dataset grows past a few hundred rows.
- Files:
EmployeeRepository.findEmployees, EmployeeController, EmployeeListPage.jsx, new Flyway index migration.
3. Warm the Hikari pool
minimum-idle: 1 means cold connections pay a full TLS handshake to the remote DB on first use after idle.
- Fix: set
minimum-idle = maximum-pool-size; consider enabling PgJDBC server-side prepared-statement caching (safe on the 5432 session pooler; must be disabled if ever switching to the 6543 transaction pooler).
- Impact: removes cold-start stalls.
- Files:
backend/src/main/resources/application.yml
4. (Optional) single-query employee detail
GET /api/employees/{id} runs 4 sequential queries (detail + assignments + salary + pending count). Low priority — could be one query with LEFT JOIN LATERAL / JSON aggregation if it shows up in profiling.
Acceptance criteria
Follow-ups to #5 (which removed the post-mutation full reload and the
users.list()N+1). Root cause of perceived slowness: chatty sequential queries × network RTT to the remote DB (Supabase pooler, Tokyo, ~70–90 ms/query). These are optimizations, not ship-blockers.1. Collapse INSERT+SELECT in
ensureXhelpers (POST create)EmployeeRepository.create()runs ~20 sequential round trips; theensureTitle/Division/Position/Level/Location/Statushelpers each do anINSERT ... ON CONFLICTthen a separate SELECT to read the id back.RETURNING <id>to the upsert (orINSERT ... ON CONFLICT DO UPDATE ... RETURNING) so each is one round trip.backend/.../employee/EmployeeRepository.java2. Server-side pagination + push filters down
GET /api/employeesreturns the whole table (16 LEFT JOINs, noLIMIT); the frontend filters/paginates client-side.LIMIT/OFFSET(or keyset) + use the existing filter params server-side; add apg_trgmGIN index for theILIKE '%x%'search.EmployeeRepository.findEmployees,EmployeeController,EmployeeListPage.jsx, new Flyway index migration.3. Warm the Hikari pool
minimum-idle: 1means cold connections pay a full TLS handshake to the remote DB on first use after idle.minimum-idle=maximum-pool-size; consider enabling PgJDBC server-side prepared-statement caching (safe on the 5432 session pooler; must be disabled if ever switching to the 6543 transaction pooler).backend/src/main/resources/application.yml4. (Optional) single-query employee detail
GET /api/employees/{id}runs 4 sequential queries (detail + assignments + salary + pending count). Low priority — could be one query withLEFT JOIN LATERAL/ JSON aggregation if it shows up in profiling.Acceptance criteria
/api/employeessupports server-side pagination + filtering; search uses an index.