Skip to content

13.7 Database & Redis topology

บทนี้สำรวจโครงสร้าง database และ cache ที่ใช้ในระบบ รวมถึง connection pool, การกระจาย schema, และจุดเชื่อมระหว่าง services

ภาพรวม database

┌──────────────────────────────────────────────────────────────────────┐
│  MariaDB cluster (on-prem)                                           │
│                                                                      │
│  ├── schema "vtrc"            ──── vtrc-api (db)                    │
│  │   └── ตารางหลัก: User, Session, Leave, Withdraw, Study, ...     │
│  │                                                                  │
│  └── schema "vtrc-centralize" ──── vtrc-api (db2)                   │
│      └── ตารางหลัก: Profile, Organization (mirror ของ cu-central) │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│  MSSQL — HRMI (on-prem)                                              │
│                                                                      │
│  ├── cu-central-api  ──── Sequelize + tedious                        │
│  │   └── ข้อมูล HR ที่แท้จริง — เงินเดือน, ตำแหน่ง, วันลา              │
│  │                                                                  │
│  └── centralize-api  ──── TypeORM + mssql                            │
│      └── มุมมอง HR ผ่าน facade (read-only ส่วนใหญ่)                  │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│  Redis                                                               │
│                                                                      │
│  ├── vtrc-api  ──── redis 6379 (container ใน compose ของ vtrc-api)  │
│  └── cu-central-api ── redis 6379 (container แยกใน compose ของ cu)  │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

vtrc-api — 2 connections ของ MariaDB

vtrc-api/api/src/connector.js:8-50:

javascript
const db = new Sequelize(DB_NAME, DB_USERNAME, DB_PASSWORD, {
  host: DB_HOST,
  port: DB_PORT,
  dialect: DB_SCHEMA,
  ...
  pool: { max: 100, min: 0, acquire: 60000, idle: 10000 }
})

const db2 = new Sequelize(DB2_NAME, DB2_USERNAME, DB2_PASSWORD, {
  host: DB2_HOST,
  ...
  pool: { max: 100, min: 0, acquire: 60000, idle: 10000 }
})

จุดสังเกต:

  1. สอง connection ไปยัง MariaDB เดียวกัน — ทั้ง db (schema vtrc) และ db2 (schema vtrc-centralize) อ้าง host เดียวกัน (DB_HOST = DB2_HOST = vtrc-database ใน .env.example:15,22) ต่างกันเพียงชื่อ schema

  2. connection pool 100max: 100 ต่อ replica หมายความว่า 4 replica รวมกันเปิด connection ได้สูงสุด 400 — ต้องตรวจสอบว่า MariaDB รองรับขนาดนั้น

  3. ไม่มีการ share connection ระหว่าง replica — แต่ละ replica มี pool ของตัวเอง ทำให้ภาระรวมต่อ database สูง

  4. charset: 'utf8mb4' — รองรับภาษาไทยและ emoji แบบ 4-byte UTF-8

  5. useUTC: false — ไม่ใช้ UTC ทำให้ timestamp เก็บเป็นเวลาท้องถิ่น (Asia/Bangkok) โดยตรง — ทำให้การย้ายข้าม timezone ยากขึ้น

vtrc-api — connection lifecycle

vtrc-api/api/src/index.js:101-120:

javascript
db
  .authenticate()
  .then(async () => {
    syncModelToTable()
    console.log('Connection has been established successfully.')
  })
  .catch(err => {
    console.error('Unable to connect to the database:', err)
  })

db2
  .authenticate()
  .then(async () => {
    createDatabases(db2)
    console.log('Connection #2 has been established successfully.')
  })
  .catch(err => {
    console.error('Unable to connect to the database:', err)
  })

จุดสังเกต:

  • .catch ไม่ได้ crash process — ถ้า db เชื่อมต่อไม่ได้ service ยังรันต่อแต่จะ fail ทุก query ที่เข้ามา
  • syncModelToTable() รันหลังเชื่อมต่อสำเร็จ — อัปเดต schema อัตโนมัติตามโมเดลใน code (รายละเอียด Volume 8 บทที่ 4)

cu-central-api — MSSQL + multi-tenant sharding

cu-central-api เชื่อมต่อ MSSQL HRMI ผ่าน tedious driver และมีรูปแบบ SourceDB sharding — แต่ละ "source database" เป็น tenant ของหน่วยงานย่อยในกาชาด (รายละเอียด Volume 4 บทที่ 5)

sourceDB ปกติมีหลายค่า เช่น:

  • RC — กาชาดหลัก
  • HOS — โรงพยาบาล
  • และอื่น ๆ ตามที่กำหนด

การ query จะสลับ connection ตาม sourceDb ที่ระบุใน JWT หรือ parameter — ทำให้ต้องระวังเรื่อง permission scoping

centralize-api — TypeORM + 2 MSSQL connections

centralize-api/src/app.module.ts กำหนด TypeORM configuration สองชุด:

  • Connection 1 — MSSQL HRMI หลัก
  • Connection 2 — MSSQL HRMI สำรอง (รายละเอียด Volume 5 บทที่ 2)

TypeORM config มี flag ที่น่ากังวล:

typescript
extra: {
  trustServerCertificate: true,
}

ปิดการตรวจสอบใบรับรอง TLS ของ MSSQL — ทำให้การเชื่อมต่อสามารถถูก intercept ได้ถ้าอยู่บน network ที่ไม่น่าเชื่อถือ (รายละเอียด Volume 9 บทที่ 1)

Redis topology

ServiceRedis containerPortUse case
vtrc-apivtrc-redis-db (docker-compose.yml:3-13)6379cache ผลลัพธ์บาง query เป็นหลัก — ไม่ใช่ที่เก็บ OTP ของ registration
cu-central-apiredis-db (docker-compose.yml:3-13)6379session (sess:{userUid}_{sessionId}), profile/org cache, rate limit

จุดสังเกต:

  1. Redis สอง instance แยกกัน — vtrc-api และ cu-central-api แต่ละตัวมี container Redis ของตัวเอง ไม่ share
  2. Redis config มาจาก volume./redis-db/conf/redis.conf mount เข้า container ทำให้การตั้งค่า (เช่น maxmemory, persistence) อยู่ในไฟล์ที่ไม่ได้ commit
  3. Persistence./redis-db/data mount สำหรับ AOF/RDB file แต่ไม่เห็น config ที่ระบุว่าใช้ mode ใด
  4. ไม่มี Redis cluster — Redis แต่ละตัวรัน single instance ไม่มี replica หรือ sentinel

OTP — เก็บที่ MariaDB ของ cu-central (ไม่ใช่ vtrc Redis)

Registration / forget-password OTP ไม่ได้อยู่ใน vtrc Redis:

  1. vtrc-api proxy mutation requestOtpCD ไปยัง cu-central GraphQL (vtrc-api/api/src/centralize/controllers/verification.js)
  2. cu-central-api เขียนแถวลงตาราง otp ผ่าน Models.Otp (main-api/src/lib/otp/sendOtp.model.js, tableName: 'otp') — MariaDB ของ cu-central
  3. TTL เริ่มต้น 3 นาที จาก SMS_OTP_EXPIRE_MINUTES = 3 (main-api/src/config.js)

อย่าใช้ KEYS otp:* บน vtrc-redis เพื่อ debug OTP — key pattern นั้นไม่ใช่ที่เก็บ OTP ของระบบ

การใช้ Redis ใน vtrc-api

vtrc-api/api/src/lib/redis.js เชื่อมต่อ Redis ผ่าน redis v3 (callback-style) — การใช้งานหลักที่ยืนยันจากโค้ด:

  • cache ผลลัพธ์บาง query ผ่าน redis.controller.js (setCacheData / getCacheData)
  • ไม่มี OTP store / session store หลักใน vtrc Redis — OTP อยู่ MariaDB ของ cu-central; session หลักของ vtrc อยู่ตาราง Sessions (MariaDB)

vtrc Redis จึงถือว่า เกือบ vestigial สำหรับ cache — crash ของ vtrc Redis ไม่ทำให้ OTP หาย (แต่ cache miss อาจเพิ่มภาระ DB)

การใช้ Redis ใน cu-central-api

  • session: key รูปแบบ sess:{userUid}_{sessionId} (main-api/src/lib/session/session.js + prefix sess จาก redis.controller.js)
  • cache ของ profile / org unit
  • ไม่ใช่ ที่เก็บ OTP — OTP อยู่ตาราง Models.Otp

connection pool — ขนาดและความเสี่ยง

ตารางสรุป connection pool ของแต่ละ service:

ServiceDatabaseMax poolจำนวน replicaรวม connection สูงสุด
vtrc-api (db)MariaDB vtrc1004400
vtrc-api (db2)MariaDB vtrc-centralize1004400
cu-central-apiMSSQL HRMI(ตาม default)3~ 30
centralize-apiMSSQL HRMI(ตาม default)1~ 10

รวม connection ที่อาจเปิดไปยัง MariaDB ได้สูงสุด ~ 800 — ถ้า MariaDB ตั้ง max_connections ต่ำกว่านี้จะเกิด "Too many connections" error ในช่วงที่มี traffic สูง

การ migrate schema

Serviceกลไก migrateรายละเอียด
vtrc-apisyncModelToTable() ทุกครั้งที่ startไม่มี migration file แยก — Sequelize auto-sync ตามโมเดล
cu-central-apisync() ของ Sequelizeเช่นเดียวกัน
centralize-apiTypeORM synchronize: true หรือ falseขึ้นกับ env (ดู app.module.ts)

ปัญหาของการใช้ sync แทน migration:

  • ไม่สามารถ rollback ได้
  • การเปลี่ยนแปลง column type อาจ drop ข้อมูล
  • ไม่มี review ก่อน deploy schema change
  • ถ้าโมเดลมีความผิดพลาด schema จะถูกแก้ทันทีใน production

รายละเอียดเต็มอยู่ใน Volume 8 บทที่ 4

ปัญหาเชิงปฏิบัติการ

ปัญหาที่มาผลกระทบ
ไม่มี migration filesyncModelToTableschema change ไม่มี review
pool 100 × 4 replicaconnector.js:22อาจทำ MariaDB overload
Redis ไม่มี replicadocker-composeถ้า Redis crash — cu-central session หาย; cache ของ vtrc หาย; OTP ไม่หาย (อยู่ MariaDB)
trustServerCertificate: true (MSSQL)app.module.tsMITM risk
useUTC: falseconnector.js:13timezone migration ยาก
cross-schema write ไม่มี transactionconnector.jspartial write เมื่อ fail (Volume 8 บทที่ 11)

ข้อแนะนำเพื่อปรับปรุง

  1. เปลี่ยนจาก sync เป็น migration — Sequelize CLI migration หรือ golang-migrate
  2. เพิ่ม Redis HA — Redis Sentinel หรือ Redis Cluster
  3. ลด pool size — หรือใช้ pgBouncer-style proxy (แม้จะใช้กับ MySQL/MariaDB ได้ผ่าน ProxySQL)
  4. ตั้ง useUTC: true และแปลง timezone ที่ presentation layer
  5. เพิ่ม transaction สำหรับ cross-schema write — หรือ redesign ให้แต่ละ schema เป็น service boundary (รายละเอียด Volume 8)
  6. เปิด trustServerCertificate: false และติดตั้ง internal CA cert