15.3 Performance debt
บทนี้รวบรวม debt ด้าน performance — ปัญหาที่ทำให้ระบบช้าและขยายตัวได้ยาก
หมายเหตุ: PERF-7 (phpMyAdmin เปิดสาธารณะ) อยู่ในบท 14.1 เพราะเป็น security Critical
รายการทั้งหมด
PERF-1 · execSync ใน request handler
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/routes.js:73,75,115,117,210,260,312 — ใช้ execSync สำหรับ qpdf |
| ปัญหา | execSync บล็อก event loop ของ Node — การสร้าง PDF หนึ่งไฟล์ใช้เวลา 2-5 วินาที ในช่วงนั้น request อื่น ๆ บน replica เดียวกันต้องรอ กับ 4 replica ความสามารถสูงสุด = 4 PDF พร้อมกัน |
| Impact | ตอนออกเงินเดือน (มีคนดาวน์โหลด payslip พร้อมกัน) ระบบช้ามาก ผู้ใช้รายอื่น (ที่ไม่ได้ดาวน์โหลด PDF) ก็ช้าด้วย |
| Mitigation ระยะสั้น | เปลี่ยนเป็น async worker queue — request สร้าง job แล้ว return ทันที ผู้ใช้รอ download link (เช่น 30 วินาที) |
| แนวทางระยะยาว | ย้าย PDF generation ไป service แยกที่ใช้ chromedp (Go) หรือ worker ที่ไม่ใช้ event loop |
PERF-2 · Apollo fetchPolicy 'no-cache' ทุก query
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-web/src/config/apollo.js:173-181 — fetchPolicy: 'no-cache' กำหนดทั้ง watch + query |
| ปัญหา | normalized cache ของ Apollo ถูกปิด — ทุกการนำทางใหม่เรียก network ทั้งหมด เป็นแหล่งเดียวที่ทำให้ frontend ช้าที่สุด |
| Impact | UX ช้า โหลดซ้ำข้อมูลที่เปลี่ยนน้อย (เช่น profile, organization list) กระทบโดยเฉพาะผู้ใช้มือถือบนเครือข่ายช้า |
| Mitigation ระยะสั้น | เปลี่ยน default เป็น cache-and-network หรือ stale-while-revalidate สำหรับ query ที่ไม่ sensitive |
| แนวทางระยะยาว | ใช้ @nuxtjs/apollo ที่มี cache strategy ที่ดีกว่า หรือ useFetch ของ Nuxt 3 |
PERF-3 · Session lookup ด้วย JSON LIKE
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/repositories/session/session.js:31-33 — userSession: { [Op.like]: \%${jwtid}%` }` |
| ปัญหา | ทุก authenticated request ต้องค้นหา session โดยการ LIKE บน JSON blob ในคอลัมน์ userSession — เป็น full table scan ไม่มี index |
| Impact | เมื่อตาราง Session ใหญ่ขึ้น (ผู้ใช้ login หลายครั้ง) การ authenticate ช้าขึ้นเรื่อย ๆ |
| Mitigation ระยะสั้น | เพิ่มคอลัมน์ jwtId (indexed) แล้ว lookup ด้วยคอลัมน์นี้แทน |
| แนวทางระยะยาว | เก็บ sessionId ใน JWT แล้ว lookup ด้วย primary key |
PERF-4 · N+1 RBAC query ทุก field
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/directives/auth.js:43-44 + session.js:127-153, 50-69 |
| ปัญหา | directive @auth ทำ 3+ database round-trips ต่อการเรียกแต่ละ field ที่ protected — getCurrentRoleUser query Role แล้ว User แล้ว filter ใน JS, checkMenuAuth query User แล้ว RoleAccess แยกกัน |
| Impact | GraphQL query ที่มี field ที่ protected หลาย field จะทำ database query จำนวนมากผิดปกติ |
| Mitigation ระยะสั้น | cache permission set ของ user ใน Redis (TTL = session TTL) |
| แนวทางระยะยาว | โหลด permission set ครั้งเดียวต่อ request ใส่ใน context |
PERF-5 · Approval-chain fetch ข้าม service แบบ synchronous
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/controllers/hrmi/hrmi.js:65,122 — เรียก hrmiGetApproverCD |
| ปัญหา | ทุกการสร้าง leave/withdraw ดึง approval chain จาก cu-central-api แบบ synchronous — ถ้า cu-central-api ช้า การสร้างทั้งหมด hang |
| Impact | ประสบการณ์ผู้ใช้แย่ตอน cu-central-api มีปัญหา — แม้จะไม่ได้ใช้ข้อมูล approval chain ในทันที |
| Mitigation ระยะสั้น | เพิ่ม timeout + fallback (ใช้ cache หรือ default chain) |
| แนวทางระยะยาว | pre-cache approval ladder ต่อ org, invalidate เมื่อ MasterApprover เปลี่ยน |
PERF-6 · God controller 3,179 บรรทัด
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/controllers/withdraw/hospital.js — 3,179 LOC |
| ปัญหา | controller เดียวผสมทุกอย่าง — creation, approval, bank export, reporting, status transition, attachment การแก้ของเล็ก ๆ มีความเสี่ยงกระทบส่วนอื่น |
| Impact | การ maintainability ต่ำ การเพิ่มเติม feature ใหม่ทำได้ยาก การ review ใช้เวลานาน |
| Mitigation ระยะสั้น | แยก method ที่เกี่ยวข้องกันเป็น module ย่อย (ไม่เปลี่ยน behavior) |
| แนวทางระยะยาว | แยกเป็น domain aggregate — Claim (lifecycle), ApproverChain, BankExportBatch, Report แต่ละตัว < 400 LOC |
PERF-8 · network_mode: host ใน docker-compose
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/docker-compose.yml:14-34 |
| ปัญหา | nginx container ใช้ network_mode: host — ไม่มี port isolation, container เห็น network ของ host ทั้งหมด |
| Impact | ความเสี่ยงด้าน security (container ที่ถูก compromise สามารถเข้าถึง service อื่นบน host) และความยากในการจำกัด network access |
| Mitigation ระยะสั้น | เปลี่ยนเป็น bridge network + explicit port mapping |
| แนวทางระยะยาว | ทุก container ใช้ bridge network และกำหนด network policy |
สรุป
| ID | รายการ | Severity | Effort ระยะสั้น |
|---|---|---|---|
| PERF-1 | execSync บล็อก event loop | Critical | 3-5 วัน (worker queue) |
| PERF-2 | no-cache ทุก query | High | 1-2 วัน |
| PERF-3 | Session LIKE JSON | High | 1-2 วัน (เพิ่มคอลัมน์) |
| PERF-4 | N+1 RBAC query | High | 2-3 วัน (Redis cache) |
| PERF-5 | sync approval fetch | High | 2-3 วัน (timeout + cache) |
| PERF-6 | God controller 3,179 LOC | High | 1-2 สัปดาห์ (split) |
| PERF-8 | network_mode: host | High | 1 วัน |
บทถัดไปจะไปยัง debt ด้านความถูกต้องของข้อมูล ซึ่งเป็นหมวดที่กระทบความน่าเชื่อถือของระบบโดยตรง