วิธี Cache ข้อมูล Ecommerce โดยไม่แสดงราคาหรือสต็อกที่ล้าสมัย

March 8, 2026

การ Caching คือหนึ่งในวิธีที่เร็วที่สุดในการเพิ่มประสิทธิภาพร้านค้าออนไลน์ — และในขณะเดียวกันก็เป็นหนึ่งในวิธีที่ง่ายที่สุดในการทำลายความไว้วางใจของลูกค้า ลองนึกภาพลูกค้าที่กดเพิ่มสินค้าลงตะกร้าในราคา ฿1,290 แต่ถูกเรียกเก็บเงิน ฿1,790 ตอน checkout — โอกาสที่เขาจะกลับมาซื้ออีกครั้งนั้นแทบจะเป็นศูนย์ หรือปุ่ม "เพิ่มลงตะกร้า" บนสินค้าที่หมดสต็อกไปแล้วตั้งแต่สามชั่วโมงก่อน ก็คือ Ticket แจ้งปัญหาที่รอวันเกิดขึ้น

คู่มือนี้จะอธิบายวิธี cache ข้อมูลที่ถูกต้อง ด้วย TTL ที่เหมาะสม และกลยุทธ์การ Invalidation ที่ดี — เพื่อให้ได้ประโยชน์ด้านความเร็วโดยไม่เสียความแม่นยำ

บริบทตลาดไทย: แพลตฟอร์มอย่าง Lazada, Shopee, LINE Shopping และ TikTok Shop ปรับราคาและโปรโมชันแบบ Real-time ตลอดเวลา โดยเฉพาะช่วงแคมเปญ 11.11, 12.12 หรือ Hardsale ของ Shopee ปริมาณ Traffic และการเปลี่ยนแปลงราคาในช่วงนั้นสูงมาก การจัดการ Cache ที่ดีจึงไม่ใช่แค่เรื่อง "ดีถ้ามี" — แต่เป็นสิ่งจำเป็น


ทำไม Ecommerce Caching ถึงแตกต่างจากกรณีอื่น

คู่มือ Caching ส่วนใหญ่มองว่าข้อมูลล้าสมัย (Staleness) เป็นเรื่องที่ยอมรับได้ แต่สำหรับ Ecommerce ข้อมูลบางประเภทห้ามล้าสมัยเด็ดขาด:

  • ราคาสินค้า — เปลี่ยนตามโปรโมชัน กฎภาษี หรืออัตราแลกเปลี่ยน (โดยเฉพาะร้านที่ขายในหลายสกุลเงิน เช่น THB/USD)
  • จำนวนสต็อก — สำคัญมากสำหรับสินค้า Limited Edition หรือช่วง Flash Sale บน Shopee/Lazada
  • โค้ดส่วนลด — หมดอายุหรือถูกใช้ครบจำนวนได้ระหว่าง Session
  • ค่าจัดส่ง — ขึ้นอยู่กับขนส่ง (Kerry, Flash Express, J&T, ไปรษณีย์ไทย) และปลายทาง

ข้อมูลที่รับ Staleness ได้มากกว่า:

  • รูปภาพและรายละเอียดสินค้า
  • โครงสร้างหมวดหมู่และ Navigation
  • รีวิวและคะแนนสินค้า
  • สินค้าแนะนำ

หลักสำคัญคือ ต้องรู้ว่าข้อมูลแต่ละประเภทอยู่ในกลุ่มไหน — แล้วใช้กลยุทธ์ Caching ที่ต่างกันในแต่ละกลุ่ม


ขั้นตอนที่ 1: จำแนกข้อมูลตามระดับความทนต่อความล้าสมัย

ก่อนเขียน Logic Cache ใดๆ ให้สร้างตารางจำแนกข้อมูลสำหรับร้านของคุณ นี่คือ Template เริ่มต้น:

ประเภทข้อมูล ทนต่อ Staleness TTL แนะนำ สิ่งที่ Trigger การ Invalidate
รูปภาพสินค้า สูง 24 ชม.–7 วัน อัปเดตไฟล์ Asset
รายละเอียดสินค้า ปานกลาง 1–4 ชม. แก้ไขเนื้อหา
หมวดหมู่ / Navigation ปานกลาง 1–2 ชม. เปลี่ยนโครงสร้างแคตตาล็อก
ราคาปกติ ต่ำ 5–15 นาที เปลี่ยน Price Rule
ราคาโปรโมชัน ต่ำมาก 1–2 นาที เริ่ม/จบแคมเปญ
จำนวนสต็อก ต่ำมาก 30–60 วินาที มีออเดอร์ / อัปเดตสต็อก
ความถูกต้องของโค้ดส่วนลด ไม่มีเลย ห้าม Cache ตรวจสอบ Live เสมอ
ยอดรวมตะกร้า ไม่มีเลย ห้าม Cache คำนวณ Live เสมอ

หลักการง่ายๆ: ถ้าข้อมูลล้าสมัยแล้วอาจทำให้เกิดความไม่ตรงกันทางการเงิน หรือทำให้ลูกค้าได้รับประสบการณ์ที่ผิดพลาด ห้าม Cache — หรือใช้กลยุทธ์ Write-through พร้อม Immediate Invalidation


ขั้นตอนที่ 2: ใช้สถาปัตยกรรม Cache แบบหลายชั้น

Cache ชั้นเดียวสร้างจุดล้มเหลวเดียวและให้ TTL เดียวกับทุกอย่าง ให้ใช้สามชั้นที่มีหน้าที่ต่างกันแทน:

Request จากลูกค้า
  │
  ▼
┌─────────────────────────┐
│   CDN / Edge Cache      │  ← Static Assets, หน้าหมวดหมู่ที่ Render แล้ว
└─────────────────────────┘
  │ miss
  ▼
┌─────────────────────────┐
│  Application Cache      │  ← ข้อมูลสินค้า, รายการราคา, Snapshot สต็อก
│  (Redis / Memcached)    │
└─────────────────────────┘
  │ miss
  ▼
┌─────────────────────────┐
│   Origin / Database     │  ← Source of Truth: ราคาจริง, สต็อกจริง
└─────────────────────────┘

ชั้นที่ 1: CDN / Edge Cache

Cache HTML ที่ Render แล้วสำหรับหน้าหมวดหมู่และหน้าสินค้าที่ ไม่มี Personalisation ใช้ Surrogate Keys (รองรับโดย Cloudflare, Fastly, AWS CloudFront) เพื่อ Invalidate ทุกหน้าที่อ้างถึงสินค้าชิ้นนั้นได้ทันทีเมื่อมีการเปลี่ยนแปลง

ห้าม Cache ที่ CDN Level:

  • หน้าที่แสดงราคาของผู้ใช้ที่ Login แล้ว (เช่น ราคา Reseller หรือราคา Corporate)
  • หน้าที่แสดงสถานะตะกร้า
  • หน้าที่แสดงสต็อก Real-time ("เหลือสินค้าเพียง 2 ชิ้น!")

ระวัง Header Vary: Cookie ที่ตั้งค่าผิด — อาจทำให้ CDN Cache ถูก Bypass สำหรับผู้ใช้ที่ Login ทั้งหมดโดยไม่ตั้งใจ

ชั้นที่ 2: Application Cache (Redis)

นี่คือ Layer หลักที่ทำงานหนักที่สุด ใช้ Cache Price List ที่ Resolve แล้ว, Inventory Snapshot, Product Attribute Set และทุกอย่างที่ต้อง Join หลาย Table

# ตัวอย่าง: ดึงราคาสินค้าด้วย Cache-Aside Pattern และ TTL สั้น
def get_price(product_id: str, customer_group: str) -> Decimal:
    cache_key = f"price:{product_id}:{customer_group}"
    cached = redis.get(cache_key)

    if cached:
        return Decimal(cached)

    price = db.query_price(product_id, customer_group)
    redis.setex(cache_key, ttl=60, value=str(price))  # TTL 60 วินาที
    return price

ใช้ Namespaced Keys (price:, stock:, product:) เพื่อให้ Flush ข้อมูลทั้งหมวดได้ระหว่าง Bulk Update โดยไม่กระทบ Cache อื่น

ชั้นที่ 3: Origin / Database

ห้าม Cache ที่ Layer นี้ — มันคือ Source of Truth การ Caching ใดๆ ในชั้นนี้ควรให้ Database จัดการเองผ่าน Query Cache หรือ Read Replica ไม่ใช่ Application Logic


ขั้นตอนที่ 3: ใช้ Event-Driven Cache Invalidation

TTL Expiry เป็นแค่ Safety Net ไม่ใช่กลยุทธ์ สำหรับราคาและสต็อก คุณต้องการ Event-Driven Invalidation — ล้าง Cache ทันทีที่ข้อมูลต้นทางเปลี่ยน

Pattern: Publish on Write, Invalidate on Consume

อัปเดตราคาใน ERP / ระบบหลังบ้าน
        │
        ▼
  Message Broker
  (Kafka / AWS SQS / Redis Pub/Sub)
        │
        ▼
  Cache Invalidation Service
        │
        ├── ลบ Redis key: price:{product_id}:*
        └── Purge CDN Surrogate Key: product-{product_id}

วิธีนี้ทำให้ Cache ไม่แสดงราคาเก่าเกินไปกว่าไม่กี่วินาทีหลังจากการเปลี่ยนแปลงถูก Commit — ไม่ว่า TTL จะตั้งไว้เท่าไรก็ตาม

ข้อมูลที่ควรส่งใน Event

Event อัปเดตราคา/สต็อกควรมีบริบทเพียงพอสำหรับการ Invalidate อย่างแม่นยำ:

{
  "event": "price_updated",
  "product_id": "SKU-12345",
  "affected_customer_groups": ["retail", "reseller"],
  "effective_at": "2026-03-08T10:00:00+07:00"
}

Event ที่แม่นยำ = การ Invalidate ที่แม่นยำ หลีกเลี่ยงการ "ล้าง Cache ทั้งหมด" ในช่วง Traffic สูง — มันทำให้เกิดปัญหา Thundering Herd ที่ทุก Cache Miss พุ่งเข้า Database พร้อมกัน


ขั้นตอนที่ 4: ป้องกัน Thundering Herd เมื่อ Cache Miss

เมื่อ Cache Key ยอดนิยมหมดอายุ Request หลายร้อยรายการอาจพุ่งเข้า Database พร้อมกัน นี่คือปัญหา Thundering Herd ที่สามารถล่มระบบได้ในช่วง Flash Sale

วิธีที่ 1: Probabilistic Early Expiry (TTL Jitter)

เพิ่ม Jitter สุ่มให้กับ TTL เพื่อให้ Cache ของสินค้าที่คล้ายกันไม่หมดอายุพร้อมกัน:

import random

BASE_TTL = 60  # วินาที
jitter = random.randint(0, 10)
redis.setex(cache_key, ttl=BASE_TTL + jitter, value=data)

วิธีที่ 2: Request Coalescing (Single-Flight)

ให้แน่ใจว่ามีเพียง Request เดียวที่คำนวณค่าใหม่เมื่อ Cache Miss ส่วนที่เหลือรอ:

# ใช้ Distributed Lock ป้องกันการคำนวณซ้ำพร้อมกัน
lock_key = f"lock:{cache_key}"

if redis.set(lock_key, "1", nx=True, ex=5):  # nx = ตั้งค่าเฉพาะเมื่อยังไม่มี Key นี้
    # Request นี้ได้ Lock — คำนวณและเติม Cache
    value = db.fetch(...)
    redis.setex(cache_key, ttl=60, value=value)
    redis.delete(lock_key)
else:
    # Request อื่นกำลังคำนวณอยู่ — รอสักครู่แล้ว Retry
    time.sleep(0.05)
    value = redis.get(cache_key)

วิธีที่ 3: Stale-While-Revalidate

ส่งค่า Cache เก่าทันทีในขณะที่คำนวณใหม่แบบ Asynchronous ในเบื้องหลัง เหมาะสำหรับรายละเอียดสินค้าและ Navigation — ไม่เหมาะกับราคาที่ต้องการความแม่นยำ


ขั้นตอนที่ 5: ออกแบบ Checkout Path ให้ใช้ข้อมูล Live เสมอ

ไม่ว่าหน้าสินค้าจะ Cache ได้ดีแค่ไหน ขั้นตอน Checkout ต้องใช้ข้อมูล Live สำหรับทุกการคำนวณราคาและสต็อก

สิ่งที่ต้องตรวจสอบ Live ที่ Checkout (ห้ามข้าม):

  1. คำนวณราคาตะกร้าใหม่ เมื่อเริ่ม Checkout — ดึงราคาปัจจุบัน คำนวณโปรโมชันใหม่ คำนวณ VAT 7% ใหม่
  2. จอง Stock ที่ Order Confirmation ไม่ใช่ตอน "เพิ่มลงตะกร้า" — ใช้ช่วงเวลาจอง (เช่น 15 นาที) และปล่อยคืนถ้าไม่ชำระเงิน
  3. ตรวจสอบโค้ดส่วนลด Live ณ จุดที่ใช้ — ตรวจวันหมดอายุ จำนวนที่ใช้ได้ และเงื่อนไขสิทธิ์แบบ Real-time
  4. คำนวณค่าจัดส่งใหม่ ที่ขั้นตอนสุดท้าย — อัตราค่าขนส่ง (Kerry, Flash, J&T, ไปรษณีย์ไทย) อาจเปลี่ยนได้ระหว่าง Session ที่ยาวนาน

Pattern สำหรับการจอง Stock:

ลูกค้ากด "สั่งซื้อ"
        │
        ▼
  พยายามจอง Stock (ลดจำนวนพร้อมตรวจสอบ Floor)
        │
  ┌─────┴──────┐
  │ สำเร็จ     │ ล้มเหลว (stock = 0)
  │            │
  ▼            ▼
ดำเนินการ    คืนค่า "สินค้าหมดแล้ว"
ชำระเงิน     ก่อนที่จะพยายามเรียกเก็บเงิน

ห้ามเรียกเก็บเงินลูกค้าสำหรับสินค้าที่ยังไม่ได้ยืนยันว่ามีอยู่จริง


ขั้นตอนที่ 6: ติดตาม Cache Health ใน Production

ปัญหา Cache คือ Silent Failure เพิ่ม Monitoring เพื่อตรวจพบก่อนลูกค้าเจอ

Metric ที่ต้องติดตาม:

Metric บอกอะไร เกณฑ์แจ้งเตือน
Cache Hit Rate ประสิทธิภาพ Cache โดยรวม < 80% ต้องตรวจสอบ
Stale Read Rate ความถี่ที่ข้อมูล TTL-Expired ถูกส่งออก ติดตาม Trend แจ้งเตือนเมื่อพุ่งขึ้น
Invalidation Lag เวลาระหว่างข้อมูลเปลี่ยนและ Cache ถูกล้าง > 30 วินาทีสำหรับราคาถือว่ามีปัญหา
Cache Eviction Rate Cache เล็กเกินไปหรือ Key ใหญ่เกินไป Eviction สูง = ขยายหรือตัดแต่ง
Price Discrepancy Events ราคาในตะกร้า ≠ ราคา Order จริง ทุกครั้งที่เกิดต้องตรวจสอบ

ตั้ง Canary Check: ทุก 60 วินาที ดึงราคาสินค้าที่รู้จักจากทั้ง Cache และ Database ถ้าต่างกันเกิน Tolerance ที่ยอมรับได้ ให้ส่ง Alert ทันที — เพื่อให้รู้ก่อนลูกค้าเจอปัญหา


ขั้นตอนที่ 7: จัดการ Flash Sale และ Peak Event แบบพิเศษ

Flash Sale ทำลายสมมติฐาน Caching ทั่วไป ราคาเปลี่ยนตามเวลา สต็อกหมดในไม่กี่วินาที และ Traffic พุ่งขึ้น 10–100 เท่า

สำหรับตลาดไทยโดยเฉพาะ: แคมเปญ 11.11, 12.12, Shopee Payday, Lazada Birthday Sale และ Flash Sale บน LINE Shopping เกิดขึ้นบ่อยและมีผู้ใช้งานพร้อมกันจำนวนมาก ต้องเตรียมระบบให้พร้อมล่วงหน้าเสมอ

Pre-warm Cache ก่อน Event เริ่ม — เติมข้อมูลราคาและสินค้าใน Redis ไม่กี่นาทีก่อน Go-live เพื่อให้ Traffic ระลอกแรกไม่โดน Cold Cache

ใช้ Dedicated Inventory Service สำหรับการอัปเดต Stock ที่มีปริมาณสูง Redis Counter พร้อม Atomic Decrement (DECR) ทำงานได้เร็วกว่า Database Row Lock มากภายใต้ภาระงานพร้อมกัน:

# Atomic Stock Decrement — คืนค่าสต็อกที่เหลือหลังจากลด
remaining = redis.decr(f"stock:{product_id}")

if remaining < 0:
    # Oversold — ยกเลิกและปฏิเสธ
    redis.incr(f"stock:{product_id}")
    raise OutOfStockError()

Degrade อย่างสง่างามภายใต้ภาระงาน ถ้า Cache Layer ล่ม Fallback ควรเป็นการตอบสนองแบบเรียบง่าย ("กรุณาตรวจสอบสินค้าที่ Checkout") แทนที่จะให้ Database รับ Request ที่ไม่มี Cache ทั้งหมด


Checklist ก่อน Production

  • [ ] สร้างตารางจำแนกข้อมูลครบ — ทุกประเภทมี TTL และกลยุทธ์ Invalidation แล้ว
  • [ ] สถาปัตยกรรม Cache แบบสามชั้นพร้อม (CDN + Application Cache + Origin)
  • [ ] ใช้ Namespaced Redis Keys สำหรับ Targeted Invalidation
  • [ ] Event-Driven Invalidation สำหรับการเปลี่ยนราคาและสต็อก
  • [ ] ใช้ TTL Jitter ป้องกัน Thundering Herd
  • [ ] ยืนยันว่า Checkout Path ใช้ข้อมูล Live เท่านั้น
  • [ ] ใช้ Stock Reservation Logic ที่ Order Confirmation
  • [ ] ตรวจสอบโค้ดส่วนลด Live เสมอ
  • [ ] ติดตาม Cache Hit Rate, Invalidation Lag และ Price Discrepancy แล้ว
  • [ ] เขียนและทดสอบ Runbook สำหรับ Flash Sale แล้ว

สรุป

Ecommerce Caching ที่ทั้งเร็วและแม่นยำไม่ได้อยู่ที่การเลือกอย่างใดอย่างหนึ่ง — แต่คือการใช้กลยุทธ์ที่ถูกต้องกับข้อมูลที่ถูกต้อง Cache Static Content อย่างก้าวร้าว ใช้ TTL สั้นและ Event-Driven Invalidation สำหรับราคาและสต็อก ห้าม Cache ยอดรวมตะกร้าหรือความถูกต้องของโค้ดส่วนลด และสร้าง Checkout Path ที่ถือว่าข้อมูล Live เป็นสิ่งที่ต้องใช้เสมอ

ทีมที่จัดการเรื่องนี้ได้ถูกต้องไม่ได้แค่มีร้านค้าที่เร็วกว่า — พวกเขามีร้านค้าที่ลูกค้าไว้วางใจ


ต้องการให้ผู้เชี่ยวชาญรีวิว Ecommerce Caching Architecture ของคุณไหม? จองคำปรึกษาฟรีกับ Simplico

Ready to talk about your project?

Share goals and constraints. We'll assemble architects and engineers to move fast with you.

Get in touch