วางระบบฐานข้อมูลแบบ Distributed SQL ด้วย CockroachDB บน Docker Swarm: ตั้งแต่แนวคิดถึง Production

บทความนี้ครอบคลุม: สถาปัตยกรรม Distributed SQL, การติดตั้ง CockroachDB บน Docker Swarm แบบ Secure, Security PKI, Monitoring ด้วย Prometheus/Grafana และแนวทาง Production-ready ทั้งหมด


ส่วนที่ 1 — บริบทและแนวคิด (Context & Concepts)

1.1 ปฐมบท: ทำไมฐานข้อมูลแบบเดิมจึงไม่เพียงพอ

1.1.1 ข้อจำกัดของ Traditional RDBMS และ Vertical Scaling

ฐานข้อมูลเชิงสัมพันธ์แบบดั้งเดิม (Traditional RDBMS) อย่าง PostgreSQL, MySQL, หรือ Oracle ถูกออกแบบมาในยุคที่ Application ทำงานบนเครื่องเดียว (Single Machine) และมีผู้ใช้งานในระดับหลักร้อยถึงหลักพัน ระบบเหล่านี้ยึดหลัก ACID (Atomicity, Consistency, Isolation, Durability) อย่างเคร่งครัด ซึ่งเป็นสิ่งที่ดี แต่กลับกลายเป็น Bottleneck เมื่อ Scale ขึ้น

ปัญหาหลักของ Vertical Scaling:

┌─────────────────────────────────────────────────┐
│           Vertical Scaling (Scale Up)           │
│                                                 │
│  [Small Server]  →  [Big Server]  →  [LIMIT!]  │
│   4 CPU, 16GB      32 CPU, 256GB    128 CPU?    │
│                                                 │
│  ✗ ราคาแพงแบบ Exponential                      │
│  ✗ Single Point of Failure ยังอยู่             │
│  ✗ มี Physical Ceiling                         │
└─────────────────────────────────────────────────┘

ตัวอย่างการคำนวณต้นทุน Vertical Scaling:

ขนาดเซิร์ฟเวอร์ ราคาโดยประมาณ/เดือน (Cloud) Throughput ที่ได้
4 vCPU, 16 GB RAM $150 ~1,000 TPS
16 vCPU, 64 GB RAM $600 ~3,500 TPS
64 vCPU, 256 GB RAM $2,400 ~10,000 TPS
128 vCPU, 512 GB RAM $9,600 ~18,000 TPS

สังเกตว่าราคาเพิ่ม 64 เท่า แต่ Throughput เพิ่มเพียง 18 เท่า — นี่คือปัญหา Diminishing Returns ของ Vertical Scaling

1.1.2 NoSQL แก้ปัญหาได้จริงหรือ? และต้นทุนที่แลกมา

NoSQL (Not Only SQL) เกิดขึ้นในช่วงปี 2000s เพื่อแก้ปัญหา Scale ของ Web Scale Companies อย่าง Facebook, Amazon, Google ระบบเหล่านี้สามารถ Scale แนวนอน (Horizontal Scaling) ได้ดีมาก แต่แลกมาด้วยต้นทุนที่สำคัญ

ประเภทของ NoSQL และข้อจำกัด:

CAP Theorem และการ Trade-off:

ทฤษฎีบท CAP (CAP Theorem) ระบุว่าระบบ Distributed ไม่สามารถรับประกันได้ทั้ง 3 อย่างพร้อมกัน:

CAP = Consistency + Availability + Partition Tolerance

โดยที่:

NoSQL ส่วนใหญ่เลือก AP (Available + Partition Tolerant) จึงยอมเสีย Consistency โดยใช้ Eventual Consistency ซึ่งหมายความว่า:

[Node A] เขียน: balance = 1000
[Node B] อ่าน: balance = 800  ← อาจยังไม่ sync!
[Node B] อ่านอีกครั้ง: balance = 1000  ← sync แล้ว

สำหรับ Application ทางการเงิน การที่ยอดเงินไม่ถูกต้อง แม้เพียงชั่วคราว ถือเป็นปัญหาร้ายแรงมาก

1.1.3 NewSQL คืออะไร และมาอยู่ตรงไหนในสมการ

NewSQL คือแนวคิดที่ต้องการ "เค้กทั้งชิ้น" — ความสามารถในการ Scale แนวนอนของ NoSQL รวมกับ ACID Guarantees ของ RDBMS แบบดั้งเดิม

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2', 'primaryBorderColor': '#504945', 'lineColor': '#d5c4a1', 'secondaryColor': '#3c3836', 'tertiaryColor': '#32302f', 'background': '#282828', 'mainBkg': '#282828', 'nodeBorder': '#504945', 'clusterBkg': '#3c3836', 'titleColor': '#ebdbb2', 'edgeLabelBackground': '#3c3836', 'attributeBackgroundColorEven': '#282828', 'attributeBackgroundColorOdd': '#3c3836'}}}%%
graph LR
    subgraph Traditional["Traditional RDBMS (CP)"]
        T1["✓ Strong Consistency\n✓ ACID Transaction\n✗ Horizontal Scale\n✗ Single Point of Failure"]
    end

    subgraph NoSQL["NoSQL (AP)"]
        N1["✓ Horizontal Scale\n✓ High Availability\n✗ Eventual Consistency\n✗ Limited Transactions"]
    end

    subgraph NewSQL["NewSQL (CP + Scale)"]
        NS1["✓ Strong Consistency\n✓ ACID Transaction\n✓ Horizontal Scale\n✓ Fault Tolerant"]
    end

    Traditional -->|"Scale Problem"| NewSQL
    NoSQL -->|"Consistency Problem"| NewSQL

    style Traditional fill:#cc241d,color:#ebdbb2
    style NoSQL fill:#d79921,color:#282828
    style NewSQL fill:#98971a,color:#ebdbb2

ตัวแทนของ NewSQL:

ระบบ ต้นกำเนิด ฐานเทคโนโลยี
CockroachDB Cockroach Labs Raft Consensus + RocksDB/Pebble
Google Spanner Google TrueTime API + Paxos
YugabyteDB Yugabyte Inc. Raft + DocDB
TiDB PingCAP Raft + TiKV
Vitess YouTube/CNCF MySQL Sharding Layer

1.2 ต้นกำเนิด CockroachDB

1.2.1 แรงบันดาลใจจาก Google Spanner และเอกสาร Spanner Paper

CockroachDB ถูกสร้างขึ้นโดย Spencer Kimball, Peter Mattis และ Ben Darnell ในปี 2015 ทั้งสามคนเป็นอดีตวิศวกรจาก Google โดยตรงได้รับแรงบันดาลใจจากเอกสารวิจัย "Spanner: Google's Globally-Distributed Database" ที่ตีพิมพ์ในปี 2012

สิ่งที่ Google Spanner ทำได้:

ความท้าทายที่ CockroachDB ต้องแก้: Google มี Hardware พิเศษ (GPS + Atomic Clock) ที่ทั่วไปไม่มี CockroachDB จึงต้องคิดค้น Hybrid Logical Clock (HLC) เพื่อแทนที่ TrueTime โดยไม่ต้องพึ่ง Hardware พิเศษ

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2', 'primaryBorderColor': '#504945', 'lineColor': '#d5c4a1', 'secondaryColor': '#3c3836', 'tertiaryColor': '#32302f', 'background': '#282828', 'mainBkg': '#282828', 'nodeBorder': '#504945', 'clusterBkg': '#3c3836', 'titleColor': '#ebdbb2', 'edgeLabelBackground': '#3c3836'}}}%%
timeline
    title วิวัฒนาการของ Distributed Database
    2012 : Google Spanner Paper ตีพิมพ์
         : โลกรู้ว่า Global Strong Consistency เป็นไปได้
    2015 : Cockroach Labs ก่อตั้ง
         : CockroachDB v1 เริ่มพัฒนา
         : ใช้ Raft แทน Paxos
    2017 : CockroachDB v1.0 Released
         : Production-ready ครั้งแรก
    2019 : v19.1 - Multi-region Partitioning
         : Geo-partitioning feature
    2021 : v21.1 - Multi-region Abstractions
         : Survival Goals concept
    2022 : v22.1 - License เปลี่ยนเป็น BSL
         : ข้อจำกัดการใช้งานเชิงพาณิชย์
    2023 : v23.1 - Logical Data Replication
         : Physical Cluster Replication
    2024 : v24.x - Performance improvements
         : Enhanced observability

1.2.2 ปรัชญา "ฆ่าไม่ตาย" (Survive Anything) และ Cloud-native by Design

ชื่อ "CockroachDB" มาจากแมลงสาบ (Cockroach) — สัตว์ที่ขึ้นชื่อเรื่องความอึด ทนทาน และรอดชีวิตได้ในสภาวะที่เลวร้าย ปรัชญาการออกแบบมีหลักการสำคัญ:

  1. Survive node failures — ระบบยังทำงานได้แม้โหนดล่ม
  2. Survive disk failures — ข้อมูลมี Replication หลายชุด
  3. Survive network partitions — ใช้ Raft Consensus รับมือกับ Network Split
  4. Survive entire Data Center failures — Zone/Region-aware replication

Cloud-native Design Principles:

1.2.3 เปรียบเทียบคู่แข่ง: YugabyteDB, Vitess, Patroni+PostgreSQL

คุณสมบัติ CockroachDB YugabyteDB Vitess Patroni+PG
PostgreSQL Compatible บางส่วน (Wire Protocol) สูง (YQL) ต่ำ (Sharding Layer) 100%
Distributed Transaction ✓ Native ✓ Native ✗ Limited ✗ ไม่มี
Auto Rebalancing
Multi-region Native
License BSL (v22.1+) Apache 2.0 Apache 2.0 Apache 2.0
Operational Complexity ปานกลาง ปานกลาง สูง สูงมาก
OLTP Performance ดี ดี ดีมาก (Single shard) ดีมาก
Community ใหญ่ กลาง ใหญ่ ใหญ่มาก

เมื่อไหร่ควรเลือกอะไร:


1.3 License Model และข้อควรรู้ก่อนใช้งาน

1.3.1 Business Source License (BSL) ตั้งแต่ v22.1+

ตั้งแต่ CockroachDB v22.1 (พฤษภาคม 2022) Cockroach Labs ได้เปลี่ยน License จาก Apache 2.0 เป็น Business Source License (BSL) 1.1 ซึ่งมีผลกระทบที่ต้องเข้าใจ

ข้อจำกัดหลักของ BSL:

BSL → Apache 2.0 อัตโนมัติ:

Source Code จะถูก Convert เป็น Apache 2.0 โดยอัตโนมัติหลังจาก 4 ปี นับจากวันที่ Release

v22.1 (2022) → Apache 2.0 ในปี 2026
v23.1 (2023) → Apache 2.0 ในปี 2027

1.3.2 CockroachDB Core vs CockroachDB Cloud vs Enterprise

Edition ราคา Feature หลัก ข้อจำกัด
Core (Self-hosted) ฟรี SQL, Replication, HA, Backup ไม่มี Geo-partitioning, ไม่มี Encrypted Backup
CockroachDB Cloud Pay-as-you-go Managed, Auto-scaling Cloud vendor lock-in
Enterprise License fee Geo-partitioning, Encrypted Backup, SAML SSO ราคาสูง

1.3.3 ทางเลือกสำหรับ Open Source 100%

หากต้องการ Open Source อย่างแท้จริง พิจารณา:


ส่วนที่ 2 — สถาปัตยกรรมเชิงลึก (Architecture Deep Dive)

2.1 โครงสร้างภายในของ CockroachDB

2.1.1 Node & Cluster: Peer-to-Peer ไม่มี Master

สถาปัตยกรรมของ CockroachDB เป็นแบบ Symmetric Peer-to-Peer — ทุกโหนดเท่าเทียมกัน ไม่มีโหนดพิเศษที่เป็น "Master" ซึ่งแตกต่างจาก MySQL Replication หรือ Elasticsearch ที่มี Master Node

ข้อดีของ Peer-to-Peer:

Client → [Node 1] ──── [Node 2]
              │              │
              └──── [Node 3] ┘

ทุกโหนดสามารถรับ SQL Query ได้
โหนดที่รับ Query จะเป็น "Gateway" สำหรับ Transaction นั้น

2.1.2 Layers ภายใน: SQL → KV → Distribution → Replication → Storage

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2', 'primaryBorderColor': '#504945', 'lineColor': '#d5c4a1', 'secondaryColor': '#3c3836', 'tertiaryColor': '#32302f', 'background': '#282828', 'mainBkg': '#3c3836', 'nodeBorder': '#689d6a', 'clusterBkg': '#3c3836', 'titleColor': '#fabd2f', 'edgeLabelBackground': '#3c3836'}}}%%
graph TB
    subgraph "CockroachDB Internal Layers"
        SQL["SQL Layer\n(Parser, Planner, Executor)\nรับ SQL จาก Client แปลงเป็น KV Operations"]
        KV["Transactional KV Layer\n(MVCC, Transaction Coordinator)\nจัดการ Multi-key Transactions"]
        DIST["Distribution Layer\n(Range Cache, DistSender)\nหา Range ที่ถูกต้องสำหรับ Key"]
        REPL["Replication Layer\n(Raft Consensus)\nแน่ใจว่าข้อมูลถูก Replicate ครบ Quorum"]
        STORE["Storage Layer\n(Pebble - LSM Tree)\nเขียนข้อมูลลง Disk จริงๆ"]
    end

    CLIENT["SQL Client\n(Application)"] -->|"SQL Query\nSELECT * FROM orders"| SQL
    SQL -->|"KV Operations\nGet/Put/Scan"| KV
    KV -->|"Route to Range\nKey → Range → Node"| DIST
    DIST -->|"Raft Propose\nWrite to Leader"| REPL
    REPL -->|"Write WAL + SST\nCommit to Disk"| STORE

    style SQL fill:#458588,color:#ebdbb2
    style KV fill:#689d6a,color:#ebdbb2
    style DIST fill:#d79921,color:#282828
    style REPL fill:#cc241d,color:#ebdbb2
    style STORE fill:#8f3f71,color:#ebdbb2
    style CLIENT fill:#504945,color:#ebdbb2

อธิบายแต่ละ Layer:

  1. SQL Layer — รับ SQL Query, Parse เป็น AST (Abstract Syntax Tree), Optimize Query Plan, Execute
  2. Transactional KV Layer — ทุก SQL Operation ถูกแปลงเป็น Key-Value Operations พร้อม MVCC (Multi-Version Concurrency Control)
  3. Distribution Layer — รู้ว่า Key แต่ละตัวอยู่ใน Range ไหน บน Node ไหน (ผ่าน Range Cache)
  4. Replication Layer — ใช้ Raft Algorithm เพื่อให้แน่ใจว่าการเขียนถูก Commit ครบ Quorum ก่อน Return Success
  5. Storage Layer — ใช้ Pebble (Go implementation ของ RocksDB) ซึ่งเป็น LSM Tree-based Storage Engine

2.2 Raft Consensus Algorithm

2.2.1 กลไกการเลือก Leader และ Quorum

Raft คือ Consensus Algorithm ที่ออกแบบให้เข้าใจง่ายกว่า Paxos โดยมีแนวคิดหลักคือ "Replicated State Machine" — ทุก Node ต้อง Execute ลำดับคำสั่งเดียวกัน เพื่อให้ State เหมือนกัน

กระบวนการเลือก Leader (Leader Election):

  1. เริ่มต้นทุกโหนดเป็น Follower
  2. หาก Follower ไม่ได้รับ Heartbeat จาก Leader ภายใน Election Timeout (150–300ms) จะเปลี่ยนเป็น Candidate
  3. Candidate ส่ง RequestVote ไปยัง Follower ทุกตัว
  4. Follower Vote ให้ Candidate ที่มี Log ใหม่ที่สุด (ป้องกันข้อมูลหาย)
  5. Candidate ที่ได้รับ Vote ≥ Quorum กลายเป็น Leader

สูตร Quorum:

Quorum = N 2 + 1

โดยที่ N = จำนวน Replica ทั้งหมด

ตัวอย่างการคำนวณ:

2.2.2 อธิบาย Write Path และ Read Path แบบ Step-by-step

Write Path (การเขียนข้อมูล):

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2', 'primaryBorderColor': '#504945', 'lineColor': '#d5c4a1', 'secondaryColor': '#3c3836', 'tertiaryColor': '#32302f', 'background': '#282828', 'mainBkg': '#282828', 'nodeBorder': '#504945', 'clusterBkg': '#3c3836', 'titleColor': '#fabd2f', 'edgeLabelBackground': '#3c3836'}}}%%
sequenceDiagram
    participant Client as SQL Client
    participant Gateway as Gateway Node
    participant Leader as Raft Leader
    participant F1 as Follower 1
    participant F2 as Follower 2

    Client->>Gateway: INSERT INTO orders VALUES (...)
    Gateway->>Leader: KV Put (Route to Leaseholder)
    Note over Leader: 1. AppendEntries to Log
    Leader->>F1: AppendEntries RPC
    Leader->>F2: AppendEntries RPC
    F1-->>Leader: ACK (Log written)
    F2-->>Leader: ACK (Log written)
    Note over Leader: 2. Quorum reached (2/2 Followers)
    Note over Leader: 3. Commit entry to State Machine
    Leader-->>Gateway: Write Committed
    Gateway-->>Client: SUCCESS
    Note over F1,F2: 4. Apply entry asynchronously

Read Path (การอ่านข้อมูล):

Strong Read (Default):
Client → Gateway → Leaseholder → ตอบกลับทันที
(ไม่ต้องรอ Quorum เพราะ Leaseholder รับประกัน Fresh Data)

Follower Read (Stale, ถ้าเปิดใช้):
Client → Gateway → Follower → ตอบกลับ (อาจช้า ~500ms)
(ข้อมูลอาจช้ากว่า Reality เล็กน้อย แต่ Latency ต่ำกว่า)

2.2.3 Strong Consistency (Serializability) ทำงานอย่างไร

CockroachDB รับประกัน Serializable Isolation ซึ่งเป็น Level สูงสุดของ ACID Isolation โดยใช้ MVCC + Write Intent + Timestamp Ordering

กลไก MVCC (Multi-Version Concurrency Control):

-- Transaction 1 (เริ่มที่ T=100)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
-- อ่าน Snapshot ณ T=100 → balance = 1000

-- Transaction 2 (เริ่มที่ T=101)
BEGIN;
UPDATE accounts SET balance = 500 WHERE id = 1;
COMMIT; -- Commit ที่ T=102

-- Transaction 1 ยังคงเห็น balance = 1000
-- เพราะมัน Snapshot ที่ T=100
SELECT balance FROM accounts WHERE id = 1;
-- ยังคงเห็น 1000 (ไม่ใช่ 500)
COMMIT;

2.3 Range, Replica และ Leaseholder

2.3.1 การแบ่งข้อมูลเป็น Range

Range คือหน่วยพื้นฐานของการจัดการข้อมูลใน CockroachDB โดย:

Table: orders (1.5 GB ข้อมูล)

Range 1: id [1 ... 50,000]          → 500 MB → บน Node 1
Range 2: id [50,001 ... 100,000]    → 500 MB → บน Node 2
Range 3: id [100,001 ... 150,000]   → 500 MB → บน Node 3

2.3.2 Replica Placement และ Replication Factor

Replication Factor = จำนวน Copy ของแต่ละ Range (Default = 3)

Range 1: id [1 ... 50,000]
  Replica 1 (Leader): Node 1  ←── Raft Leader
  Replica 2 (Follower): Node 2
  Replica 3 (Follower): Node 3

Zone Configuration สำหรับ Replication Factor:

-- ดู Replication Zone ปัจจุบัน
SHOW ZONE CONFIGURATION FOR RANGE default;

-- เปลี่ยน Default Replication Factor เป็น 5
ALTER RANGE default CONFIGURE ZONE USING num_replicas = 5;

-- กำหนด Replication สำหรับ Table เฉพาะ
ALTER TABLE orders CONFIGURE ZONE USING
  num_replicas = 5,
  constraints = '{"+region=th-central": 3, "+region=th-east": 2}';

2.3.3 Leaseholder คืออะไรและมีผลต่อ Latency อย่างไร

Leaseholder คือ Replica ที่ได้รับ "สิทธิ์ชั่วคราว" ในการให้บริการ Strong Read โดยไม่ต้องรอ Quorum

ผลต่อ Latency:

ถ้า Application อยู่ในประเทศไทย แต่ Leaseholder อยู่ที่ US:
→ Latency สูงมาก (200ms+ ต่อ Query)

ถ้า Leaseholder ถูก Move มาไว้ใกล้ Application:
→ Latency ลดลงอย่างมาก (< 5ms)
-- Follow-the-Workload: ย้าย Leaseholder ตามการใช้งาน (Enterprise)
ALTER TABLE orders CONFIGURE ZONE USING
  lease_preferences = '[[+region=th-central]]';

2.4 HLC และความสำคัญของนาฬิกาในระบบ

2.4.1 Hybrid Logical Clock (HLC) ทำงานอย่างไร

HLC (Hybrid Logical Clock) รวม 2 แนวคิดเข้าด้วยกัน:

HLC = ( l , c )

โดยที่:

กฎการอัปเดต HLC:

  1. เมื่อส่ง Message: l = max(l_local, time_now), c = c + 1
  2. เมื่อรับ Message: l = max(l_local, l_message, time_now), ปรับ c ตามกฎ

2.4.2 ผลลัพธ์เมื่อ Clock Drift เกิน 500ms — และทำไมถึง Fatal

CockroachDB ปฏิเสธที่จะทำงาน หากนาฬิกาของโหนดใดโหนดหนึ่งต่างจากโหนดอื่นเกิน 500ms

ทำไมถึง Fatal:

สมมติ Node A มีเวลา T=1000ms
Node B มีเวลา T=600ms (drift 400ms)

Transaction ที่ Commit บน Node A ที่ T=900ms
จะดู "เหมือนเกิดในอนาคต" จากมุมมองของ Node B
→ ลำดับ Transaction ผิดพลาด
→ ข้อมูลอาจไม่ Consistent!

เมื่อ Clock Drift เกิน Threshold จะเห็น Error:

clock synchronization error: this node is more than 500ms ahead of the connected nodes

2.4.3 NTP/Chrony ต้องตั้งค่าก่อนสิ่งอื่นใด

# ติดตั้ง Chrony บน Ubuntu/Debian
apt-get install -y chrony

# ตรวจสอบ Configuration
cat /etc/chrony.conf

# ต้องมี Server ที่ดี เช่น
# server time.google.com iburst
# server time1.google.com iburst

# ตรวจสอบ Synchronization Status
chronyc tracking
# ดู System time offset — ต้องน้อยกว่า 500ms

chronyc sources -v
# ดู NTP Server ที่ใช้อยู่

# ตรวจสอบว่า Sync แล้ว
timedatectl status
# ต้องเห็น: System clock synchronized: yes

ตัวอย่าง Output ที่ถูกต้อง:

Reference ID    : 8.8.4.4 (time.google.com)
Stratum         : 2
Ref time (UTC)  : Wed Mar 18 10:00:00 2026
System time     : 0.000012345 seconds slow of NTP time  ← ต้องน้อยกว่า 0.5
Last offset     : -0.000012345 seconds
RMS offset      : 0.000023456 seconds
Frequency       : -2.345 ppm fast

2.5 ทำไมต้อง 5 Node: Fault Tolerance Analysis

2.5.1 ตารางเปรียบเทียบ 3/5/7 Node

จำนวน Node Replication Factor Quorum ต้องการ ทนการสูญเสียได้ ต้นทุน
3 3 2 1 node ต่ำ
5 5 3 2 nodes ปานกลาง
7 7 4 3 nodes สูง
9 9 5 4 nodes สูงมาก

2.5.2 การคำนวณ: สูญเสียได้กี่โหนดโดยระบบยังทำงาน

MaxFailures = N - 1 2

โดยที่ N = จำนวน Replica (= จำนวน Node ในกรณี Replication Factor = N)

ตัวอย่างการคำนวณ:

ทำไม Production ควรใช้ 5 Node:

2.5.3 แนวคิด Rack Awareness และ Zone Distribution

Production Best Practice: กระจายโหนดข้าม Failure Domain

Zone/Rack 1    Zone/Rack 2    Zone/Rack 3
[Node 1]       [Node 2]       [Node 3]
[Node 4]       [Node 5]

→ แม้ Rack 1 ไฟดับทั้ง Rack → เหลือ Node 2,3,4,5 → Quorum ยังได้ (4 จาก 5)
# กำหนด Locality เมื่อ Start CockroachDB
cockroach start \
  --locality=region=th-central,zone=dc1,rack=rack-01 \
  ...

ส่วนที่ 3 — การออกแบบโครงสร้างพื้นฐาน (Infrastructure Design)

3.1 Network Topology และ Architecture Diagram

3.1.1 ภาพรวม: Client → Load Balancer → CockroachDB Cluster

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2', 'primaryBorderColor': '#504945', 'lineColor': '#d5c4a1', 'secondaryColor': '#3c3836', 'tertiaryColor': '#32302f', 'background': '#282828', 'mainBkg': '#282828', 'nodeBorder': '#504945', 'clusterBkg': '#3c3836', 'titleColor': '#fabd2f', 'edgeLabelBackground': '#3c3836'}}}%%
graph TB
    subgraph Internet["Internet / Internal Network"]
        APP["Application Servers\n(microservices)"]
    end

    subgraph DMZ["Load Balancer Layer"]
        LB1["HAProxy 1\n(Active)"]
        LB2["HAProxy 2\n(Standby)"]
        VIP["Virtual IP\n(Keepalived)"]
    end

    subgraph SwarmCluster["Docker Swarm Cluster"]
        subgraph Managers["Swarm Managers (3 nodes)"]
            MGR1["Manager 1\nCockroachDB Node 1\n10.0.1.1"]
            MGR2["Manager 2\nCockroachDB Node 2\n10.0.1.2"]
            MGR3["Manager 3\nCockroachDB Node 3\n10.0.1.3"]
        end
        subgraph Workers["Swarm Workers (2 nodes)"]
            WRK1["Worker 1\nCockroachDB Node 4\n10.0.1.4"]
            WRK2["Worker 2\nCockroachDB Node 5\n10.0.1.5"]
        end
    end

    subgraph Storage["Persistent Storage"]
        VOL["Local SSD Volumes\n/cockroach-data"]
    end

    APP -->|"Port 5432/26257"| VIP
    VIP --> LB1
    VIP -.->|"Failover"| LB2
    LB1 -->|"Round Robin\nHealth Check"| MGR1
    LB1 --> MGR2
    LB1 --> MGR3
    LB1 --> WRK1
    LB1 --> WRK2

    MGR1 <-->|"Raft + Gossip\nOverlay Network"| MGR2
    MGR2 <-->|":26257"| MGR3
    MGR3 <-->|"Encrypted"| WRK1
    WRK1 <-->|"VXLAN"| WRK2
    MGR1 <-->|""| WRK1
    MGR2 <-->|""| WRK2

    MGR1 --- VOL
    MGR2 --- VOL
    MGR3 --- VOL
    WRK1 --- VOL
    WRK2 --- VOL

    style Internet fill:#1d2021,color:#ebdbb2
    style DMZ fill:#3c3836,color:#ebdbb2
    style SwarmCluster fill:#282828,color:#ebdbb2
    style Managers fill:#1d2021,color:#ebdbb2
    style Workers fill:#32302f,color:#ebdbb2
    style Storage fill:#3c3836,color:#ebdbb2

3.1.2 การวางโหนดใน Docker Swarm

แนะนำให้ใช้ Manager Node เป็น CockroachDB Node ด้วย ในระบบขนาดกลาง เพราะ:

Node Swarm Role CockroachDB Role IP
server-01 Manager CockroachDB Node 1 10.0.1.1
server-02 Manager CockroachDB Node 2 10.0.1.2
server-03 Manager CockroachDB Node 3 10.0.1.3
server-04 Worker CockroachDB Node 4 10.0.1.4
server-05 Worker CockroachDB Node 5 10.0.1.5

3.1.3 Overlay Network: แยก Network สำหรับ DB Cluster และ App Tier

# สร้าง Network สำหรับ CockroachDB Cluster (Encrypted)
docker network create \
  --driver overlay \
  --opt encrypted \
  --subnet 10.10.0.0/24 \
  cockroach-net

# สร้าง Network สำหรับ Application Tier
docker network create \
  --driver overlay \
  --subnet 10.20.0.0/24 \
  app-net

# CockroachDB อยู่ใน cockroach-net
# Application อยู่ใน app-net + cockroach-net (เชื่อมทั้งสอง)
# ทำให้ DB Node ไม่ expose ตรงไปยัง External

3.2 การเตรียม Server/VM

3.2.1 Spec แนะนำต่อโหนด

ระดับ CPU RAM Disk Network
Development 2 vCPU 4 GB 50 GB SSD 1 Gbps
Small Production 8 vCPU 32 GB 500 GB NVMe 10 Gbps
Medium Production 16 vCPU 64 GB 1 TB NVMe 10 Gbps
Large Production 32 vCPU 128 GB 2 TB NVMe RAID 25 Gbps

หมายเหตุ: CockroachDB ไวต่อ Disk Latency มากกว่า Throughput — ใช้ NVMe SSD แทน SATA SSD หรือ HDD เสมอ

3.2.2 OS และ Kernel Tuning

# ===== File Descriptors =====
# CockroachDB ต้องการ File Descriptors จำนวนมาก
echo "* soft nofile 1048576" >> /etc/security/limits.conf
echo "* hard nofile 1048576" >> /etc/security/limits.conf
echo "root soft nofile 1048576" >> /etc/security/limits.conf
echo "root hard nofile 1048576" >> /etc/security/limits.conf

# ตรวจสอบหลัง Relogin
ulimit -n
# ต้องเห็น: 1048576

# ===== Kernel Parameters =====
cat >> /etc/sysctl.conf << 'EOF'
# สำหรับ CockroachDB / Docker Swarm
vm.max_map_count = 1048576
vm.swappiness = 1
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65536
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6

# สำหรับ Docker Overlay Network
net.ipv4.ip_forward = 1
EOF

sysctl -p

# ===== Transparent HugePages (ปิด — เป็นปัญหากับ Pebble/RocksDB) =====
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# ให้ Persistent หลัง Reboot
cat >> /etc/rc.local << 'EOF'
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF
chmod +x /etc/rc.local

3.2.3 NTP/Chrony Configuration

# ติดตั้ง Chrony
apt-get update && apt-get install -y chrony

# สร้าง Configuration ที่เหมาะสม
cat > /etc/chrony.conf << 'EOF'
# ใช้ Google Public NTP Servers
server time1.google.com iburst minpoll 4 maxpoll 6
server time2.google.com iburst minpoll 4 maxpoll 6
server time3.google.com iburst minpoll 4 maxpoll 6
server time4.google.com iburst minpoll 4 maxpoll 6

# Fallback ถ้า Google ไม่ได้
pool pool.ntp.org iburst

# Log file
logdir /var/log/chrony
log tracking measurements statistics

# Allow step on first three clock updates only
makestep 1.0 3

# ป้องกัน Clock ถอยหลัง (ห้าม Step ถ้า Drift เกิน 1 วินาที)
maxdistance 1.5

rtcsync
EOF

systemctl enable chrony
systemctl restart chrony

# ตรวจสอบสถานะ
chronyc tracking
chronyc sources -v

# Script ตรวจสอบทุกวัน
cat > /usr/local/bin/check-clock-sync.sh << 'EOF'
#!/bin/bash
OFFSET=$(chronyc tracking | grep "System time" | awk '{print $4}')
THRESHOLD=0.4  # 400ms (ต่ำกว่า 500ms limit ของ CockroachDB)

if (( $(echo "$OFFSET > $THRESHOLD" | bc -l) )); then
    echo "WARNING: Clock offset ${OFFSET}s exceeds ${THRESHOLD}s threshold!"
    logger -t clock-check "CRITICAL: Clock drift ${OFFSET}s on $(hostname)"
fi
EOF
chmod +x /usr/local/bin/check-clock-sync.sh

# เพิ่มใน Crontab
echo "*/5 * * * * /usr/local/bin/check-clock-sync.sh" | crontab -

3.3 Docker Swarm Setup

3.3.1 Initialize Swarm Manager และ Join Worker Nodes

# ===== บน server-01 (First Manager) =====
docker swarm init \
  --advertise-addr 10.0.1.1 \
  --listen-addr 10.0.1.1:2377

# บันทึก Token ที่ได้ ทั้ง Manager Token และ Worker Token
docker swarm join-token manager
docker swarm join-token worker

# ===== บน server-02, server-03 (Additional Managers) =====
docker swarm join \
  --token SWMTKN-1-xxxxx-manager-token \
  10.0.1.1:2377

# ===== บน server-04, server-05 (Workers) =====
docker swarm join \
  --token SWMTKN-1-xxxxx-worker-token \
  10.0.1.1:2377

# ===== ตรวจสอบบน Manager =====
docker node ls
# ต้องเห็นทุก Node และ Status = Ready

3.3.2 Label Node สำหรับ Placement Constraints

# เพิ่ม Label เพื่อใช้ใน Placement Constraints
# บน server-01
docker node update --label-add role=cockroachdb server-01
docker node update --label-add zone=dc1 server-01
docker node update --label-add rack=rack-01 server-01

# บน server-02
docker node update --label-add role=cockroachdb server-02
docker node update --label-add zone=dc1 server-02
docker node update --label-add rack=rack-02 server-02

# บน server-03
docker node update --label-add role=cockroachdb server-03
docker node update --label-add zone=dc2 server-03
docker node update --label-add rack=rack-01 server-03

# บน server-04
docker node update --label-add role=cockroachdb server-04
docker node update --label-add zone=dc2 server-04
docker node update --label-add rack=rack-02 server-04

# บน server-05
docker node update --label-add role=cockroachdb server-05
docker node update --label-add zone=dc3 server-05
docker node update --label-add rack=rack-01 server-05

# ตรวจสอบ Labels
docker node inspect server-01 --format '{{json .Spec.Labels}}'

3.3.3 การสร้าง Overlay Network

# สร้าง Overlay Network แบบ Encrypted สำหรับ CockroachDB
docker network create \
  --driver overlay \
  --opt encrypted \
  --attachable \
  --subnet 10.10.0.0/24 \
  --gateway 10.10.0.1 \
  cockroach-net

# สร้าง Network สำหรับ App Tier
docker network create \
  --driver overlay \
  --attachable \
  --subnet 10.20.0.0/24 \
  app-net

# ตรวจสอบ Networks
docker network ls | grep overlay
docker network inspect cockroach-net

3.4 Firewall และ Port Planning

# ===== UFW Rules (Ubuntu) =====

# Allow Swarm Management Port (เฉพาะระหว่าง Swarm Nodes)
ufw allow from 10.0.1.0/24 to any port 2377 proto tcp comment "Docker Swarm Manager"

# Allow Container Network Discovery
ufw allow from 10.0.1.0/24 to any port 7946 proto tcp comment "Swarm Container Discovery TCP"
ufw allow from 10.0.1.0/24 to any port 7946 proto udp comment "Swarm Container Discovery UDP"

# Allow Overlay Network (VXLAN)
ufw allow from 10.0.1.0/24 to any port 4789 proto udp comment "Docker Overlay Network VXLAN"

# Allow CockroachDB SQL / Inter-node
ufw allow from 10.0.1.0/24 to any port 26257 proto tcp comment "CockroachDB SQL + Inter-node"

# Allow CockroachDB Admin UI (เฉพาะ Internal หรือ VPN)
ufw allow from 10.0.0.0/8 to any port 8080 proto tcp comment "CockroachDB Admin UI"

# Allow HAProxy Health Check Port
ufw allow from 10.0.1.0/24 to any port 8081 proto tcp comment "HAProxy Stats"

# ===== iptables Rules (CentOS/RHEL) =====
# iptables -A INPUT -s 10.0.1.0/24 -p tcp --dport 26257 -j ACCEPT
# iptables -A INPUT -s 10.0.1.0/24 -p tcp --dport 8080 -j ACCEPT

สรุปตาราง Port:

Port Protocol วัตถุประสงค์ Source
26257 TCP SQL Client + Inter-node Communication All Nodes + Apps
8080 TCP Admin Web UI Internal/VPN Only
2377 TCP Docker Swarm Manager Swarm Nodes
7946 TCP/UDP Container Network Discovery Swarm Nodes
4789 UDP Overlay Network (VXLAN) Swarm Nodes

3.5 Storage Strategy

3.5.1 Local SSD vs Network Storage

คุณสมบัติ Local SSD/NVMe Network Storage (NFS/Ceph)
Latency < 0.1ms 1–5ms
IOPS 100K–1M+ 10K–100K
Availability ต่ำ (ผูกกับ Host) สูง (HA built-in)
Data Safety ต้องพึ่ง CockroachDB Replication มี Storage Level Replication
แนะนำ Production CockroachDB ✗ ไม่แนะนำสำหรับ DB

CockroachDB มี Replication ในตัว จึงไม่จำเป็นต้องใช้ Storage ที่มี HA → ใช้ Local SSD ตรงๆ ดีกว่า เพราะ Latency ต่ำกว่ามาก

3.5.2 Persistent Volume ใน Docker Swarm

# สร้างไดเรกทอรีบน Host ทุกโหนด
# ทำบนทุก Node ที่จะรัน CockroachDB
mkdir -p /data/cockroach/node1
chmod 755 /data/cockroach/node1
chown 1000:1000 /data/cockroach/node1  # CockroachDB User ID

# ใน docker-compose.yml ใช้ bind mount
# volumes:
#   - type: bind
#     source: /data/cockroach/node1
#     target: /cockroach/cockroach-data

3.5.3 IOPS Requirements

Minimum IOPS per CockroachDB Node:
- 500 IOPS สำหรับ Small workload
- 2,000 IOPS สำหรับ Medium workload
- 10,000+ IOPS สำหรับ High workload

ทดสอบ IOPS ก่อน Deploy:
fio --name=iops-test \
    --filename=/data/cockroach/test \
    --rw=randwrite \
    --bs=4k \
    --iodepth=16 \
    --numjobs=4 \
    --direct=1 \
    --size=1G \
    --runtime=60 \
    --time_based

# เป้าหมาย: Write IOPS > 2000 สำหรับ Production

ส่วนที่ 4 — ระบบความปลอดภัย (Security Implementation)

4.1 PKI Design: Certificate Authority

4.1.1 สร้าง CA ด้วย cockroach cert create-ca

# ===== ขั้นตอนการสร้าง PKI =====

# 1. สร้างไดเรกทอรีสำหรับ Certificates
mkdir -p /cockroach-certs
chmod 700 /cockroach-certs
cd /cockroach-certs

# 2. ดึง CockroachDB Binary (ถ้ายังไม่มี)
# Docker-based approach
docker run --rm -v /cockroach-certs:/certs \
  cockroachdb/cockroach:v24.1.0 \
  cert create-ca \
  --certs-dir=/certs \
  --ca-key=/certs/ca.key \
  --lifetime=87600h  # 10 ปี

# หรือใช้ Binary โดยตรง
cockroach cert create-ca \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=87600h

# ไฟล์ที่ได้:
# /cockroach-certs/ca.crt  ← Public CA Certificate (แจกได้)
# /cockroach-certs/ca.key  ← Private CA Key (เก็บเป็นความลับ!)

4.1.2 โครงสร้างไฟล์ Certificate

/cockroach-certs/
├── ca.crt              ← CA Certificate (Public)
├── ca.key              ← CA Private Key (เก็บเป็นความลับ!)
├── node.crt            ← Node Certificate
├── node.key            ← Node Private Key
├── client.root.crt     ← Client Certificate สำหรับ root user
├── client.root.key     ← Client Private Key สำหรับ root
├── client.app.crt      ← Client Certificate สำหรับ app user
└── client.app.key      ← Client Private Key สำหรับ app

4.1.3 อายุและการต่ออายุใบรับรอง

# ตรวจสอบอายุ Certificate
openssl x509 -in /cockroach-certs/ca.crt -noout -dates
# notBefore=Mar 18 00:00:00 2026 GMT
# notAfter=Mar 18 00:00:00 2036 GMT

# ตรวจสอบ SAN ใน Certificate
openssl x509 -in /cockroach-certs/node.crt -noout -text | grep -A1 "Subject Alternative Name"

# Script ตรวจสอบ Expiry (ตั้งเตือน 30 วันก่อนหมด)
cat > /usr/local/bin/check-cert-expiry.sh << 'EOF'
#!/bin/bash
CERT_DIR="/cockroach-certs"
WARN_DAYS=30

for cert in $CERT_DIR/*.crt; do
  EXPIRY=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
  EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  if [ $DAYS_LEFT -lt $WARN_DAYS ]; then
    echo "WARNING: $cert expires in $DAYS_LEFT days ($EXPIRY)"
  fi
done
EOF
chmod +x /usr/local/bin/check-cert-expiry.sh

4.2 Node Certificates

4.2.1 สร้าง Certificate สำหรับโหนดทั้ง 5

# สร้าง Node Certificate สำหรับแต่ละโหนด
# ต้องระบุ IP Address และ Hostname ทั้งหมดที่โหนดนี้ตอบสนอง

# Node 1
cockroach cert create-node \
  10.0.1.1 \
  server-01 \
  server-01.internal \
  localhost \
  127.0.0.1 \
  cockroach1 \
  cockroach1.cockroach-net \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=8760h \
  --overwrite

# เปลี่ยนชื่อเพื่อจัดเก็บแยก Node
mv /cockroach-certs/node.crt /cockroach-certs/node1.crt
mv /cockroach-certs/node.key /cockroach-certs/node1.key

# Node 2
cockroach cert create-node \
  10.0.1.2 \
  server-02 \
  server-02.internal \
  localhost \
  127.0.0.1 \
  cockroach2 \
  cockroach2.cockroach-net \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=8760h \
  --overwrite

mv /cockroach-certs/node.crt /cockroach-certs/node2.crt
mv /cockroach-certs/node.key /cockroach-certs/node2.key

# ทำแบบเดียวกันสำหรับ Node 3, 4, 5
# ...

# ตรวจสอบ SAN ใน Certificate
openssl x509 -in /cockroach-certs/node1.crt -noout -text | grep -A5 "Subject Alternative Name"

4.3 Client Certificates

4.3.1 สร้าง Certificate สำหรับ User root และ admin

# สร้าง Client Certificate สำหรับ root (ใช้สำหรับ Init และ Admin)
cockroach cert create-client root \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=8760h

# สร้าง Client Certificate สำหรับ Application User
cockroach cert create-client app_user \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=8760h

# สร้าง Client Certificate สำหรับ Read-only User
cockroach cert create-client readonly_user \
  --certs-dir=/cockroach-certs \
  --ca-key=/cockroach-certs/ca.key \
  --lifetime=8760h

# ตรวจสอบทุก Certificate ที่สร้าง
cockroach cert list --certs-dir=/cockroach-certs

4.3.2 การแยก Certificate ตาม Role

Role Hierarchy:
  root          → DBA operations, CREATE/DROP DATABASE, GRANT
  admin         → Database Admin, CREATE TABLE, GRANT ในฐานะของตน
  app_user      → Application: INSERT/UPDATE/DELETE/SELECT ใน Database ที่กำหนด
  readonly_user → Reporting: SELECT เท่านั้น

4.4 Docker Secrets Management

4.4.1 การ Create Secrets จาก Certificate Files

# สร้าง Docker Secrets จาก Certificate Files

# CA Certificate (Public — แชร์ได้)
docker secret create cockroach-ca-crt /cockroach-certs/ca.crt

# Node 1 Certificates
docker secret create cockroach-node1-crt /cockroach-certs/node1.crt
docker secret create cockroach-node1-key /cockroach-certs/node1.key

# Node 2 Certificates
docker secret create cockroach-node2-crt /cockroach-certs/node2.crt
docker secret create cockroach-node2-key /cockroach-certs/node2.key

# Node 3 Certificates
docker secret create cockroach-node3-crt /cockroach-certs/node3.crt
docker secret create cockroach-node3-key /cockroach-certs/node3.key

# Node 4 Certificates
docker secret create cockroach-node4-crt /cockroach-certs/node4.crt
docker secret create cockroach-node4-key /cockroach-certs/node4.key

# Node 5 Certificates
docker secret create cockroach-node5-crt /cockroach-certs/node5.crt
docker secret create cockroach-node5-key /cockroach-certs/node5.key

# Client Certificate สำหรับ root
docker secret create cockroach-client-root-crt /cockroach-certs/client.root.crt
docker secret create cockroach-client-root-key /cockroach-certs/client.root.key

# ตรวจสอบ Secrets ที่สร้าง
docker secret ls

4.4.2 Mount Path ใน Container และ Permission

# ใน docker-compose.yml
secrets:
  cockroach-ca-crt:
    external: true
  cockroach-node1-crt:
    external: true
  cockroach-node1-key:
    external: true

services:
  cockroach1:
    secrets:
      - source: cockroach-ca-crt
        target: /cockroach/certs/ca.crt
        mode: 0444  # Read-only for all
        uid: "1000"
        gid: "1000"
      - source: cockroach-node1-crt
        target: /cockroach/certs/node.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node1-key
        target: /cockroach/certs/node.key
        mode: 0400  # Read-only for owner only
        uid: "1000"
        gid: "1000"

4.5 Network Security

4.5.1 Encryption in Transit

TLS Configuration:
- CockroachDB ใช้ TLS 1.2+ สำหรับทุก Connection
- Client → Node: TLS (ใช้ Client Certificate สำหรับ Authentication)
- Node → Node: TLS (ใช้ Node Certificate)
- Cipher Suite: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384

4.5.2 Principle of Least Privilege

-- สร้าง Role แยกตามหน้าที่
CREATE ROLE readonly;
CREATE ROLE readwrite;
CREATE ROLE admin_role;

-- Assign Privileges
GRANT SELECT ON DATABASE myapp TO readonly;
GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE myapp TO readwrite;
GRANT ALL ON DATABASE myapp TO admin_role;

-- สร้าง User และ Assign Role
CREATE USER reporting_user WITH PASSWORD 'strong-password';
GRANT readonly TO reporting_user;

CREATE USER app_service WITH PASSWORD 'another-strong-password';
GRANT readwrite TO app_service;

-- ตรวจสอบ Privileges
SHOW GRANTS ON DATABASE myapp;

ส่วนที่ 5 — การติดตั้งและ Deploy (Deployment)

5.1 ออกแบบ docker-compose.yml

# cockroach.yml — Production Docker Swarm Stack
# ใช้: docker stack deploy -c cockroach.yml crdb

version: "3.8"

# ===== SECRETS =====
secrets:
  cockroach-ca-crt:
    external: true
  cockroach-node1-crt:
    external: true
  cockroach-node1-key:
    external: true
  cockroach-node2-crt:
    external: true
  cockroach-node2-key:
    external: true
  cockroach-node3-crt:
    external: true
  cockroach-node3-key:
    external: true
  cockroach-node4-crt:
    external: true
  cockroach-node4-key:
    external: true
  cockroach-node5-crt:
    external: true
  cockroach-node5-key:
    external: true
  cockroach-client-root-crt:
    external: true
  cockroach-client-root-key:
    external: true

# ===== NETWORKS =====
networks:
  cockroach-net:
    external: true
  app-net:
    external: true

# ===== VOLUMES =====
volumes:
  cockroach1-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/cockroach/node1
  cockroach2-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/cockroach/node2
  cockroach3-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/cockroach/node3
  cockroach4-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/cockroach/node4
  cockroach5-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/cockroach/node5

# ===== SERVICES =====
services:

  # ─────────── CockroachDB Node 1 ───────────
  cockroach1:
    image: cockroachdb/cockroach:v24.1.0
    hostname: cockroach1
    command: >
      start
      --certs-dir=/cockroach/certs
      --store=/cockroach/cockroach-data
      --listen-addr=0.0.0.0:26257
      --advertise-addr=cockroach1:26257
      --http-addr=0.0.0.0:8080
      --join=cockroach1:26257,cockroach2:26257,cockroach3:26257,cockroach4:26257,cockroach5:26257
      --locality=region=th-central,zone=dc1,rack=rack-01
      --cache=8GiB
      --max-sql-memory=8GiB
      --logtostderr=WARNING
      --log-config-file=/cockroach/log.yaml
    networks:
      - cockroach-net
    ports:
      - target: 8080
        published: 8081
        protocol: tcp
        mode: host
    volumes:
      - cockroach1-data:/cockroach/cockroach-data
    secrets:
      - source: cockroach-ca-crt
        target: /cockroach/certs/ca.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node1-crt
        target: /cockroach/certs/node.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node1-key
        target: /cockroach/certs/node.key
        mode: 0400
        uid: "1000"
        gid: "1000"
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.hostname == server-01
          - node.labels.role == cockroachdb
      resources:
        limits:
          cpus: "14"
          memory: 28G
        reservations:
          cpus: "4"
          memory: 16G
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 5
        window: 120s
    healthcheck:
      test: ["CMD", "curl", "-f", "--insecure", "https://localhost:8080/health?ready=1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # ─────────── CockroachDB Node 2 ───────────
  cockroach2:
    image: cockroachdb/cockroach:v24.1.0
    hostname: cockroach2
    command: >
      start
      --certs-dir=/cockroach/certs
      --store=/cockroach/cockroach-data
      --listen-addr=0.0.0.0:26257
      --advertise-addr=cockroach2:26257
      --http-addr=0.0.0.0:8080
      --join=cockroach1:26257,cockroach2:26257,cockroach3:26257,cockroach4:26257,cockroach5:26257
      --locality=region=th-central,zone=dc1,rack=rack-02
      --cache=8GiB
      --max-sql-memory=8GiB
      --logtostderr=WARNING
    networks:
      - cockroach-net
    volumes:
      - cockroach2-data:/cockroach/cockroach-data
    secrets:
      - source: cockroach-ca-crt
        target: /cockroach/certs/ca.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node2-crt
        target: /cockroach/certs/node.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node2-key
        target: /cockroach/certs/node.key
        mode: 0400
        uid: "1000"
        gid: "1000"
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.hostname == server-02
          - node.labels.role == cockroachdb
      resources:
        limits:
          cpus: "14"
          memory: 28G
        reservations:
          cpus: "4"
          memory: 16G
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 5
    healthcheck:
      test: ["CMD", "curl", "-f", "--insecure", "https://localhost:8080/health?ready=1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # Node 3, 4, 5 มีโครงสร้างเหมือนกัน เปลี่ยนเพียง:
  # hostname, advertise-addr, placement constraints,
  # secrets (node3/4/5), locality (zone/rack), volumes

  # ─────────── CockroachDB Node 3 ───────────
  cockroach3:
    image: cockroachdb/cockroach:v24.1.0
    hostname: cockroach3
    command: >
      start
      --certs-dir=/cockroach/certs
      --store=/cockroach/cockroach-data
      --listen-addr=0.0.0.0:26257
      --advertise-addr=cockroach3:26257
      --http-addr=0.0.0.0:8080
      --join=cockroach1:26257,cockroach2:26257,cockroach3:26257,cockroach4:26257,cockroach5:26257
      --locality=region=th-central,zone=dc2,rack=rack-01
      --cache=8GiB
      --max-sql-memory=8GiB
      --logtostderr=WARNING
    networks:
      - cockroach-net
    volumes:
      - cockroach3-data:/cockroach/cockroach-data
    secrets:
      - source: cockroach-ca-crt
        target: /cockroach/certs/ca.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node3-crt
        target: /cockroach/certs/node.crt
        mode: 0444
        uid: "1000"
        gid: "1000"
      - source: cockroach-node3-key
        target: /cockroach/certs/node.key
        mode: 0400
        uid: "1000"
        gid: "1000"
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.hostname == server-03
          - node.labels.role == cockroachdb
      resources:
        limits:
          cpus: "14"
          memory: 28G
        reservations:
          cpus: "4"
          memory: 16G
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 5
    healthcheck:
      test: ["CMD", "curl", "-f", "--insecure", "https://localhost:8080/health?ready=1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # ─────────── HAProxy Load Balancer ───────────
  haproxy:
    image: haproxy:2.8-alpine
    networks:
      - cockroach-net
      - app-net
    ports:
      - target: 26257
        published: 26257
        protocol: tcp
        mode: ingress
      - target: 8404
        published: 8404
        protocol: tcp
        mode: ingress
    configs:
      - source: haproxy-config
        target: /usr/local/etc/haproxy/haproxy.cfg
    deploy:
      replicas: 2
      placement:
        constraints:
          - node.role == manager
      update_config:
        parallelism: 1
        delay: 30s
      restart_policy:
        condition: on-failure
    healthcheck:
      test: ["CMD", "haproxy", "-c", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
      interval: 30s
      timeout: 10s
      retries: 3

configs:
  haproxy-config:
    external: true

5.2 Deploy Stack และลำดับการ Start

# ===== ขั้นตอนการ Deploy =====

# 1. ตรวจสอบว่า Swarm พร้อม
docker node ls

# 2. ตรวจสอบว่า Secrets และ Networks พร้อม
docker secret ls
docker network ls | grep overlay

# 3. สร้างไดเรกทอรีบน Host แต่ละ Node
# ทำบนทุก Node ที่จะรัน CockroachDB
for node in 1 2 3 4 5; do
  mkdir -p /data/cockroach/node${node}
  chown 1000:1000 /data/cockroach/node${node}
done

# 4. Deploy Stack
docker stack deploy -c cockroach.yml crdb

# 5. ตรวจสอบ Services
docker stack services crdb
# ต้องเห็น REPLICAS = 1/1 สำหรับทุก Service

# 6. ตรวจสอบ Container Status
docker service ls
docker service ps crdb_cockroach1
docker service ps crdb_cockroach2
# ... และ Service อื่นๆ

# 7. ดู Logs เพื่อตรวจสอบการ Start
docker service logs crdb_cockroach1 --tail 50 -f
# รอจนเห็น: "node starting"

Common Errors และวิธีแก้:

# Error 1: "no such file or directory" สำหรับ Data Directory
# สาเหตุ: ไดเรกทอรีบน Host ยังไม่ได้สร้าง
# แก้: mkdir -p /data/cockroach/node1

# Error 2: Secret "cockroach-node1-crt" not found
# สาเหตุ: ยังไม่ได้ Create Docker Secret
# แก้: docker secret create cockroach-node1-crt /path/to/cert

# Error 3: Network "cockroach-net" not found
# สาเหตุ: ยังไม่ได้สร้าง Overlay Network
# แก้: docker network create --driver overlay --opt encrypted cockroach-net

# Error 4: Certificate SAN mismatch
# สาเหตุ: hostname ใน advertise-addr ไม่ตรงกับ SAN ใน Certificate
# แก้: Regenerate Certificate พร้อม hostname ที่ถูกต้อง

5.3 Cluster Initialization

# ===== Init Cluster — ทำครั้งเดียวเท่านั้น! =====

# รอจนทุก Node Start แล้ว (ตรวจสอบจาก Logs)
# จากนั้น Exec เข้าไปใน Container ของ Node ใดก็ได้

docker exec -it $(docker ps -q -f name=crdb_cockroach1) \
  cockroach init \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257

# ผลลัพธ์ที่ถูกต้อง:
# Cluster successfully initialized

# ===== ตรวจสอบสถานะ =====

# ดู Node Status
docker exec -it $(docker ps -q -f name=crdb_cockroach1) \
  cockroach node status \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257

# ต้องเห็น 5 Nodes ทั้งหมด และ is_live = true

# ดู Cluster Status ผ่าน SQL
docker exec -it $(docker ps -q -f name=crdb_cockroach1) \
  cockroach sql \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257 \
  --execute="SELECT node_id, address, is_live FROM crdb_internal.gossip_liveness;"

5.4 Load Balancer Setup (HAProxy)

5.4.1 HAProxy Configuration

# haproxy.cfg สำหรับ CockroachDB

global
    log stdout format raw local0
    maxconn 10000
    tune.ssl.default-dh-param 2048

defaults
    mode tcp
    log global
    option tcplog
    option dontlognull
    option tcp-check
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    retries 3

# ===== Frontend สำหรับ SQL Client =====
frontend cockroachdb_sql
    bind *:26257
    default_backend cockroachdb_nodes

# ===== Backend CockroachDB Nodes =====
backend cockroachdb_nodes
    balance roundrobin
    option tcp-check

    # Health Check: ใช้ HTTP endpoint /health?ready=1
    # โหนดที่ยัง Bootstrapping หรือ Unhealthy จะถูก Remove ออก
    option httpchk GET /health?ready=1
    http-check expect status 200

    server cockroach1 cockroach1:26257 check port 8080 inter 2000 rise 3 fall 2
    server cockroach2 cockroach2:26257 check port 8080 inter 2000 rise 3 fall 2
    server cockroach3 cockroach3:26257 check port 8080 inter 2000 rise 3 fall 2
    server cockroach4 cockroach4:26257 check port 8080 inter 2000 rise 3 fall 2
    server cockroach5 cockroach5:26257 check port 8080 inter 2000 rise 3 fall 2

# ===== Stats Page =====
frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 30s
    stats auth admin:securepassword
# สร้าง Docker Config สำหรับ HAProxy
docker config create haproxy-config haproxy.cfg

# ตรวจสอบ HAProxy Logs
docker service logs crdb_haproxy -f

# ทดสอบ Connection ผ่าน HAProxy
cockroach sql \
  --certs-dir=/cockroach-certs \
  --host=localhost:26257 \
  --execute="SELECT 1;"

ส่วนที่ 6 — การจัดการผู้ใช้และฐานข้อมูล (User & Database Management)

6.1 การสร้าง Database และ User

6.1.1 สร้าง Database แรกและตั้งค่า

-- เชื่อมต่อด้วย Root Certificate
-- cockroach sql --certs-dir=/cockroach/certs --host=localhost:26257

-- ===== สร้าง Database =====
CREATE DATABASE myapp
  ENCODING = 'UTF8';

-- ตรวจสอบ Database
SHOW DATABASES;

-- ===== สร้าง Schema =====
USE myapp;
CREATE SCHEMA app;
CREATE SCHEMA audit;
CREATE SCHEMA reporting;

-- ===== ตัวอย่างการสร้างตาราง =====
CREATE TABLE app.orders (
  order_id    UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  customer_id UUID        NOT NULL,
  status      STRING      NOT NULL DEFAULT 'pending',
  total_amount DECIMAL(15,2) NOT NULL,
  currency    STRING(3)   NOT NULL DEFAULT 'THB',
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),

  INDEX idx_customer_id (customer_id),
  INDEX idx_status (status),
  INDEX idx_created_at (created_at DESC)
);

CREATE TABLE app.customers (
  customer_id UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  email       STRING      NOT NULL UNIQUE,
  name        STRING      NOT NULL,
  phone       STRING,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- ===== ตรวจสอบ Table Schema =====
SHOW TABLES FROM app;
SHOW CREATE TABLE app.orders;

6.1.2 สร้าง Role และ User

-- สร้าง Role ตามหน้าที่
CREATE ROLE app_readonly;
CREATE ROLE app_readwrite;
CREATE ROLE app_admin;

-- Grant Privileges ให้ Role
GRANT SELECT ON DATABASE myapp TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO app_readonly;

GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE myapp TO app_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_readwrite;

GRANT ALL ON DATABASE myapp TO app_admin;

-- สร้าง Users
CREATE USER service_account WITH PASSWORD 'Str0ng!P@ssw0rd#2026';
GRANT app_readwrite TO service_account;

CREATE USER reporting_bot WITH PASSWORD 'R3p0rt!ng@2026';
GRANT app_readonly TO reporting_bot;

CREATE USER dba_user WITH PASSWORD 'DB@Adm1n!2026';
GRANT app_admin TO dba_user;

-- ตรวจสอบ
SHOW GRANTS ON DATABASE myapp;
SHOW ROLES;

6.2 Connection String

6.2.1 รูปแบบ Connection URL

# Format มาตรฐาน
postgresql://{user}:{password}@{host}:{port}/{database}?sslmode=verify-full&sslrootcert={ca.crt}&sslcert={client.crt}&sslkey={client.key}

# ตัวอย่าง — Secure Mode (แนะนำ)
postgresql://service_account@haproxy:26257/myapp?sslmode=verify-full&sslrootcert=/certs/ca.crt

# ตัวอย่าง — ใช้ Client Certificate Authentication
postgresql://root@localhost:26257/myapp?sslmode=verify-full&sslrootcert=/certs/ca.crt&sslcert=/certs/client.root.crt&sslkey=/certs/client.root.key

6.2.2 Connection ด้วย cockroach sql

# Secure Connection พร้อม Certificate
cockroach sql \
  --certs-dir=/cockroach-certs \
  --host=haproxy:26257 \
  --user=root \
  --database=myapp

# Insecure (Development Only — ไม่แนะนำ Production)
cockroach sql \
  --insecure \
  --host=localhost:26257

6.2.3 Connection Pooling ด้วย PgBouncer

ทำไมต้องใช้ Connection Pooling:

# pgbouncer.ini
[databases]
myapp = host=haproxy port=26257 dbname=myapp sslmode=verify-full \
        sslrootcert=/etc/pgbouncer/ca.crt \
        sslcert=/etc/pgbouncer/client.crt \
        sslkey=/etc/pgbouncer/client.key

[pgbouncer]
listen_port = 5432
listen_addr = 0.0.0.0
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
server_lifetime = 300
server_idle_timeout = 30
log_connections = 1
log_disconnections = 1
# สร้าง userlist.txt
echo '"service_account" "md5hash_of_password"' > /etc/pgbouncer/userlist.txt
# หรือใช้ scram-sha-256 format

ส่วนที่ 7 — การดูแลและบริหารระบบ (Operations)

7.1 Admin UI Dashboard

7.1.1 การเข้าถึง Admin UI

# Admin UI อยู่ที่ port 8080 ของทุก Node
# เข้าผ่าน HTTPS
https://server-01:8080

# หรือผ่าน Port ที่ Published
https://localhost:8081

# Login ด้วย:
# Username: root (หรือ User ที่มี admin Role)
# Certificate หรือ Password ที่ตั้งไว้

7.1.2 Metrics ที่สำคัญใน Admin UI

Overview Dashboard:

Metrics ที่ต้อง Monitor:

Metric ค่าปกติ ค่าที่ต้อง Alert
Under-replicated ranges 0 > 0 นาน > 5 นาที
Node liveness All alive Any node dead
P99 SQL Latency < 100ms > 500ms
QPS ตาม Workload Drop > 50%
Clock offset < 100ms > 400ms
Disk usage < 70% > 85%

7.2 การทดสอบ Fault Tolerance

7.2.1 จำลองโหนดล่ม

# ===== Test 1: Drain โหนดออก (Graceful) =====
# จำลองการทำ Maintenance

# Mark Node เป็น Drain
docker node update --availability drain server-05

# สังเกตใน Admin UI:
# - Container บน Node 5 จะถูก Reschedule
# - CockroachDB Node 5 จะรายงานว่า Suspect แล้ว Dead
# - Range Re-replication จะเริ่มทำงาน

# ===== Test 2: Kill Container โดยตรง (Abrupt) =====
docker service scale crdb_cockroach5=0

# สังเกตใน Terminal:
watch -n2 'docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach node status \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257'

# ===== Test 3: Network Partition Simulation =====
# บน server-05: Block ทราฟฟิก CockroachDB
iptables -A INPUT -p tcp --dport 26257 -j DROP
iptables -A OUTPUT -p tcp --dport 26257 -j DROP

# สังเกต: Node 5 กลายเป็น Suspect ใน ~5 วินาที
# สังเกต: Cluster ยังทำงานปกติด้วย 4 Node

# คืนสภาพ
iptables -D INPUT -p tcp --dport 26257 -j DROP
iptables -D OUTPUT -p tcp --dport 26257 -j DROP

7.2.2 การกู้คืน: Auto-rejoin

# นำโหนดกลับมา
docker service scale crdb_cockroach5=1

# สังเกตการ Rejoin:
docker service logs crdb_cockroach5 --tail 20 -f
# เห็น: "node joined cluster"

# ตรวจสอบ Replication Recovery
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach node status \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257

# คืน Availability
docker node update --availability active server-05

7.3 Backup & Restore

7.3.1 Backup ไปยัง Local Storage

-- Backup Database ทั้งหมดไปยัง Local Storage
BACKUP DATABASE myapp
  INTO 'nodelocal://1/backup/myapp'
  AS OF SYSTEM TIME '-10s';
-- AS OF SYSTEM TIME ช่วยให้ Backup ไม่กระทบ Performance มาก

-- Backup ทั้ง Cluster
BACKUP INTO 'nodelocal://1/backup/full-cluster'
  AS OF SYSTEM TIME '-10s';

-- Incremental Backup (ต้องมี Full Backup ก่อน)
BACKUP DATABASE myapp
  INTO LATEST IN 'nodelocal://1/backup/myapp'
  AS OF SYSTEM TIME '-10s';

-- ดู Backup ที่มีอยู่
SHOW BACKUPS IN 'nodelocal://1/backup/myapp';

7.3.2 Backup ไปยัง S3 / Object Storage

-- Backup ไปยัง S3
BACKUP DATABASE myapp
  INTO 's3://my-bucket/cockroachdb/myapp?AUTH=specified&AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy'
  AS OF SYSTEM TIME '-10s';

-- หรือใช้ IAM Role (ถ้ารันบน AWS EC2)
BACKUP DATABASE myapp
  INTO 's3://my-bucket/cockroachdb/myapp?AUTH=implicit'
  AS OF SYSTEM TIME '-10s';

-- MinIO (Self-hosted S3-compatible)
BACKUP DATABASE myapp
  INTO 's3://my-bucket/cockroachdb/myapp?AWS_ENDPOINT=https://minio:9000&AUTH=specified&AWS_ACCESS_KEY_ID=minioadmin&AWS_SECRET_ACCESS_KEY=minioadmin&AWS_REGION=us-east-1'
  AS OF SYSTEM TIME '-10s';

7.3.3 RESTORE

-- ตรวจสอบ Backup ก่อน Restore
SHOW BACKUP 's3://my-bucket/cockroachdb/myapp/2026/03/18-000000';

-- Restore Database
RESTORE DATABASE myapp
  FROM LATEST IN 's3://my-bucket/cockroachdb/myapp'
  WITH new_db_name = 'myapp_restored';

-- Restore Table เดียว
RESTORE TABLE myapp.app.orders
  FROM LATEST IN 's3://my-bucket/cockroachdb/myapp'
  WITH into_db = 'myapp_restored';

-- Restore ณ เวลาที่กำหนด (Point-in-time Recovery)
RESTORE DATABASE myapp
  FROM 's3://my-bucket/cockroachdb/myapp'
  AS OF SYSTEM TIME '2026-03-18 10:00:00';

7.3.4 Backup Script อัตโนมัติ

#!/bin/bash
# /usr/local/bin/cockroach-backup.sh

COCKROACH_HOST="localhost"
COCKROACH_PORT="26257"
CERTS_DIR="/cockroach-certs"
S3_BUCKET="s3://my-backup-bucket/cockroachdb"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/cockroach-backup.log"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE; }

log "Starting CockroachDB backup..."

# Full Backup ทุก Sunday
if [ "$(date +%u)" -eq 7 ]; then
  log "Running FULL backup..."
  docker exec $(docker ps -q -f name=crdb_cockroach1) \
    cockroach sql \
    --certs-dir=$CERTS_DIR \
    --host=$COCKROACH_HOST:$COCKROACH_PORT \
    --execute="BACKUP INTO '${S3_BUCKET}/full-${DATE}' AS OF SYSTEM TIME '-30s';"
  log "Full backup completed: ${S3_BUCKET}/full-${DATE}"
else
  # Incremental Backup ทุกวัน
  log "Running INCREMENTAL backup..."
  docker exec $(docker ps -q -f name=crdb_cockroach1) \
    cockroach sql \
    --certs-dir=$CERTS_DIR \
    --host=$COCKROACH_HOST:$COCKROACH_PORT \
    --execute="BACKUP INTO LATEST IN '${S3_BUCKET}' AS OF SYSTEM TIME '-30s';"
  log "Incremental backup completed."
fi

log "Backup process finished."

# Crontab: รันทุกวัน 02:00
# 0 2 * * * /usr/local/bin/cockroach-backup.sh

7.4 Rolling Upgrade (Zero-downtime)

7.4.1 ขั้นตอนการอัปเกรด

# ===== ก่อน Upgrade =====

# 1. ตรวจสอบ Version ปัจจุบัน
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach version \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257

# 2. Backup ก่อน Upgrade!
# (ทำ Backup เหมือนหัวข้อ 7.3)

# 3. Set Downgrade Option (ป้องกัน Auto-finalize)
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach sql \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257 \
  --execute="SET CLUSTER SETTING cluster.preserve_downgrade_option = '24.1';"

# ===== Upgrade ทีละโหนด =====

# 4. อัปเดต Image ใน docker-compose.yml
# เปลี่ยน: cockroachdb/cockroach:v24.1.0
# เป็น: cockroachdb/cockroach:v24.2.0

# 5. Update Service ทีละ Service
docker service update \
  --image cockroachdb/cockroach:v24.2.0 \
  --update-parallelism 1 \
  --update-delay 30s \
  crdb_cockroach1

# รอ Service อัปเดตเสร็จและ Healthy
watch -n5 'docker service ps crdb_cockroach1'

# 6. ทำซ้ำสำหรับ Node 2, 3, 4, 5
docker service update --image cockroachdb/cockroach:v24.2.0 crdb_cockroach2
# รอ...
docker service update --image cockroachdb/cockroach:v24.2.0 crdb_cockroach3
# รอ...
# ทำจนครบทุก Node

# ===== หลัง Upgrade =====

# 7. Finalize Version (หลัง Upgrade ครบทุกโหนด)
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach sql \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257 \
  --execute="RESET CLUSTER SETTING cluster.preserve_downgrade_option;"

# 8. ตรวจสอบ Version
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach sql \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257 \
  --execute="SELECT version();"

7.5 Troubleshooting & Common Pitfalls

อาการ สาเหตุที่พบบ่อย วิธีแก้
clock synchronization error NTP ไม่ Sync chronyc tracking, ตรวจสอบ Firewall ไปยัง NTP Server
Under-replicated ranges ค้างนาน โหนดล่มเกิน Quorum ตรวจ cockroach node status, Restart Node ที่ Dead
OOM Killed --max-sql-memory สูงเกิน ลด --max-sql-memory, เพิ่ม RAM, ตรวจ Long-running Queries
Certificate error / TLS handshake SAN ไม่ตรง Hostname openssl x509 -text -in node.crt, Regenerate Certificate
Node ไม่ Join Cluster Network ไม่ถึงกัน ตรวจ Firewall Port 26257, Docker Overlay Network
Split brain Network Partition ตรวจ Overlay Network, รอ Network กลับมา
High P99 Latency Hotspot หรือ Index ขาด ตรวจ EXPLAIN ANALYZE, เพิ่ม Index
Connection refused HAProxy ไม่ Health check ผ่าน ตรวจ /health?ready=1 ของแต่ละ Node
# Debug Commands ที่มีประโยชน์

# ตรวจสอบ Range Distribution
cockroach sql --certs-dir=/cockroach/certs --host=cockroach1:26257 \
  --execute="SHOW RANGES FROM TABLE app.orders;"

# ตรวจสอบ Active Queries
cockroach sql --certs-dir=/cockroach/certs --host=cockroach1:26257 \
  --execute="SELECT * FROM [SHOW CLUSTER STATEMENTS] WHERE application_name != '§ internal' ORDER BY start DESC LIMIT 10;"

# ตรวจสอบ Slow Queries
cockroach sql --certs-dir=/cockroach/certs --host=cockroach1:26257 \
  --execute="SELECT query, elapsed, rows FROM crdb_internal.cluster_queries ORDER BY elapsed DESC LIMIT 10;"

# ดู Event Log
cockroach sql --certs-dir=/cockroach/certs --host=cockroach1:26257 \
  --execute="SELECT timestamp, event_type, info FROM system.eventlog ORDER BY timestamp DESC LIMIT 20;"

ส่วนที่ 8 — การปรับแต่งขั้นสูง (Advanced Configuration)

8.1 Memory Tuning

8.1.1 สูตรคำนวณ --cache และ --max-sql-memory

cache = Total RAM × 0.25 1 max-sql-memory = Total RAM × 0.25 1 Total Allocated = cache + max-sql-memory Total RAM × 0.75

ตัวอย่างการคำนวณสำหรับ Server 32GB RAM:

Total RAM = 32 GB

cache = 32 × 0.25 = 8 GB
max-sql-memory = 32 × 0.25 = 8 GB
Total Allocated = 8 + 8 = 16 GB = 50% (✓ ต่ำกว่า 75%)

OS + Docker overhead ≈ 4 GB
CockroachDB Process + Go Runtime ≈ 4 GB
เหลือ Buffer: 32 - 16 - 4 - 4 = 8 GB

Command:
--cache=8GiB
--max-sql-memory=8GiB

ระวัง: ถ้าตั้ง --max-sql-memory สูงเกินไปและมี Query ที่ใช้ Memory มาก (เช่น ORDER BY บน Dataset ใหญ่) อาจทำให้ Container ถูก OOM Kill

# ตรวจสอบ Memory Usage ปัจจุบัน
docker stats $(docker ps -q -f name=crdb_cockroach)

# ดู Memory Breakdown ใน CockroachDB
docker exec $(docker ps -q -f name=crdb_cockroach1) \
  cockroach sql --certs-dir=/cockroach/certs --host=cockroach1:26257 \
  --execute="SELECT * FROM crdb_internal.node_memory_metrics;"

8.2 Locality และ Multi-region

8.2.1 --locality Flag

# ตัวอย่าง Locality สำหรับ Single Region Multi-Zone
--locality=region=th-central,zone=bkk-az1,rack=rack-01

# ตัวอย่าง Locality สำหรับ Multi-Region
--locality=region=ap-southeast-1,zone=ap-southeast-1a  # Singapore
--locality=region=ap-southeast-7,zone=ap-southeast-7a  # Thailand
--locality=region=ap-east-1,zone=ap-east-1a            # Hong Kong

8.2.2 Survival Goals (CockroachDB Core)

-- ดู Survival Goal ของ Database
SHOW SURVIVAL GOAL FOR DATABASE myapp;

-- ตั้งค่า Zone Survival (ทน Zone ล่มได้)
-- ต้องมี 3 Zone ใน Cluster
ALTER DATABASE myapp SURVIVE ZONE FAILURE;

-- ตั้งค่า Region Survival (ทน Region ล่มได้)
-- ต้องมี 3 Region ใน Cluster
ALTER DATABASE myapp SURVIVE REGION FAILURE;

8.2.3 Multi-region Table Types

-- Regional Table: ข้อมูลอยู่ใน Region เดียว (Low Latency Read/Write)
ALTER TABLE app.orders SET LOCALITY REGIONAL BY TABLE IN "ap-southeast-7";

-- Regional By Row: แต่ละ Row อยู่ใน Region ของมัน
ALTER TABLE app.customers ADD COLUMN region crdb_internal_region
  NOT NULL DEFAULT default_to_database_primary_region(gateway_region())::crdb_internal_region;
ALTER TABLE app.customers SET LOCALITY REGIONAL BY ROW AS region;

-- Global Table: ทุก Row ถูก Replicate ไปทุก Region (Read ที่ไหนก็ Fast)
ALTER TABLE app.config SET LOCALITY GLOBAL;

8.3 Logging Configuration

8.3.1 Log Configuration File

# log.yaml — วางใน /cockroach/log.yaml
sinks:
  stderr:
    channels: [DEV, OPS, HEALTH]
    filter: WARNING
    format: crdb-v2

  file-groups:
    sql:
      channels: [SQL_SCHEMA, SQL_EXEC, SQL_PERF]
      dir: /cockroach/logs
      max-file-size: 100MiB
      max-group-size: 1GiB
      filter: INFO
      format: json

    ops:
      channels: [OPS, HEALTH, STORAGE]
      dir: /cockroach/logs
      max-file-size: 50MiB
      max-group-size: 500MiB
      filter: WARNING
      format: json

    dev:
      channels: [DEV]
      dir: /cockroach/logs
      max-file-size: 50MiB
      filter: WARNING
      format: crdb-v2

8.3.2 ส่ง Log ไปยัง Loki

# promtail-config.yml สำหรับส่ง Log ไปยัง Loki
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: cockroachdb
    static_configs:
      - targets:
          - localhost
        labels:
          job: cockroachdb
          service: crdb
          __path__: /cockroach/logs/*.json

    pipeline_stages:
      - json:
          expressions:
            timestamp: timestamp
            severity: sev
            channel: channel
            message: message
            node_id: node_id
      - labels:
          severity:
          channel:
          node_id:

ส่วนที่ 9 — Observability Stack (Monitoring)

9.1 Prometheus & Grafana Integration

9.1.1 Prometheus Configuration

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: cockroachdb-prod
    environment: production

rule_files:
  - /etc/prometheus/rules/*.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

scrape_configs:
  # ===== CockroachDB Metrics =====
  - job_name: 'cockroachdb'
    scheme: https
    tls_config:
      ca_file: /etc/prometheus/certs/ca.crt
      cert_file: /etc/prometheus/certs/client.prometheus.crt
      key_file: /etc/prometheus/certs/client.prometheus.key
      insecure_skip_verify: false

    static_configs:
      - targets:
          - 'cockroach1:8080'
          - 'cockroach2:8080'
          - 'cockroach3:8080'
          - 'cockroach4:8080'
          - 'cockroach5:8080'
        labels:
          cluster: cockroachdb-prod

    metrics_path: '/_status/vars'

    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        regex: '([^:]+).*'
        replacement: '${1}'

  # ===== HAProxy Metrics =====
  - job_name: 'haproxy'
    static_configs:
      - targets: ['haproxy:8404']
    metrics_path: '/stats;csv'

9.1.2 Grafana Dashboard

Import Dashboard จาก Grafana.com:

Metrics ที่ต้องมีใน Dashboard:

# Key Metrics Queries

# Node Liveness
cockroachdb_liveness_livenesscount{job="cockroachdb"}

# Under-replicated Ranges
cockroachdb_ranges_underreplicated{job="cockroachdb"}

# SQL QPS
rate(cockroachdb_sql_select_count[5m]) + rate(cockroachdb_sql_insert_count[5m])

# P99 Query Latency
histogram_quantile(0.99, rate(cockroachdb_sql_service_latency_bucket[5m])) * 1e-9

# Clock Offset
cockroachdb_clock_offset_meannanos / 1e9

# Storage Used
cockroachdb_capacity_used{job="cockroachdb"} / cockroachdb_capacity{job="cockroachdb"}

9.2 Alerting Rules

9.2.1 Prometheus Alert Rules

# cockroachdb-alerts.yml
groups:
  - name: cockroachdb.critical
    rules:
      # Node Down
      - alert: CockroachDBNodeDown
        expr: absent(cockroachdb_liveness_livenesscount) or cockroachdb_liveness_livenesscount < 5
        for: 1m
        labels:
          severity: critical
          team: database
        annotations:
          summary: "CockroachDB node is down"
          description: "One or more CockroachDB nodes are not responding. Current live nodes: {{ $value }}"
          runbook: "https://wiki.internal/runbooks/cockroachdb-node-down"

      # Under-replicated Ranges
      - alert: CockroachDBUnderReplicatedRanges
        expr: cockroachdb_ranges_underreplicated > 0
        for: 5m
        labels:
          severity: warning
          team: database
        annotations:
          summary: "CockroachDB has under-replicated ranges"
          description: "{{ $value }} ranges are under-replicated on {{ $labels.instance }}"
          runbook: "https://wiki.internal/runbooks/cockroachdb-under-replicated"

      # Clock Skew
      - alert: CockroachDBClockSkew
        expr: abs(cockroachdb_clock_offset_meannanos) > 400000000
        for: 1m
        labels:
          severity: critical
          team: database
        annotations:
          summary: "CockroachDB clock skew is high"
          description: "Clock offset on {{ $labels.instance }} is {{ $value | humanizeDuration }}"
          runbook: "https://wiki.internal/runbooks/cockroachdb-clock-skew"

  - name: cockroachdb.performance
    rules:
      # High P99 Latency
      - alert: CockroachDBHighLatency
        expr: |
          histogram_quantile(0.99,
            rate(cockroachdb_sql_service_latency_bucket[5m])
          ) > 0.5
        for: 10m
        labels:
          severity: warning
          team: database
        annotations:
          summary: "CockroachDB P99 latency is high"
          description: "P99 SQL latency on {{ $labels.instance }} is {{ $value | humanizeDuration }}"

      # Low QPS (possible outage)
      - alert: CockroachDBLowQPS
        expr: |
          sum(rate(cockroachdb_sql_query_started_count[5m])) < 10
        for: 5m
        labels:
          severity: warning
          team: database
        annotations:
          summary: "CockroachDB QPS is suspiciously low"
          description: "QPS is {{ $value }}, which may indicate an outage or traffic drop"

  - name: cockroachdb.storage
    rules:
      # Disk Usage High
      - alert: CockroachDBDiskUsageHigh
        expr: |
          (cockroachdb_capacity_used / cockroachdb_capacity) * 100 > 85
        for: 10m
        labels:
          severity: warning
          team: database
        annotations:
          summary: "CockroachDB disk usage is high"
          description: "Disk usage on {{ $labels.instance }} is {{ $value | humanize }}%"

      # Low Available Disk
      - alert: CockroachDBDiskAlmostFull
        expr: |
          (cockroachdb_capacity_used / cockroachdb_capacity) * 100 > 95
        for: 5m
        labels:
          severity: critical
          team: database
        annotations:
          summary: "CockroachDB disk is almost full!"
          description: "Disk usage on {{ $labels.instance }} is {{ $value | humanize }}%"

9.2.2 Alertmanager Configuration

# alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'

route:
  group_by: ['alertname', 'cluster']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  receiver: 'database-team'

  routes:
    - match:
        severity: critical
      receiver: 'database-oncall'
      repeat_interval: 1h

    - match:
        severity: warning
      receiver: 'database-team'

receivers:
  - name: 'database-oncall'
    pagerduty_configs:
      - routing_key: 'xxx'
    slack_configs:
      - channel: '#db-alerts-critical'
        title: '🚨 CRITICAL: {{ .CommonAnnotations.summary }}'
        text: '{{ .CommonAnnotations.description }}'

  - name: 'database-team'
    slack_configs:
      - channel: '#db-alerts'
        title: '⚠️ WARNING: {{ .CommonAnnotations.summary }}'
        text: '{{ .CommonAnnotations.description }}\nRunbook: {{ .CommonAnnotations.runbook }}'

ส่วนที่ 10 — บทสรุปและข้อแนะนำ (Conclusion)

10.1 เมื่อไหร่ควรใช้ CockroachDB

10.1.1 Use Case ที่เหมาะสม

✅ เหมาะมาก:

  1. Financial Transactions — ระบบที่ต้องการ ACID Transaction ข้ามตาราง เช่น โอนเงิน, หักบัญชี ที่ไม่สามารถยอมรับ Inconsistency ได้แม้ชั่วคราว
  2. Multi-region Applications — App ที่มีผู้ใช้หลายประเทศและต้องการ Low Latency ในทุก Region พร้อมกัน
  3. High Availability Requirements — ระบบที่ต้องการ Uptime > 99.99% และไม่ยอมรับ Planned Downtime สำหรับ Maintenance
  4. Microservices with Shared Database — เมื่อหลาย Service ต้องทำ Transaction ข้าม Service Boundary
  5. E-commerce Inventory — ระบบที่ต้องการ Strong Consistency ในการจัดการ Stock

ตัวอย่าง Use Case จริง:

-- Financial Transfer — ต้องการ ACID Transaction
BEGIN;
  UPDATE accounts SET balance = balance - 1000
    WHERE account_id = 'acc-001' AND balance >= 1000;

  UPDATE accounts SET balance = balance + 1000
    WHERE account_id = 'acc-002';

  INSERT INTO transactions (from_account, to_account, amount, timestamp)
    VALUES ('acc-001', 'acc-002', 1000, now());
COMMIT;
-- ถ้า Node ล่มระหว่าง Transaction → Auto-rollback → ข้อมูลสมบูรณ์เสมอ

10.1.2 Use Case ที่อาจเกินความจำเป็น

❌ อาจไม่คุ้มค่า:


10.2 Best Practices Checklist

10.2.1 Pre-production Checklist ก่อน Go-live

Infrastructure:
□ NTP/Chrony sync ครบทุกโหนด (offset < 200ms)
□ Kernel parameters ตั้งค่าแล้ว (file descriptors, vm.max_map_count)
□ Transparent HugePages ปิดแล้ว
□ Disk Benchmark ผ่าน (IOPS > 2000 สำหรับ Production)
□ Network Bandwidth ทดสอบระหว่าง Nodes (> 1 Gbps)
□ Firewall Rules ตั้งค่าครบ

Security:
□ TLS Certificates สร้างครบทุกโหนด (CA, Node, Client)
□ Certificate SAN ครอบคลุมทุก Hostname/IP
□ Docker Secrets สร้างและ Mount ถูกต้อง
□ Certificate Expiry Monitoring ตั้งค่าแล้ว
□ Database Users แยกตาม Role (Least Privilege)
□ Admin UI ไม่ Expose สู่ Internet โดยตรง

High Availability:
□ 5 Nodes (ขั้นต่ำ) พร้อม Locality Labels
□ Nodes กระจายข้าม Failure Domains
□ Load Balancer ตั้งค่าพร้อม Health Check
□ Connection Pooling (PgBouncer) ตั้งค่าแล้ว
□ Fault Tolerance ทดสอบแล้ว (Kill 2 Nodes พร้อมกัน)

Backup & Recovery:
□ Backup Strategy กำหนดไว้ (Full + Incremental)
□ Backup ทดสอบ Restore แล้ว
□ Backup ไปยัง External Storage (S3/MinIO)
□ RTO และ RPO กำหนดและทดสอบแล้ว

Monitoring:
□ Prometheus scraping ทุก Node
□ Grafana Dashboard ตั้งค่าแล้ว
□ Alert Rules สำหรับ Critical Metrics
□ Log Aggregation (Loki/ELK) ตั้งค่าแล้ว
□ Runbook สำหรับแต่ละ Alert เขียนแล้ว

10.2.2 Performance Tuning Checklist

Query Performance:
□ ทุก Table มี Primary Key ที่เหมาะสม (หลีกเลี่ยง Sequential Integer)
□ Index ครอบคลุม Common Query Patterns
□ ใช้ EXPLAIN ANALYZE ตรวจสอบ Query Plan
□ ไม่มี Full Table Scan ใน Critical Path

Hardware:
□ ใช้ NVMe SSD (ไม่ใช่ SATA SSD หรือ HDD)
□ RAM เพียงพอสำหรับ Cache + SQL Memory
□ Network Latency ระหว่าง Nodes < 5ms

Memory:
□ --cache = 25% RAM
□ --max-sql-memory = 25% RAM
□ Container Memory Limit = RAM Total × 0.85

Application:
□ ใช้ Connection Pooling
□ หลีกเลี่ยง Long-running Transactions
□ ใช้ Prepared Statements
□ Retry Logic สำหรับ Serialization Errors

10.3 แหล่งข้อมูลและการเรียนรู้ต่อ

10.3.1 Official Resources

10.3.2 Papers ที่น่าอ่าน

Paper เนื้อหา ลิงก์
Spanner (2012) Google's Globally Distributed Database OSDI 2012
Raft (2014) In Search of an Understandable Consensus Algorithm USENIX ATC 2014
Percolator (2010) Large-Scale Incremental Processing (MVCC basis) OSDI 2010
CockroachDB (2020) CockroachDB: The Resilient Geo-Distributed SQL Database SIGMOD 2020
Calvin (2012) Fast Distributed Transactions for Partitioned DB SIGMOD 2012

10.3.3 Tools ที่มีประโยชน์

# cockroach demo — ทดลองใช้ Local Cluster โดยไม่ต้องติดตั้ง
docker run -it --rm cockroachdb/cockroach:v24.1.0 demo

# workload simulator — ทดสอบ Performance
docker exec -it $(docker ps -q -f name=crdb_cockroach1) \
  cockroach workload init tpcc \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257

docker exec -it $(docker ps -q -f name=crdb_cockroach1) \
  cockroach workload run tpcc \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257 \
  --duration=5m

# คำสั่ง Debug ที่มีประโยชน์
cockroach debug zip /tmp/cockroach-debug.zip \
  --certs-dir=/cockroach/certs \
  --host=cockroach1:26257
# ส่งไฟล์นี้ให้ Support หรือ Forum เมื่อต้องการความช่วยเหลือ

สรุป: CockroachDB บน Docker Swarm เป็นทางเลือกที่ยอดเยี่ยมสำหรับ Application ที่ต้องการ Distributed SQL พร้อม High Availability โดยไม่ต้องพึ่งพา Cloud Provider เฉพาะราย กุญแจสำคัญคือการเตรียม Infrastructure ให้ถูกต้อง (โดยเฉพาะ NTP และ Security) และการ Monitor อย่างสม่ำเสมอ เพื่อให้ระบบทำงานได้อย่างมั่นคงในระยะยาว