Skip to content

13.11 Cron scheduler & operational gaps + scorecard

บทสุดท้ายของ Volume 13 สำรวจ cron scheduler ที่ใช้ในระบบ, ปัญหาเชิงปฏิบัติการที่เหลือ, และ scorecard สรุปของทั้ง volume

ภาพรวม scheduler

ServiceLibraryที่อยู่กลไก HA
vtrc-apinode-scheduleapi/src/crons.jsเฉพาะ updateSuccessFundChangeRateByJob ใช้ APP_NO===1; job อื่นรันทุก replica
cu-central-apinode-schedulemain-api/src/cron.jsAPP_NO gate ทั้งไฟล์ (ทุก job ใน if (+APP_NO === 1))
centralize-apiไม่มี
vtrc-webไม่มี (frontend)
vtrc-rc-backofficeไม่มี (frontend)

vtrc-api — crons.js

vtrc-api/api/src/crons.js กำหนด cron job ผ่าน node-schedule:

ลักษณะสำคัญ:

  1. node-schedule เป็น in-process scheduler — รันใน Node process เดียวกับ HTTP server ไม่ใช่ worker แยก
  2. APP_NO gate ใช้เฉพาะ fund rate job — บล็อก if (+APP_NO === 1) ครอบคลุมแค่ updateSuccessFundChangeRateByJob (crons.js:14-18) — ไม่ใช่ทุก job
  3. updateDelegatesExpire และ statLog ลงทะเบียนนอกเงื่อนไข APP_NO — รันบน ทุก replica ที่ start (crons.js:20-27)
  4. ไม่มี retry — ถ้า job fail จะไม่รันซ้ำจนกว่าจะถึง schedule ถัดไป
  5. ไม่มี visibility — ไม่มี API ที่บอกว่า job ใดรันล่าสุดเมื่อใด, สำเร็จหรือไม่

job ที่ใช้งานใน vtrc-api:

JobScheduleAPP_NO gate?หน้าที่
updateSuccessFundChangeRateByJob* 1 * * * * (วินาทีที่ 1 ของทุกนาที)ใช่ — เฉพาะ APP_NO=1flip PENDING → SUCCESS ของ Fund rate change
updateDelegatesExpire30 3 * * * (03:30 ทุกวัน)ไม่ — ทุก replicaexpire delegate ที่หมดอายุ
statLog30 0 * * * (00:30 ทุกวัน)ไม่ — ทุก replicaสถิติรายวัน
auto-cancel leave(disabled)ยกเลิก leave ค้าง — ปิดไว้

cu-central-api — cron.js

cu-central-api/main-api/src/cron.js ห่อ ทุก job ใน if (+APP_NO === 1) — ต่างจาก vtrc-api ที่ gate เฉพาะ fund rate

ปัญหาของ APP_NO gate (ขอบเขตที่ถูกต้อง)

APP_NO gate เป็นวิธีป้องกันการรัน cron ซ้ำในหลาย replica แต่มีข้อจำกัด:

  1. SPOF เฉพาะ job ที่ถูก gate — ถ้า replica APP_NO=1 ตาย, เฉพาะ updateSuccessFundChangeRateByJob (vtrc) และ cron ทั้งชุดของ cu-central จะไม่รัน — อย่าสรุปว่า updateDelegatesExpire / statLog หยุดด้วย เพราะ job เหล่านั้นรันทุก replica (และอาจรันซ้ำซ้อนกัน)

  2. ไม่ใช่ distributed lock ที่แท้จริง — ไม่มี atomicity ในการ acquire ถ้า APP_NO=1 start พร้อมกัน 2 instance (edge case ระหว่าง deploy) อาจรันซ้ำได้

  3. job ที่ไม่ gate อาจรันซ้ำupdateDelegatesExpire / statLog บนทุก replica อาจเขียนซ้ำหรือแข่งกันถ้าไม่มี idempotency ในตัว handler

ทางเลือกที่ดีกว่าคือ Redis SET NX กับ TTL — เป็น distributed lock ที่ทำงานได้ถูกต้องแม้มีหลาย replica

ปัญหาเชิงปฏิบัติการ — สรุปทั้ง volume

ตารางสรุปปัญหาที่พบใน Volume 13 เรียงตามความสำคัญ:

#ปัญหาSeverityที่มา
1Node runtime EOL (12, 14, 16)CriticalDockerfiles ทุกตัว
2JWT_SECRET = 'secret' defaultCriticalvtrc-api/api/src/config.js:12
3GCP SA key commit ใน repoCriticalvtrc-api/vtrc-gcp-sa-key.json
4phpMyAdmin expose port 8093Criticalvtrc-api/docker-compose.yml:35-46
5NODE_ENV=development ใน compose ของ cu-central-apiHighcu-central-api/docker-compose.yml:45,64,81
6ไม่มี health check endpointHighทุก service
7ไม่มี structured logger / log aggregationHighทุก service
8ไม่มี CI/CD สำหรับ 3 reposHighcu-central-api, centralize-api, backoffice
9ไม่มี migration — syncModelToTableHighvtrc-api, cu-central-api
10APP_NO gate (fund rate / cu-central) ไม่ใช่ distributed lockHighcrons.js / cron.js
11network_mode: host ของ nginxHighvtrc-api, cu-central-api docker-compose
12express.static('/', storage) ไม่มี authHighvtrc-api/api/src/routes.js:988
13ไม่มี lockfile ของ centralize-apiHighcentralize-api/
14deploy ด้วยมือทุกครั้งMedpipeline push image จบ, deploy manual
15PII ใน URL pathMedroutes.js:51
16setTimeout(deletePDF, 5000) raceMedroutes.js:79
17ไม่มี resource limit ใน docker-composeMedทุก compose
18image: latest ทุก serviceMedทุก compose
19path src/storage/prs/ vs storage/prs/Medหลายไฟล์
20trustServerCertificate: true (MSSQL)Medcentralize-api app.module.ts
21Redis ไม่มี replicaMeddocker-compose
22TRANSFER_PROTOTAL typoLow.env.example:26

Scorecard สรุปของ Volume 13

Legend:

  • 🟢 = ดี, ไม่มีปัญหาเร่งด่วน
  • 🟡 = มีปัญหาปานกลาง, ควรปรับปรุง
  • 🔴 = มีปัญหาวิกฤต, ต้องแก้ด่วน
มิติvtrc-apivtrc-webvtrc-rc-backofficecentralize-apicu-central-api
Runtime ทันสมัย🔴 Node 12🟡 Node 14 build🔴 Node 16 build🔴 Node 16🔴 Node 12
Container best practice🔴 ไม่มี multi-stage🟢 multi-stage🟢 multi-stage🟢 multi-stage🔴 ไม่มี multi-stage
Dockerfile security🔴 root, no healthcheck🟡 root, no healthcheck🔴 root, no healthcheck🔴 root, no healthcheck🔴 root, no healthcheck
docker-compose discipline🟡 nginx host; api bridge+ports🟢 bridge🟡 bridge, no limit🟡 external network🟡 nginx host; api bridge; NODE_ENV dev
nginx config quality🟡 ปนกับ secret🟢 เรียบง่าย🟢 เรียบง่าย🔴 upstream ชื่อผิด
env config safety🔴 JWT default 'secret'🟢 build-time🟢 build-time🟡 strictNullChecks off🔴 NODE_ENV dev default
DB migration🔴 sync ทุกครั้ง🟡 TypeORM sync🔴 Sequelize sync
CI/CD🟡 มี pipeline แต่ deploy manual🟡 เช่นเดียวกัน🔴 ไม่มี🔴 ไม่มี🔴 ไม่มี
Logging🔴 console.log + wlog ผสม🟡 CRA console🟡 CRA console🟢 I3GatewayLogger🔴 PM2 log ที่ไม่ได้ใช้
Monitoring🔴 ไม่มี🔴 ไม่มี🔴 ไม่มี🔴 ไม่มี🔴 ไม่มี
Health check🔴 ไม่มี🔴 ไม่มี🔴 ไม่มี
Scheduler HA🟡 APP_NO เฉพาะ fund rate; delegate/stat รันทุก replica🔴 APP_NO gate ทั้งชุด

ข้อแนะนำเรียงตาม priority

Phase 1 — แก้ด่วน (สัปดาห์ที่ 1-2)

การกระทำผลกระทบ
ตั้ง JWT_SECRET ที่เป็นความลับจริง, service crash ถ้าว่างกัน auth bypass
ลบ phpMyAdmin ออกจาก compose ของ prod หรือจำกัด IPลด attack surface
เปลี่ยน NODE_ENV=production ใน compose ของ cu-central-apiIS_PRODUCTION ทำงานถูก
Rotate GCP SA key + ย้ายออกจาก repoลดช่องทางดึงข้อมูล
เพิ่ม /health endpoint ในทุก backend serviceเปิดทาง monitoring

Phase 2 — ปรับปรุงระยะกลาง (เดือนที่ 1-3)

การกระทำผลกระทบ
ยก Node runtime เป็น 20 LTSความปลอดภัย
เพิ่ม CI/CD สำหรับ 3 repos ที่ขาดdeploy สม่ำเสมอ
commit lockfile ของ centralize-apibuild ทำซ้ำได้ผลลัพธ์เดิม
เปลี่ยน syncModelToTable เป็น migrationschema change reviewable
เพิ่ม structured logger (pino หรือ winston)debug ง่ายขึ้น
เพิ่ม Sentry สำหรับ error trackingรู้ปัญหาก่อนผู้ใช้รายงาน

Phase 3 — ยกระดับระยะยาว (เดือนที่ 3-12)

การกระทำผลกระทบ
ย้ายจาก docker-compose ไป Kubernetesscale ได้, self-healing
เปลี่ยน APP_NO gate เป็น Redis distributed lock (fund rate + cu-central; พิจารณา idempotency ของ delegate/stat ที่รันทุก replica)cron job HA
เพิ่ม Prometheus + Grafanametric monitoring
เพิ่ม log aggregation (Loki หรือ ELK)debug ข้าม services
ใช้ secret manager (Vault, GCP Secret Manager)secret rotation
เปลี่ยน file storage ไป GCS / S3durability, scalability
เพิ่ม distributed tracing (OpenTelemetry)visibility ข้าม services

สรุป Volume 13

Volume นี้เจาะลึกว่าระบบ VTRC ทำงานบน infrastructure ใด — ผสมระหว่าง GCP container registry และ on-prem database/LDAP, orchestrate ผ่าน docker-compose ไม่ใช่ Kubernetes, deploy ผ่าน Bitbucket Pipelines เฉพาะ 2 จาก 5 repos

ปัญหาเชิงโครงสร้างหลักคือระบบเก็บไว้ในสภาพ "ตอบโต้ได้" (works as-is) แต่ไม่ได้ออกแบบมาให้ scale, monitor, หรือ recover จาก failure โดยอัตโนมัติ — ทุกอย่างต้องดูแลด้วยมือของทีมปฏิบัติการ

ความเสี่ยงวิกฤตสูงสุดคือ Node runtime ที่ EOL มานาน, JWT secret ที่เป็นค่า default, และ GCP key ที่ commit ใน repo — ทั้งสามข้อแก้ไขได้ในเวลาไม่กี่วัน แต่ต้องการการประสานงานกับทีมปฏิบัติการและผู้ดูแลระบบ

Volume ถัดไปจะเจาะลึกเรื่องอื่น ๆ ที่ยังไม่ครอบคลุม (เช่น Operations Runbook, Disaster Recovery, Modernization Plan) ตามลำดับความสำคัญที่ผู้ใช้กำหนด