หลังจากเขียน Stored Units ได้ครบทุกเสาหลัก คำถามสำคัญที่สุดในชีวิตวิศวกรคือ "ตรรกะไหนควรทำใน App? และตรรกะไหนควรส่งให้ DB?" วันนี้เราจะสร้าง Architectural Decision Matrix อย่างเป็นระบบ แยกงาน CPU-Bound (Hashing, Crypto, String Parsing, External API) ไปไว้บน Stateless App เพื่อให้ Scale-Out ได้ง่าย และปกป้องเซิร์ฟเวอร์ DB ให้รับเฉพาะงาน Data-Bound & Atomic Transactions ปลอดล็อก CPU 100% ตลอดกาล!
ในอดีต ยุค Monolithic DB มักมีค่านิยมสุดโต่งเขียน Business Logic ทุกอย่างไว้ใน Stored Procedures จน DB กลายเป็น "Monolithic Brain" แต่ในยุค Cloud-Native ปัจจุบัน การหลับหูหลับตาโยนทุกอย่างลง DB ทำให้เซิร์ฟเวอร์ DB เกิด CPU Bottleneck 100% และค้างทั้งระบบ ในทางกลับกัน หากผลักทุกอย่างไปอยู่บน App ก็จะเกิดปัญหา Chatty Network RTT และขาดความปลอดภัยด้าน Data Integrity!
ระบบชำระเงินของแพลตฟอร์ม E-Commerce ขนาดใหญ่ มีทีมพัฒนาที่หลงใหลใน PL/pgSQL มาก จึงเขียนตรรกะทุกอย่างลงใน DB:
ต้นตอของวิกฤตนี้คือ "การไร้ขอบเขตสถาปัตยกรรม (Architectural Boundary Breakdown)" ทางออกเดียวคือการใช้ Architectural Decision Matrix เป็นธรรมนูญของทีมครับ!
| ประเภทภาระงาน (Workload Type) | สถานที่ประมวลผลที่ถูกต้อง | เหตุผลเชิงสถาปัตยกรรมและผลกระทบต่อระบบ |
|---|---|---|
| Complex Math / Hashing / Crypto bcrypt, Argon2, SHA-256, JWT Token |
💻 Application Layer | เป็นงาน CPU-Bound อย่างแท้จริง; ฝั่ง App สามารถขยายเครื่องแบบ Scale-Out (Kubernetes/Nodes) ได้ง่ายและถูกกว่า DB มหาศาล |
| External API / HTTP Requests Payment Gateway, SMS, Line Notify |
💻 Application Layer | ป้องกันไม่ให้ Network Timeout ของระบบภายนอกมาแช่ idle in transaction และล็อกตารางจน Connection Pool ใน DB เต็ม |
| File / PDF / Image Formatting PDF Invoice Generation, Image Resizing |
💻 Application Layer | สิ้นเปลือง Memory Heap และ CPU Cycle สูงมาก ไม่เกี่ยวกับความถูกต้องของ Relational State |
| Multi-Row Atomic Transactions ตัดเงินในกระเป๋า + เพิ่มเงินปลายทาง + บันทึกบัญชี |
🗄️ Database Layer | การันตีคุณสมบัติ ACID, ควบคุม Row Locks สั้นที่สุด และป้องกัน Race Condition / Double Spending 100% |
| Mass Data Aggregation (Sum/Avg) สรุปยอดขาย 10 ล้านแถว, คำนวณงบดุล |
🗄️ Database Layer | ประมวลผลแบบ Data-Bound ติดชิดกับ Data Pages ใน RAM (shared_buffers) ลด Network RTT และหลีกเลี่ยงการขนย้ายข้อมูล |
| Data Integrity & Audit Trail Foreign Keys, Unique Constraints, Triggers |
🗄️ Database Layer | สร้าง Zero-Bypass Compliance Boundary เป็นด่านสุดท้ายที่ไม่มีใครแอบแก้ไขหรือบายพาสได้ แม้จะเชื่อมต่อผ่าน DBeaver |
"ทุกท่านครับ การโยน Business Logic ทุกอย่างลง Stored Procedure คือค่านิยมยุคเก่าที่ทำลายการ Scale ระบบบน Cloud!
ด้วย Decision Matrix ของ Day 13 เราจะคุ้มครอง CPU ของฐานข้อมูลอย่างเด็ดขาด งานประเภท Hashing, String Parsing, PDF Formatting และ External API ต้องทำบน Stateless App สเกลออกได้ 100 เครื่อง! ส่วนงานที่ต้องแตะข้อมูลล้านแถว, การตัดเงิน Atomic และ Audit Trail เราขังไว้ใน PL/pgSQL เพื่อประสิทธิภาพสูงสุด!
ผลลัพธ์คือ DB CPU ของเราจะเหลือเฟือสำหรับรับ Transaction ชำระเงิน ค่า Cloud ลดลง 60% และระบบเสถียรไม่มีวันล่มเพราะ CPU Bottleneck อีกต่อไปครับ!"
ลองเลือกสถาปัตยกรรมและประเภทของงาน เพื่อดูว่าการวางตำแหน่งโค้ดจะส่งผลต่อการใช้ CPU ของเซิร์ฟเวอร์ฐานข้อมูลและความเสี่ยงที่ระบบจะล่มอย่างไร
การออกแบบระบบแบบ Clean Boundary เริ่มจากการมองเห็นจุดตัดการทำงานอย่างชัดเจน: ฝั่ง App ทำงานที่เป็น CPU-Bound / External I/O ให้เสร็จสิ้น แล้วส่งเฉพาะค่าที่จำเป็นมาให้ Stored Procedure ฝั่ง DB ทำงานที่เป็น Data-Bound / Atomic Mutation รวดเดียวจบ:
-- ❌ ANTI-PATTERN (Heavy CPU inside DB):
-- App ---> CALL pr_everything() ---> [ Hash Password ] -> [ Parse Large JSON ] -> [ Update Table ] -> [ Call External HTTP API ]
-- ✅ REFACTORED PATTERN (Clean Boundaries):
-- 1. App Server [ Hash Password (bcrypt) & Validate JSON ] (CPU-Bound บน Stateless App)
-- 2. App Server ---> CALL pr_atomic_save(hash, jsonb) ---> DB Server [ INSERT/UPDATE + Audit Trigger ] (Data-Bound บน DB)
-- 3. App Server <--- Return Success <--- DB Server (ปลด DB Connection ทันทีใน < 2ms)
-- 4. App Server ---> Call External Payment API / Send Email (External I/O บน App นอก DB Transaction!)
-- 🎯 โจทย์: วิเคราะห์ตรรกะ 3 ข้อนี้ ว่าควรจัดวางไว้ที่เลเยอร์ใดจึงจะถูกต้องตาม Decision Matrix?
-- ข้อ [1]: การทำ Password Hashing ด้วย bcrypt / Argon2
-- ข้อ [2]: การคำนวณยอดเงินคงเหลือสุทธิจากรายการโอน 1,000,000 แถวในตาราง
-- ข้อ [3]: การส่ง Email / SMS แจ้งเตือนลูกค้าหลังชำระเงินสำเร็จ
-- ✅ เฉลยและเหตุผลเชิงสถาปัตยกรรม:
-- ข้อ [1] ➔ Application Layer:
-- เหตุผล: เป็นงาน CPU-Bound มหาศาล ยิ่งตั้ง Work Factor สูงยิ่งกิน CPU การทำบน App ช่วยให้สเกลเครื่อง App Node เพิ่มได้ง่าย
-- ข้อ [2] ➔ Database Layer (PL/pgSQL):
-- เหตุผล: เป็นงาน Data-Bound ต้องอ่าน Data Pages ใน shared_buffers การทำบน DB ช่วยตัด Network RTT และลดเวลาเหลือเสี้ยววินาที
-- ข้อ [3] ➔ Application Layer:
-- เหตุผล: เป็น External I/O ห้ามเอาไปไว้ใน DB Trigger เด็ดขาด เพราะถ้าเมลเซิร์ฟเวอร์ช้า จะดึง DB Connection ค้างจนระบบล่ม!
-- db/migration/R__01_atomic_settlement.sql
-- 🎯 ฝั่ง DB เน้นเฉพาะ Atomic State Transition และ Data Integrity เท่านั้น!
-- รับพารามิเตอร์สำเร็จรูปที่ผ่านการคำนวณและตรวจสอบมาจาก App เรียบร้อยแล้ว
CREATE OR REPLACE PROCEDURE pr_execute_settlement_atomic(
p_account_id BIGINT,
p_amount NUMERIC,
p_auth_hash VARCHAR -- รับ Hash ที่คำนวณเสร็จแล้วมาจาก App
)
LANGUAGE plpgsql
AS $$
BEGIN
-- [1] ตรวจสอบและล็อกแถวแบบ Atomic (Data Integrity Boundary)
PERFORM 1 FROM accounts WHERE account_id = p_account_id FOR UPDATE;
-- [2] อัปเดตยอดเงินใน DB (รันติดชิด RAM Buffer ในเสี้ยววินาที)
UPDATE accounts
SET balance = balance - p_amount
WHERE account_id = p_account_id;
-- [3] บันทึก Audit Log ทันที
INSERT INTO transaction_logs(account_id, amount, auth_hash, type)
VALUES (p_account_id, -p_amount, p_auth_hash, 'SETTLEMENT');
-- [4] ยืนยัน Transaction และปลด Lock ทันที ไม่ค้างคา
COMMIT;
END $$;
เปรียบเทียบโค้ดเดิมที่นำ Hashing และ String Manipulation มายัดใน Stored Procedure จน DB CPU 100% กับโค้ดใหม่ที่แบ่งขอบเขตการทำงานอย่างถูกต้อง:
-- ❌ Flawed Code: เอา Hashing และ String Manipulation มาทำใน PL/pgSQL
CREATE OR REPLACE PROCEDURE pr_process_user_registration_flawed(
p_username TEXT,
p_raw_password TEXT,
p_json_payload TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
v_hashed_pw TEXT;
v_parsed_data JSONB;
BEGIN
-- ❌ CPU Bottleneck #1: คำนวณ Hash หนักๆ ใน DB (เผา CPU บน DB)
v_hashed_pw := digest(p_raw_password || 'salt_secret', 'sha256');
-- ❌ CPU Bottleneck #2: Parse และ Clean String ขนาดใหญ่ใน DB RAM
v_parsed_data := p_json_payload::jsonb;
INSERT INTO users(username, password_hash, profile_data)
VALUES (p_username, v_hashed_pw, v_parsed_data);
COMMIT;
END $$;
-- db/migration/R__02_user_registration_optimized.sql
-- 🎯 ฝั่ง App ทำการ Hash Password และ Parse JSON มาให้เสร็จเรียบร้อยแล้ว
-- ฝั่ง DB มีหน้าที่รับค่าสำเร็จรูปมาทำ INSERT แบบ Lightning Fast!
-- [Statement 1] สร้าง Stored Procedure ที่เน้นเฉพาะ Data Persistence
CREATE OR REPLACE PROCEDURE pr_process_user_registration_optimized(
p_username VARCHAR,
p_hashed_pw VARCHAR, -- รับ Hash จาก App (App ใช้ CPU ของตัวเอง)
p_profile_data JSONB -- รับ JSONB ที่ Validate มาแล้วจาก App
)
LANGUAGE plpgsql
AS $$
BEGIN
-- [บรรทัดที่ 1-2] บันทึกข้อมูลลงตารางทันที ไม่เสียเวลาคำนวณ CPU
INSERT INTO users(username, password_hash, profile_data)
VALUES (p_username, p_hashed_pw, p_profile_data);
-- [บรรทัดที่ 3] ยืนยัน Transaction
COMMIT;
END $$;
แบบทดสอบวัดระดับความเข้าใจ Day 13: Decision Matrix - App vs. DB Boundaries & CPU Protection (CPU-Bound vs Data-Bound, Scale-Out vs Scale-Up, Zero-Bypass Compliance) และข้อสอบสะสมทบทวนครบทั้ง Days 1–12 คลิกเลือกคำตอบ ก, ข, ค, หรือ ง เพื่อดูผลลัพธ์และคำอธิบายอย่างละเอียดทันที
บทเรียน Day 13: Decision Matrix - App vs. DB Boundaries & CPU Protection ได้สร้างกรอบความเข้าใจเชิงสถาปัตยกรรมในการแบ่งแยกภาระงานระหว่าง Application Layer และ Database Layer อย่างเป็นระบบ โดยกำหนดให้นำงานประเภท CPU-Bound (Hashing, Crypto, String Parsing) และ External I/O ไปไว้ที่ฝั่ง App เพื่อเปิดทางให้ Scale-Out ได้ง่าย และปกป้อง CPU ของฐานข้อมูล ให้ทำเฉพาะงาน Data-Bound และ Atomic Transactions อย่างมีประสิทธิภาพสูงสุด
ในวันถัดไป เราจะไปสรุปภาพรวมของ Phase 3 โดยการเจาะลึกข้อดี-ข้อเสีย (Trade-offs) และรวบรวม Anti-patterns ทั้งหมดที่ต้องหลีกเลี่ยงในการสร้าง In-Database Automation Engine เพื่อเตรียมความพร้อมเข้าสู่ Phase 4 (การซ้อมพรีเซนท์และตั้งรับคำถามระดับ Executive/Senior Architect) ครับ!