ใช้เครื่องมือ cmd/index-toolkit รันบน MongoDB ของ environment develop ทำตาม 5 ขั้นตอน:
explain executionStats เก็บ baselineIXSCAN แทน
COLLSCAN — เร็วขึ้น 10-1000 เท่าใน collection ขนาดใหญ่
| Collection / Field | สถานะ | ผล |
|---|---|---|
users.username | ✓ Pass | ไม่มี username ซ้ำ |
users.merchantId | ✓ Pass | ไม่มี merchantId ซ้ำ |
permissions.code | ✓ Pass | ไม่มี permission code ซ้ำ |
payment_providers.code | ✓ Pass | ไม่มี provider code ซ้ำ |
user_roles.{userId, roleId} | ✓ Pass | ไม่มี role assignment ซ้ำ |
| # | Collection | Index Name | Keys | Type | Build Time |
|---|---|---|---|---|---|
| 1 | user_roles | idx_userId | {userId:1} | Standard | 132 ms |
| 2 | user_roles | uniq_userId_roleId | {userId:1, roleId:1} | Unique | 86 ms |
| 3 | users | uniq_username | {username:1} | Unique Partial | 89 ms |
| 4 | users | uniq_merchantId | {merchantId:1} | Unique Partial | 94 ms |
| 5 | users | idx_metaData_memberId | {metaData.memberId:1} | Standard | 121 ms |
| 6 | users | idx_type_isDeleted | {type:1, isDeleted:1} | Compound | 130 ms |
| 7 | permissions | uniq_code | {code:1} | Unique | 104 ms |
| 8 | payment_providers | uniq_code | {code:1} | Unique | 89 ms |
| 9 | settlements | idx_member_createdAt | {memberId:1, createdAt:-1} | Compound | 75 ms |
| 10 | settlements | idx_status_createdAt | {status:1, createdAt:-1} | Compound | 73 ms |
| 11 | settlements | idx_settlementRequestId | {settlementRequestId:1} | Standard | 76 ms |
| 12 | payment_providers_member | idx_member_provider_deletedAt | {memberId:1, paymentProviderId:1, deletedAt:1} | Compound | 104 ms |
| 13 | wallet_adjusts | idx_member_createdAt | {memberId:1, createdAt:-1} | Compound | 76 ms |
| 14 | transactions | idx_transferType_updatedAt | {transferType:1, updatedAt:-1} | Compound | 120 ms |
| 15 | transaction_logs | idx_txnRefId_timestamp | {txnRefId:1, timestamp:-1} | Compound | 353 ms |
Total Build Time: ~1.9 วินาที
| Query | Before | After | การปรับปรุง |
|---|---|---|---|
| users by type+isDeleted (admin/member/staff list) |
17ms · scan 288 docs COLLSCAN |
2ms · scan 32 docs idx_type_isDeleted |
|
| wallet_adjusts by memberId | 15ms · in-memory SORT COLLSCAN |
0ms · ใช้ index idx_member_createdAt |
|
| permissions by code | scan 136 docs COLLSCAN |
scan 1 doc EXPRESS_IXSCAN |
|
| payment_providers by code | scan 14 docs COLLSCAN |
scan 1 doc EXPRESS_IXSCAN |
|
| user_roles by userId (HOT path) |
scan 50 docs COLLSCAN |
scan 50 keys IXSCAN idx_userId |
Same time @ dev scale ใน prod เห็นชัดมาก |
user_roles.idx_userId — ทุก authenticated request ผ่าน Permission Middleware
payment_providers.uniq_code — ทุก payment transaction (4 hot paths)
users.idx_type_isDeleted — admin/member/staff list (3 endpoints) — 88% faster confirmed
wallet_adjusts.idx_member_createdAt — 100% improvement (กำจัด in-memory SORT)
permissions.uniq_code — ป้องกัน duplicate ในระบบ permission
users.uniq_username & uniq_merchantId — benchmark ใช้ dummy valueuser_roles.idx_userId — data dev น้อย (287 docs) ไม่ scale ให้เห็นชัด424pay-viewer (read-only)
ทำเฉพาะ read operations (collStats, aggregate $group, explain) — ไม่มีการสร้าง/แก้ไข index บน production
| Metric | Value |
|---|---|
| Database Name | 424pay_production_db |
| Total Collections | 24 |
| Total Documents | 960,134 |
| Data Size | 738 MB |
| Storage Size (compressed) | 188 MB (75% compression) |
| Total Index Size | 56 MB |
transactions by transferType sort updatedAt desc
ใช้ 204ms ต่อ request และ scan 152,399 documents (100% ของ collection)
Query: transactions.find({transferType: "DEPOSIT"})
.sort({updatedAt: -1}).limit(50)
Stage: SORT (in-memory) ❌
Index used: - (NONE!)
Time: 204 ms
Docs scanned: 152,399 / 152,399 (100%)
ผลกระทบ:
แนวทางแก้: เพิ่ม compound index {transferType: 1, updatedAt: -1} → คาดว่าลดเหลือ ~5ms (40× faster)
| Collection | Docs (Prod) | Docs (Dev) | Data Size | Indexes |
|---|---|---|---|---|
| transaction_logs 🔥 | 781,162 | 112K | 630 MB | 3 |
| transactions 🔥 | 152,399 | 20K | 94 MB | 4 |
| logging | 5,400 | 188 | 1.6 MB | 2 (TTL ✓) |
| permissions | 136 | 136 | 0.04 MB | 1 |
| users | 134 | 285 | 0.21 MB | 1 |
| user_roles | 134 | 287 | 0.02 MB | 1 |
| member_balances | 73 | 83 | 0.01 MB | 2 (unique ✓) |
| payment_providers_member | 70 | 175 | 0.03 MB | 1 |
| roles | 14 | 23 | 0.01 MB | 1 |
| settlements | 13 | 128 | 0.01 MB | 1 |
| payment_providers | 6 | 14 | 0.03 MB | 1 |
| wallet_adjusts | 3 | 14 | <0.01 MB | 1 |
การกระจายของข้อมูล — ใช้ประเมินว่า index จะลด scan ratio ได้แค่ไหน
| Field | Distribution | ผลของ Index |
|---|---|---|
| transferType | DEPOSIT 144.4K (95%) · WITHDRAW 8.0K (5%) · SETTLEMENT 11 | WITHDRAW query → scan ลด 95% |
| state | FAILED 76.5K (50%) · COMPLETE 73.4K (48%) · PENDING 2.5K · CONFLICT 7 | PENDING query → ลด 60× |
| providerGroupCode | BADOOPAY 95.4K (63%) · MAXPAY 32.9K · HUSKYPAY 20.0K · DPAY 3.9K | High cardinality → index ดีมาก |
| isConflict | false 85K · null 67.4K · true 37 | filter true → ลด 4,100×! |
| Field | Distribution |
|---|---|
| action | RESPONSE_TO_MEMBER 152.6K · REQUEST_FROM_MEMBER 152.5K · CALLBACK_FROM_PROVIDER 87.4K |
| txnType | DEPOSIT 576.5K (74%) · PAYMENT_CONFIRM_CALLBACK 158K · WITHDRAW 32.3K |
| Collection | Indexes ที่มี | หมายเหตุ |
|---|---|---|
transactions | idx_member_id, idx_reference_id, idx_txn_id | ⚠️ ขาด idx_updated_at ที่ dev มี |
transaction_logs | idx_request_id, idx_txn_ref_id | OK |
member_balances | unique_member_provider_credential | OK ✓ |
logging | createdAt_1 (TTL 1 ปี) | OK ✓ |
| 9 collections อื่น | มีแค่ _id_ (default) | ❌ ขาด index ทั้งหมด |
| Index | Hot Path | Collection Size | Estimated Impact |
|---|---|---|---|
| transactions.{transferType, updatedAt} | Report page | 152K | 🔥🔥🔥 204ms → ~5ms (40× faster) |
| transaction_logs.{txnRefId, timestamp:-1} | Latest log lookup | 781K | 🔥🔥🔥 ลด in-memory sort 781K docs |
| transactions.{state, updatedAt:-1} | Filter by status | 152K | 🔥🔥 PENDING query ลด 60× |
users.{type, isDeleted} | Admin/staff list | 134 | 🔥 88% faster (collection เล็ก) |
users.username unique | Login | 134 | 🔥🔥 100% scan → 1 doc |
users.merchantId unique | Payment callback | 134 | 🔥🔥🔥 ทุก callback (high freq) |
user_roles.userId | Permission check | 134 | 🔥 cache miss |
payment_providers.code | Payment routing | 6 | impact ต่ำ (collection จิ๋ว) |
permissions.code | Seed | 136 | impact ต่ำ |
transactions.{transferType, updatedAt:-1} → แก้ปัญหา report page ช้า 204mstransaction_logs.{txnRefId, timestamp:-1} → ลด memory pressure จาก in-memory sort 781K docstransactions.{state, updatedAt:-1} → query by status (PENDING, FAILED, etc.)users.username uniqueusers.merchantId unique partialusers.{type, isDeleted}permissions.code uniquepayment_providers.code uniqueuser_roles.{userId, roleId} unique compoundsettlements.{memberId, createdAt}, {status, createdAt}payment_providers_member.{memberId, paymentProviderId, deletedAt}wallet_adjusts.{memberId, createdAt}transaction_logs ไม่มี TTL| ตัวเลข | ค่า |
|---|---|
| Current size | 781K docs (630 MB) |
| Time range | ~7 เดือน (Oct 2025 → May 2026) |
| Growth rate | ~110K docs/เดือน |
| Estimated annual growth | 1.3M docs/ปี = ~1 GB/ปี |
| Storage limit (M30) | 40 GB → ~30-40 ปี (OK) |
| Index size impact | เพิ่มตามขนาด collection → กระทบ RAM working set |
logging collection มี TTL 1 ปี แต่ transaction_logs ไม่มี
ควรตัดสินใจ retention policy + เพิ่ม TTL หรือ archive ไป cold storage
transaction_logsdocs/INDEX_PRODUCTION_RUNBOOK.md สำหรับ runbook รายละเอียดเฉพาะ Atlas M30