PipeCraft

Roadmap Progress

ข้อมูลเบื้องต้นเกี่ยวกับวิศวกรรมข้อมูล (Introduction to Data Engineering) Python สำหรับ Data Engineering พื้นฐาน Linux & CLI สำหรับวิศวกรข้อมูล Git สำหรับระบบงานข้อมูลและทีมพัฒนา พื้นฐานเครือข่าย เว็บเทคโนโลยี และระบบกระจายศูนย์ (Web Fundamentals, Networking & Distributed Systems) SQL ขั้นสูง (Window Functions & Optimization) ฐานข้อมูลไม่ใช่เชิงสัมพันธ์ (NoSQL Databases & Modern Stores) การออกแบบโครงสร้างข้อมูล (Star/Snowflake & SCD) โครงสร้างพื้นฐานเครือข่ายระบบคลาวด์ (Cloud-based Networking) สถาปัตยกรรม Data Lakehouse (Apache Iceberg & MinIO) Local-first Data Engineering ด้วย DuckDB โครงสร้างพื้นฐานในรูปแบบโค้ด (IaC ด้วย Terraform) การแปลงข้อมูลระดับโปรด้วย dbt-core ข้อตกลงร่วมด้านข้อมูล (Data Contracts) การบริการและการส่งต่อข้อมูลวิเคราะห์ (Data Serving & Reverse ETL) การประมวลผลข้อมูลขนาดใหญ่แบบกระจายด้วย Apache Spark ระบบจัดการเวิร์กโฟลว์ข้อมูล (Airflow, Dagster & Prefect) การประมวลผลข้อมูลแบบเรียลไทม์ด้วย Kafka & Redpanda การจัดการคอนเทนเนอร์และคลัสเตอร์ (Containers & Kubernetes) ระบบ CI/CD และการเฝ้าระวังคุณภาพข้อมูล (CI/CD, Monitoring & Testing) ระบบความปลอดภัยและการควบคุมข้อมูล (Security, Governance & Privacy) ระบบปฏิบัติการและประมวลผลโมเดล (Machine Learning & MLOps) รากฐานของ AWS VPC การจัดการเส้นทางขั้นสูงและ NAT Gateway การเชื่อมต่อหลาย VPC และ Hybrid Cloud VPC Endpoints (PrivateLink) ระบบรักษาความปลอดภัยขอบเขตเครือข่าย สถาปัตยกรรมเครือข่ายสำหรับ EKS IPv6 และการจัดการ IP Address สถาปัตยกรรมเครือข่ายระดับโลก แก่นแท้ของสถาปัตยกรรม Kubernetes สถาปัตยกรรมเครื่องและคอมโพเนนต์ เลเยอร์ที่เชื่อมต่อได้ (Pluggable Layers) การรันบนโปรดักชันระดับองค์กร Apache Flink Stateful Stream Processing Real-Time CDC & Event Sourcing at Scale Low-Latency Stream-Table Joins & Windowing Data Lineage & Metadata Graph Engineering Statistical Anomaly Detection & Data Drift Data Incident Management & Automated DLQ Remediation Cloud Data FinOps & Cost Optimization Mechanics Data Mesh & Multi-Tenant Platform Architecture Vector Databases & AI-Ready Data Infrastructure API Fundamentals, Architectures & Core Components API Versioning, Docs, Real-Time & Microservices Production Reliability, Security & Resiliency World-Class Master Architecture & Traffic Management
บทที่ 11: วิศวกรรมสถาปัตยกรรม API ระดับองค์กร (Enterprise API Engineering & Architecture)

Production Reliability, Security & Resiliency

11.3 Enterprise API Reliability & Security (Thai)

เจาะลึกสถาปัตยกรรมการออกแบบ API ระดับองค์กรที่ต้องรองรับโหลดมหาศาล พร้อมทั้งรับประกันความน่าเชื่อถือและความปลอดภัยสูงสุด

🖼️ Technical Architecture Diagram: API Security & Circuit Breaker

API Security & Circuit Breaker
Step-by-Step Breakdown:
  1. Incoming requests pass through WAF and Rate Limiter.
  2. Service A calls Service B. Service B is struggling with high load.
  3. Circuit Breaker at Service A detects timeouts and trips (OPEN state).
  4. Subsequent requests instantly return a fallback response, preventing cascading failure.

🟢 Basic Level (ปูพื้นฐาน)

ถ้าปลั๊กไฟช็อต เบรกเกอร์จะตัดไฟทั้งบ้านเพื่อป้องกันไฟไหม้ Circuit Breaker ใน API ก็ตัดการเชื่อมต่อเพื่อกันเซิร์ฟเวอร์ล่ม

🟡 Intermediate Level (โค้ด/คอนฟิกจริง)

การใช้ไลบรารีอย่าง Resilience4j หรือ pybreaker กำหนด max_failures=5, timeout=2s, และ reset_timeout=30s

🔴 Professional Level (Under-the-hood & FinOps)

Distributed Circuit Breakers ผ่าน Service Mesh (Istio/Envoy) และลด Resource Exhaustion cost บน Cloud

🏢 Real-World Enterprise Scenario

แพลตฟอร์มเทรดหุ้นรับมือกับระบบ Payment Gateway ของธนาคารที่ล่ม โดยแสดงยอดเงินจำลองชั่วคราวแทนที่จะค้าง

1. Idempotency Keys (การป้องกันคำสั่งซ้ำด้วย Redis lock)

ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)

Idempotency คือคุณสมบัติที่ทำให้การเรียก API ซ้ำกี่ครั้งก็ตามด้วยข้อมูลชุดเดิม จะได้ผลลัพธ์และการเปลี่ยนแปลงสถานะเหมือนการเรียกครั้งแรกเพียงครั้งเดียว (เช่น การตัดบัตรเครดิต) กลไกที่นิยมคือให้ Client ส่ง `Idempotency-Key` (มักเป็น UUID) มาใน Header ฝั่ง Server จะใช้ Redis `SETNX` (Set if Not eXists) เพื่อเก็บ Key นี้ หากเป็นการเรียกครั้งแรกจะผ่านไปได้และล็อค Key ไว้ หากมี Request อื่นที่ใช้ Key เดียวกันตามมาจะถูกปฏิเสธหรือคืนผลลัพธ์เดิมกลับไป

ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)


import redis
from fastapi import FastAPI, Header, HTTPException

app = FastAPI()
redis_client = redis.Redis(host='localhost', port=6379, db=0)

@app.post("/payments/")
async def process_payment(payment_data: dict, idempotency_key: str = Header(...)):
    # 1. พยายามเซ็ตคีย์ใน Redis โดยมีอายุ 24 ชั่วโมง (86400 วินาที)
    is_new_request = redis_client.set(f"idemp:{idempotency_key}", "processing", nx=True, ex=86400)
    
    if not is_new_request:
        # คีย์นี้มีอยู่แล้ว แปลว่าถูกเรียกซ้ำ
        raise HTTPException(status_code=409, detail="Idempotency key already exists. Request is being processed or completed.")
    
    try:
        # 2. ประมวลผลการจ่ายเงินจริง
        result = do_actual_payment(payment_data)
        # 3. อัปเดตผลลัพธ์
        redis_client.set(f"idemp:{idempotency_key}", str(result), ex=86400)
        return {"status": "success", "result": result}
    except Exception as e:
        # หากล้มเหลว ให้ลบคีย์ทิ้งเพื่อให้ลองใหม่ได้
        redis_client.delete(f"idemp:{idempotency_key}")
        raise e
                

Use Case ในชีวิตจริง (Real-world Scenario)

แอปพลิเคชัน E-commerce ช่วง Flash Sale ลูกค้ากดปุ่ม "ชำระเงิน" รัวๆ เพราะเน็ตช้าหรือแอปค้าง หากไม่มี Idempotency บัตรเครดิตลูกค้าอาจถูกตัด 5 ครั้งตามจำนวนที่กด การใช้ `Idempotency-Key` ร่วมกับ Redis ช่วยรับประกันว่าจะมีการตัดเงินเพียงครั้งเดียวเท่านั้น

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)

Pitfall: Client ส่ง Idempotency Key เดียวกันแต่ Body ไม่เหมือนเดิม หรือ Server คืน 500 แต่ไม่ได้ปลดล็อค ทำให้ Client ลองใหม่ด้วย Key เดิมไม่ได้
Mitigation: ควรมีการ Hash Request Body เก็บไว้เทียบกับตอนที่ส่งมาครั้งแรก และควรครอบ Exception เพื่อ Release Lock หรือมีระบบ Background Cleanup ให้สอดคล้องกับสถานะจริงใน Database

2. Cursor Pagination vs Offset Pagination

ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)

Offset Pagination ใช้ `LIMIT X OFFSET Y` ฐานข้อมูลต้องกวาดข้อมูลตั้งแต่แถวแรกจนถึง OFFSET แล้วจึงตัดทิ้ง ทำให้ช้ามากเมื่อ OFFSET มีค่าสูง (Deep Paging) แถมยังมีปัญหาข้อมูลซ้ำ/หาย หากมีข้อมูลถูกแทรกหรือลบระหว่างเปลี่ยนหน้า
Cursor Pagination ใช้หลักการจำตำแหน่งล่าสุด (Cursor) ซึ่งมักเป็น Column ที่ Sort ไว้ (เช่น ID หรือ Timestamp) ควบคู่กับเงื่อนไข `WHERE id > :cursor LIMIT X` ทำให้ DB สามารถใช้ Index ข้ามไปยังตำแหน่งนั้นได้ทันที ทำงานได้เร็วคงที่ไม่ว่าข้อมูลจะเยอะแค่ไหน

ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)


-- แบบ Offset (ช้าเมื่อหน้าลึกๆ)
SELECT id, title, created_at 
FROM posts 
ORDER BY id DESC 
LIMIT 20 OFFSET 100000;

-- แบบ Cursor (เร็วเสมอ)
-- สมมติว่า Cursor ที่ Client ส่งมาคือ next_cursor = 854039
SELECT id, title, created_at 
FROM posts 
WHERE id < 854039 
ORDER BY id DESC 
LIMIT 20;
                

Use Case ในชีวิตจริง (Real-world Scenario)

News Feed ของ Facebook หรือ Twitter ที่มีระบบ Infinite Scroll การดึง Feed ใช้ Cursor Pagination (มักเข้ารหัสแบบ Base64 เป็น Opaque Token) เพื่อป้องกันปัญหาคนโพสต์ข้อมูลแทรกแล้วทำให้ผู้ใช้เห็น Feed ซ้ำซ้อนตอนเลื่อนลง

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)

Pitfall: Cursor ทำระบบ "กระโดดข้ามไปหน้า 50" ได้ยาก และ Column ที่ใช้ทำ Cursor ต้อง Unique เสมอ หากใช้ Timestamp แล้วมีข้อมูลที่ Timestamp เท่ากันเป๊ะ อาจทำให้ตกหล่นได้
Mitigation: ใช้ Multi-column cursor (เช่น `ORDER BY created_at DESC, id DESC`) และแนะนำให้ใช้ Cursor Pagination ร่วมกับ Infinite Scroll ไม่ใช่ UI แบบตัวเลขบอกหน้า

3. Circuit Breakers (Closed / Open / Half-Open)

ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)

Circuit Breaker คือสถาปัตยกรรมป้องกันความล้มเหลวแบบลูกโซ่ (Cascading Failures) เมื่อเราเรียก External Service แล้วช้าหรือพัง เราไม่ควรฝืนเรียกต่อจน Resource เราหมดตาม กลไกมี 3 สถานะ: 1) Closed (ปกติ): คำสั่งส่งผ่านปกติ 2) Open (ตัดวงจร): หาก Error เกิน Threshold จะสับสวิตช์ คำสั่งใหม่จะถูก Reject ทันทีโดยไม่ต้องรอ Timeout (Fast Failure) 3) Half-Open (ทดสอบ): หลังผ่านไประยะหนึ่ง จะลองปล่อย Request ผ่านไปจำนวนน้อยๆ หากสำเร็จจะกลับไป Closed หากยังพังจะกลับไป Open

ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)


import pybreaker
import requests

# พัง 3 ครั้งติดกันให้เปิด Circuit, รอ 60 วินาทีก่อนลอง Half-Open
api_breaker = pybreaker.CircuitBreaker(fail_max=3, reset_timeout=60)

@api_breaker
def call_external_payment_gateway():
    # หาก timeout หรือ error นับเป็น Failure
    response = requests.post("https://slow-api.com/pay", timeout=2)
    response.raise_for_status()
    return response.json()

try:
    result = call_external_payment_gateway()
except pybreaker.CircuitBreakerError:
    # Circuit Open อยู่ ให้ทำงานแบบ Fallback ทันที
    return {"status": "delayed", "message": "Payment system busy, processing later"}
                

Use Case ในชีวิตจริง (Real-world Scenario)

แอปสั่งอาหาร ส่ง Request ไปหาระบบตัดเงินของธนาคารตอนเที่ยงแล้วธนาคารล่ม หากไม่มี Circuit Breaker, Request ของผู้ใช้จะค้างรอ (Timeout) จน Thread/Connection ของแอปสั่งอาหารเต็มและล่มตามกันทั้งหมด Circuit Breaker จะตัดไฟ ทำให้แอปยังทำงานส่วนอื่นต่อได้ (เช่น โชว์หน้า Fallback ว่าให้เก็บเงินปลายทางแทน)

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)

Pitfall: การตั้งค่า Threshold ต่ำเกินไปทำให้ Circuit เปิดบ่อยแม้เป็นแค่เน็ตเวิร์คกระตุก หรือตั้ง Timeout ผิดพลาดทำให้ Circuit ไม่เคยตัด
Mitigation: ต้องทำ Load Test เพื่อหาค่า Error Rate และ Response Time ที่เหมาะสม ควบคู่ไปกับการตั้ง Fallback (เช่น ส่งต่อเข้า Message Queue) อย่างเป็นระบบ

4. OWASP API Top 10 & PDPA/GDPR Masking

ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)

ช่องโหว่ API ที่อันตรายสุดคือ BOLA (Broken Object Level Authorization) (การเข้าถึงข้อมูลคนอื่นโดยเปลี่ยน ID), BFLA (Broken Function Level Auth) (User ธรรมดาเรียก API Admin) และ Mass Assignment (User แอบส่งฟิลด์ is_admin=true เข้ามาอัปเดต) รวมถึงการคุ้มครองข้อมูลตามกฎหมาย PDPA/GDPR ด้วยการทำ Data Masking เพื่อปิดบังข้อมูลส่วนบุคคลไม่ให้หลุดผ่าน API โดยไม่ได้ตั้งใจ

ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)


# ตัวอย่างป้องกัน BOLA และ Mass Assignment
@app.put("/users/{user_id}/profile")
async def update_profile(user_id: int, request: ProfileUpdateRequest, current_user = Depends(get_current_user)):
    # 1. BOLA Prevention: เช็คว่า ID ที่แก้ไข เป็นของตัวเองหรือเปล่า
    if current_user.id != user_id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Forbidden")

    # 2. Mass Assignment Prevention: รับเฉพาะฟิลด์ที่อนุญาตผ่าน Pydantic Model (ไม่รับ is_admin)
    update_data = request.dict(exclude_unset=True) # ProfileUpdateRequest จะมีแค่ name, bio
    
    # 3. Data Masking (PDPA)
    masked_phone = mask_phone_number(current_user.phone) # '081-xxx-1234'
    return {"status": "success", "phone": masked_phone}
                

Use Case ในชีวิตจริง (Real-world Scenario)

แอปพลิเคชันเรียก `/api/receipts/555` เพื่อดูใบเสร็จ แฮกเกอร์ลองเปลี่ยนตัวเลขเป็น `556` หากไม่มีการตรวจสอบ Authorization ที่ระดับ Object (BOLA) แฮกเกอร์จะเห็นข้อมูลใบเสร็จคนอื่นทั้งหมด

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)

Pitfall: การเช็คสิทธิ์แบบกระจัดกระจาย ลืมเช็คบาง API หรือ Masking ข้อมูลช้าเกินไปจนหลุดไปใน Error Logs
Mitigation: ใช้ Middleware ในการตรวจจับ BOLA แบบรวมศูนย์ (เช่น ควบคุมผ่าน ORM Row-level Security) และทำการ Mask ข้อมูลสำคัญที่ชั้น Serialization ทันที

🛠️ Weekend Sandbox Challenge

โจทย์: สร้างระบบเติมเงิน (Wallet Top-up) ที่ไม่มีทางเติมเบิ้ล

  • ใช้ FastAPI และ Redis จำลอง API `POST /topup`
  • รับ Header `X-Idempotency-Key`
  • ใส่ `time.sleep(2)` เพื่อจำลองว่าการทำงานช้า
  • ลองยิง Request รัวๆ 5 ครั้งพร้อมกันด้วย cURL หรือ Postman
  • ต้องเห็นผลลัพธ์ว่ามียอดเงินเข้าแค่ครั้งเดียว และ Request อื่นถูก Reject อย่างรวดเร็ว

💡 Senior Technical Interview Q&A

Q: หากเราใช้ Circuit Breaker ร่วมกับ Microservices 10 ตัวที่มี dependency กัน ถ้าตัวท้ายสุดล่ม จะเกิดอะไรขึ้นกับตัวอื่นๆ?

A: หากไม่มีการตั้ง Fallback หรือ Timeout ที่เหมาะสม จะเกิด Thread Exhaustion ขึ้นแบบโดมิโนย้อนกลับไปถึง Gateway แต่ถ้าแต่ละตัวมี Circuit Breaker ที่ตั้งค่าไว้อย่างถูกต้อง ตัวที่เรียก Service ที่พังจะเปิด Circuit และส่ง Error / Fallback กลับทันที ป้องกันไม่ให้คิวสะสม และรักษา Overall System Stability ไว้ได้

Q: ทำไม Cursor Pagination ถึงใช้กับคำสั่ง Sort แบบซับซ้อน เช่น Sort ตามคะแนนความนิยม (Popularity) ได้ยาก?

A: เพราะคะแนนความนิยมมีการเปลี่ยนแปลงได้ตลอดเวลา ไม่ใช่ค่าที่เป็น Monotonically Increasing เหมือน Timestamp หรือ ID หากเรียงตามคะแนนความนิยม ระหว่างที่เลื่อนหน้าลงมา ข้อมูลด้านบนอาจถูกลดคะแนนและร่วงมาอยู่ด้านล่าง ทำให้ Cursor ผิดเพี้ยน ต้องแก้ปัญหาโดยการ Cache Snapshot ข้อมูลทั้งชุด หรือใช้ Hybrid Approach