

GCP Pub/Sub Troubleshooting แก้ปัญหา — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog
Google Cloud Pub/Sub เป็นบริการจัดการคิวข้อความ (Message Queue) และการส่งผ่านเหตุการณ์ (Event Streaming) แบบ Real-time ที่ทรงพลังและมีความน่าเชื่อถือสูง อย่างไรก็ตาม ระบบที่ซับซ้อนเช่นนี้ย่อมมีโอกาสเกิดปัญหาที่หลากหลาย ตั้งแต่ข้อความสูญหาย ความล่าช้า ไปจนถึงการจัดการ Subscription ที่ไม่มีประสิทธิภาพ คู่มือฉบับสมบูรณ์นี้จะพาคุณเจาะลึกทุกเทคนิคการ Troubleshooting พร้อม Best Practices และตัวอย่างโค้ดจริงสำหรับปี 2026 เพื่อให้คุณควบคุมระบบ Messaging ของตัวเองได้อย่างมั่นใจ
ทำความเข้าใจสถาปัตยกรรมและแนวคิดหลักของ GCP Pub/Sub
ก่อนจะเริ่มแก้ไขปัญหา การเข้าใจองค์ประกอบพื้นฐานและการไหลของข้อมูลใน Pub/Sub เป็นสิ่งสำคัญที่สุด Pub/Sub ทำงานบนโมเดล Publisher-Subscriber โดยมีองค์ประกอบหลักสามส่วน:
- Topic: ทรัพยากรกลางที่ Publisher ส่งข้อความ (Message) เข้ามา แต่ละข้อความคือข้อมูลที่คุณต้องการส่งผ่าน พร้อมด้วย Attributes สำหรับการกรอง
- Subscription: เป็นตัวแทนของกลุ่ม Subscriber ที่ต้องการรับข้อความจาก Topic นั้นๆ Pub/Sub จะจัดส่งข้อความไปยังทุก Subscription ที่ลิงก์กับ Topic (ยกเว้นใช้ Filter)
- Message: ข้อมูลที่ถูกส่งผ่าน ซึ่งประกอบด้วย Data (payload) และ Attributes (metadata ในรูปแบบ key-value)
การส่งมอบข้อความใน Pub/Sub เป็นแบบ “At-least-once” หมายความว่าข้อความอาจถูกส่งซ้ำได้ (Duplicate) แต่จะไม่สูญหายง่ายๆ นอกจากนี้ Pub/Sub รองรับทั้งการดึงข้อมูลแบบ Push (ส่ง HTTP POST ไปหา endpoint ของคุณ) และ Pull (ให้แอปพลิเคชันของคุณดึงข้อความออกมาเอง)
Flow ของข้อความและสถานะ
ข้อความที่ส่งเข้ามาใน Topic จะถูกเก็บไว้และจัดส่งไปยังแต่ละ Subscription โดยอัตโนมัติ สำหรับแต่ละ Subscription ข้อความจะมีสถานะสำคัญคือ Unacknowledged (ยังไม่ได้รับการยืนยันการประมวลผล) และ Acknowledged (ประมวลผลเสร็จแล้ว) หากข้อความค้างอยู่ในสถานะ Unack เกินระยะเวลาที่กำหนด (Ack Deadline) ข้อความนั้นจะถูกส่งซ้ำอีกครั้ง นี่คือกลไกพื้นฐานที่ทำให้เกิดปัญหาหลายอย่าง
ปัญหาทั่วไปและการวินิจฉัย (Common Issues & Diagnostics)
1. ข้อความสูญหายหรือไม่ถูกส่งไปยัง Subscriber
ปัญหานี้เป็นหนึ่งในปัญหาที่พบบ่อยที่สุด สาเหตุอาจมาจากหลายจุด
- ไม่มี Subscription ที่ทำงาน: ตรวจสอบว่า Topic มี Subscription ที่ Active และไม่ได้ถูกยกเลิกหรือไม่
- Subscription Filter: หาก Subscription ตั้งค่า Filter ไว้ ข้อความที่ไม่มี Attribute ตรงกับเงื่อนไขจะไม่ถูกส่งไปยัง Subscription นั้น ตรวจสอบ Filter Expression ให้ดี
- Permission Issues: บัญชีบริการ (Service Account) ที่ใช้รัน Subscriber อาจไม่มีสิทธิ์
pubsub.subscriptions.consumeบน Subscription นั้น - Backlog สูงมาก: จำนวนข้อความที่ค้างรอ (Backlog) สูงเกินไปจนระบบไม่สามารถส่งทัน อาจต้องดูที่เมตริก
num_undelivered_messages
2. ข้อความถูกส่งซ้ำ (Duplicate Messages)
การส่งซ้ำเป็นพฤติกรรมโดย設計ของ Pub/Sub (At-least-once delivery) แต่หากเกิดบ่อยเกินไปอาจเป็นปัญหา
- Ack Deadline ตันเกินไป: หาก Subscriber ประมวลผลข้อความไม่ทันภายใน Ack Deadline (ค่าเริ่มต้น 10 วินาที) ข้อความจะถูกมองว่า “ล้มเหลว” และถูกส่งซ้ำ
- Subscriber Crash: หาก Subscriber ตายระหว่างประมวลผลและไม่ได้ส่ง Ack กลับไป ข้อความจะถูกส่งใหม่อีกครั้งเมื่อ Subscriber ตัวใหม่เข้ามา
- การ Retry ของ Publisher: Publisher อาจส่งข้อความเดียวกันมากกว่าหนึ่งครั้งเนื่องจาก Network Issues หรือ Timeout โดยไม่ได้ใช้ Message Ordering หรือ Deduplication ID (ในบางเคส)
3. ความล่าช้าในการรับข้อความ (High Latency)
Subscriber ได้รับข้อความช้ากว่าที่คาดไว้ สาเหตุหลักมักอยู่ที่:
- รูปแบบการดึงข้อมูล (Pull vs Push): การใช้ Pull แบบ Synchronous อาจทำให้เกิด latency สูงหากจัดการ Batch ไม่ดี การใช้ Streaming Pull หรือ Push จะมีประสิทธิภาพกว่า
- Subscriber ประมวลผลช้า: เป็น Bottleneck ที่พบบ่อยที่สุด ตรวจสอบ CPU, Memory, I/O ของเครื่อง Subscriber และดูว่าโค้ดมีส่วนที่ Blocking หรือไม่
- เครือข่าย: Latency ระหว่าง Subscriber กับ Google Cloud
4. Backlog สะสมและ Subscription ล้าหลัง
จำนวนข้อความที่ยังส่งไม่สำเร็จ (Undelivered Messages) สะสมมากขึ้นเรื่อยๆ จนน่ากลัว สัญญาณนี้บ่งชี้ว่า Subscriber ทำงานช้ากว่าอัตราการผลิตข้อความของ Publisher มาก
เครื่องมือและเทคนิคการตรวจสอบ (Monitoring & Tools)
Google Cloud มีเครื่องมือชั้นเยี่ยมสำหรับตรวจสุขภาพ Pub/Sub
Cloud Monitoring และเมตริกสำคัญ
เข้าไปที่ Cloud Console > Monitoring > Metrics Explorer แล้วค้นหาเมตริกเหล่านี้:
- subscription/num_undelivered_messages: จำนวนข้อความที่ยังส่งไม่ถึง Subscriber (Backlog) ค่านี้ควรมีแนวโน้มคงที่หรือเป็นศูนย์ หากพุ่งขึ้นแสดงว่ามีปัญหา
- subscription/oldest_unacked_message_age: อายุของข้อความที่เก่าที่สุดที่ยังไม่ได้รับการ Ack ค่านี้บอกว่า Subscriber ล้าหลังขนาดไหน
- subscription/pull_message_operation_count: อัตราการดึงข้อความ ใช้ดูกิจกรรมของ Subscriber
- topic/send_message_operation_count: อัตราการส่งข้อความของ Publisher
Logging และ Cloud Trace
เปิด Cloud Logging และดู Log ของ Pub/Sub ได้โดยตรง คุณสามารถเห็นกิจกรรมการส่ง การรับ การ Ack และข้อผิดพลาดต่างๆ นอกจากนี้ การใช้ Cloud Trace กับ Push Endpoint ช่วยให้เห็นเวลาในการประมวลผลแต่ละข้อความอย่างละเอียด
การใช้ gcloud CLI และ API สำหรับการ Debug
บางครั้งการดูจาก Console ไม่พอ คุณต้องใช้ Command Line
# ดูรายละเอียดของ Subscription รวมถึงจำนวนข้อความค้าง
gcloud pubsub subscriptions describe YOUR_SUBSCRIPTION_ID --format="json(ackDeadlineSeconds,messageRetentionDuration,pushConfig,detached)"
# ดึงข้อความตัวอย่างมาโดยไม่ต้อง Ack (สำหรับ Debug เท่านั้น!)
gcloud pubsub subscriptions pull YOUR_SUBSCRIPTION_ID --auto-ack --limit=5
# ดู Policy IAM ของ Topic หรือ Subscription
gcloud pubsub topics get-iam-policy YOUR_TOPIC_ID
gcloud pubsub subscriptions get-iam-policy YOUR_SUBSCRIPTION_ID
การแก้ไขปัญหาเชิงลึกพร้อมโค้ดตัวอย่าง
1. จัดการ Ack Deadline และ Flow Control อย่างมีประสิทธิภาพ
ปัญหาการส่งซ้ำส่วนใหญ่แก้ได้ด้วยการปรับ Ack Deadline และใช้ Flow Control ใน Client Library
// ตัวอย่าง Java Client Library ด้วย Flow Control
import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.pubsub.v1.PubsubMessage;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.api.gax.batching.FlowControlSettings;
import com.google.api.gax.core.ExecutorProvider;
import com.google.api.gax.core.InstantiatingExecutorProvider;
public class RobustSubscriber {
public static void main(String... args) throws Exception {
String subscriptionId = "projects/your-project/subscriptions/your-sub";
MessageReceiver receiver =
(PubsubMessage message, AckReplyConsumer consumer) -> {
// ประมวลผลข้อความที่นี่
String data = message.getData().toStringUtf8();
System.out.println("Id: " + message.getMessageId() + ", Data: " + data);
// จำลองงานที่ใช้เวลา
try {
processMessage(data);
consumer.ack(); // ยืนยันสำเร็จ
} catch (Exception e) {
consumer.nack(); // บอกให้ส่งใหม่ในภายหลัง
}
};
// ตั้งค่า Flow Control เพื่อป้องกัน Overload
FlowControlSettings flowControlSettings =
FlowControlSettings.newBuilder()
.setMaxOutstandingElementCount(1000L) // จำกัดข้อความที่ประมวลผลพร้อมกัน
.setMaxOutstandingRequestBytes(10L * 1024L * 1024L) // จำกัดขนาดรวม (10MB)
.build();
Subscriber subscriber = Subscriber.newBuilder(subscriptionId, receiver)
.setFlowControlSettings(flowControlSettings)
.setParallelPullCount(2) // ดึงจากหลาย Stream พร้อมกัน
.setAckDeadlineExtensionSeconds(300) // ขยายเวลา Ack อัตโนมัติหากประมวลผลไม่ทัน
.build();
subscriber.startAsync().awaitRunning();
System.out.println("Subscriber เริ่มทำงานแล้ว...");
}
private static void processMessage(String data) throws InterruptedException {
// จำลองงานที่ใช้เวลา
Thread.sleep(1000);
}
}
2. ออกแบบระบบให้ทนต่อการส่งซ้ำ (Idempotent Processing)
เมื่อรับว่าข้อความอาจมาซ้ำได้ Subscriber ของคุณต้องเป็น Idempotent (ประมวลผลข้อความเดียวกันหลายครั้งได้โดยให้ผลลัพธ์เหมือนเดิม)
# ตัวอย่าง Python: ใช้ Database เป็นที่เก็บสถานะเพื่อป้องกันการประมวลผลซ้ำ
from google.cloud import pubsub_v1
from google.api_core import retry
import sqlalchemy
import hashlib
# เชื่อมต่อ Database
engine = sqlalchemy.create_engine('postgresql://user:pass@localhost/db')
metadata = sqlalchemy.MetaData()
processed_messages = sqlalchemy.Table('processed_messages', metadata,
sqlalchemy.Column('message_id', sqlalchemy.String, primary_key=True),
sqlalchemy.Column('processed_at', sqlalchemy.DateTime)
)
def callback(message):
message_id = message.message_id
data = message.data.decode('utf-8')
# ตรวจสอบว่าเคยประมวลผลข้อความนี้แล้วหรือไม่
with engine.connect() as conn:
query = sqlalchemy.select([sqlalchemy.exists().where(processed_messages.c.message_id == message_id)])
already_processed = conn.execute(query).scalar()
if already_processed:
print(f"ข้ามข้อความซ้ำ ID: {message_id}")
message.ack() # Ack เพื่อนำออกจากคิว
return
# ประมวลผลหลัก
try:
result = business_logic(data)
# บันทึกว่าเสร็จแล้ว
ins = processed_messages.insert().values(message_id=message_id, processed_at=sqlalchemy.func.now())
conn.execute(ins)
message.ack()
print(f"ประมวลผลสำเร็จ: {message_id}")
except Exception as e:
print(f"ล้มเหลว: {e}")
message.nack() # ส่งคำขอให้ลองใหม่
subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path('your-project', 'your-sub')
flow_control = pubsub_v1.types.FlowControl(max_messages=100)
streaming_pull_future = subscriber.subscribe(subscription_path, callback=callback, flow_control=flow_control)
try:
streaming_pull_future.result()
except KeyboardInterrupt:
streaming_pull_future.cancel()
3. การจัดการ Dead Letter Topics (DLQ)
สำหรับข้อความที่ล้มเหลวซ้ำๆ หลายครั้งเกินขีดจำกัด ควรโยนออกไปยัง Dead Letter Topic เพื่อไม่ให้ขัดขวางการประมวลผลข้อความปกติ
| วิธีการ | ข้อดี | ข้อเสีย | เหมาะสำหรับ |
|---|---|---|---|
| Nack และ Retry ในตัว | ง่าย, ไม่ต้องตั้งค่าเพิ่ม | อาจเกิด Infinite Loop, รบกวนข้อความอื่น | งานที่ล้มเหลวชั่วคราว (Network Glitch) |
| Dead Letter Topic (DLQ) | แยกข้อความปัญหา, วิเคราะห์แยก, ไม่รบกวน Flow หลัก | ต้องมีระบบจัดการ DLQ เพิ่ม | ข้อความที่ผิดรูปแบบ (Poison Pill), งานที่ล้มเหลวถาวร |
| ปรับ Ack Deadline ยาวๆ | ลดการส่งซ้ำ | ใช้ทรัพยากรเก็บข้อความนานขึ้น, ปิดบังปัญหา | งานประมวลผลใช้เวลานานแต่เสถียร |
Best Practices และการออกแบบเพื่อป้องกันปัญหา
การออกแบบ Topic และ Subscription
- หนึ่ง Topic ต่อประเภทเหตุการณ์: เช่น
orders.created,payments.processedอย่าผสมเหตุการณ์ที่ไม่เกี่ยวข้องใน Topic เดียว - ใช้ Subscription Filter แทนการสร้างหลาย Topic: หากข้อความคล้ายกันแต่ต้องการส่งให้คนละกลุ่ม ให้ใช้ Attribute และ Filter จะจัดการง่ายกว่า
- ตั้งค่า Message Retention Period ให้เหมาะสม: ค่าเริ่มต้นคือ 7 วัน หากต้องการ Debug ย้อนหลังได้นานๆ อาจเพิ่มเป็น 10 วัน แต่จะใช้พื้นที่มากขึ้น
การพัฒนา Publisher และ Subscriber ที่แข็งแกร่ง
- Publisher: ใช้ Retry Logic, กำหนด Ordering Key หากต้องการลำดับ, ใส่ Timestamp ใน Attributes
- Subscriber: ออกแบบให้ Idempotent เสมอ, ใช้ Flow Control, ตรวจสอบสุขภาพด้วย Liveness Probe, Log Message ID และ Delivery Attempt
- จัดการ Connection: ใช้ Connection Pooling, รีไซเคิล Subscriber Client เป็นระยะ
การ Monitor และ Alert ที่ควรตั้งค่า
ตั้ง Alert Policy ใน Cloud Monitoring สำหรับสถานการณ์ต่อไปนี้:
- Backlog สูงผิดปกติ: เมื่อ
num_undelivered_messagesเกิน阈值 (Threshold) ที่กำหนดเป็นเวลานาน (เช่น เกิน 10,000 ข้อความ นานกว่า 10 นาที) - ข้อความเก่าค้าง: เมื่อ
oldest_unacked_message_ageเกิน 5 นาที (ขึ้นอยู่กับ use case) - Error Rate สูง: เมื่ออัตราข้อความที่ Nack หรือส่งไป DLQ สูงเกิน 5%
กรณีศึกษาและตัวอย่างการประยุกต์ใช้จริง
กรณีศึกษา 1: E-commerce Platform – Order Processing Pipeline
ปัญหา: ในช่วง Peak เช่น 12.12 ระบบสั่งซื้อได้รับ Order นับหมื่นต่อนาที Subscriber ที่จัดการ Stock หยุดทำงานเป็นช่วงๆ ทำให้เกิด Backlog สูงและ Order ค้าง
การแก้ไข:
- ปรับจาก Pull แบบ Synchronous เป็น Streaming Pull พร้อมปรับ Parallel Pull Count = 4
- ตั้ง Flow Control ให้เหมาะสมกับกำลังประมวลผลของระบบ Inventory
- เพิ่ม Dead Letter Topic สำหรับ Order ที่ประมวลผลล้มเหลวเกิน 5 ครั้ง เพื่อทีม Support ตรวจสอบแยก
- ตั้ง Auto-scaling ให้ Subscriber โดยใช้เมตริก
num_undelivered_messagesเป็นตัว trigger
ผลลัพธ์: Backlog ลดลงและคงที่ แม้ใน Peak Time ระบบ Inventory ไม่ล่ม และทีมสามารถแยกแยะปัญหา Order ที่ผิดปกติได้ง่ายขึ้น
กรณีศึกษา 2: Real-time Analytics – IoT Data Stream
ปัญหา: อุปกรณ์ IoT หลายแสนชิ้นส่งข้อมูล Sensor ขึ้น Cloud ผ่าน Pub/Sub บางครั้งข้อมูลมาช้าไม่ Real-time และมีข้อมูลสูญหาย
การแก้ไข:
- ใช้ Regional Topic (แทน Global) ใน Region ที่ใกล้กับอุปกรณ์ที่สุด เพื่อลด Latency
- Publisher (บนอุปกรณ์หรือ Gateway) ใช้ Exponential Backoff ในการ Retry
- Subscriber ใช้ Batch Processing (รับข้อความเป็นชุด) เพื่อเพิ่ม Throughput ในการเขียนลง BigQuery
- เพิ่ม Attribute
device_idและsent_timestampในทุกข้อความ เพื่อใช้ในการ Debug และติดตาม Latency
ผลลัพธ์: Latency ลดลงจากเฉลี่ย 15 วินาที เหลือ 2 วินาที อัตราข้อมูลสูญหายน้อยกว่า 0.001%
| อาการที่พบ | สาเหตุที่น่าจะเป็น | การแก้ไขเบื้องต้น | เครื่องมือตรวจสอบ |
|---|---|---|---|
| Backlog สะสม | Subscriber ช้า/ล่ม, Publisher ผลิตเร็วกว่ามาก | Scale Subscriber, ปรับ Flow Control, ตรวจสอบ Ack Deadline | num_undelivered_messages, oldest_unacked_message_age |
| ข้อความมาซ้ำบ่อย | Ack Deadline สั้นเกิน, Subscriber Crash, Network Issue | เพิ่ม Ack Deadline, ทำให้ Subscriber Idempotent, ใช้ Exponential Backoff ใน Publisher | subscription/push_request_count, Logs (ดู delivery_attempt) |
| Latency สูง | Pull แบบ Synchronous, Subscriber ประมวลผลช้า, Network | เปลี่ยนเป็น Streaming Pull หรือ Push, Optimize Subscriber Code, ใช้ Regional Endpoint | subscription/push_latency, Cloud Trace |
| Permission Denied | Service Account ขาดสิทธิ์ | เพิ่ม Role roles/pubsub.subscriber หรือ roles/pubsub.publisher | Cloud Audit Logs, gcloud get-iam-policy |
Summary
การ Troubleshooting GCP Pub/Sub ให้มีประสิทธิภาพต้องอาศัยความเข้าใจในกลไกพื้นฐาน การเฝ้าติดตามเมตริกที่ถูกต้อง และการออกแบบระบบที่ทนต่อความล้มเหลวตั้งแต่เริ่มต้น จำไว้ว่าปัญหาส่วนใหญ่เกิดจาก Ack Deadline, Flow Control ที่ไม่เหมาะสม, และ Subscriber ที่ไม่ Idempotent ใช้เครื่องมืออย่าง Cloud Monitoring, Logging และ Dead Letter Topic ให้เป็นประโยชน์ ฝึกฝนตามกรณีศึกษาและ Best Practices ในคู่มือนี้ จะช่วยให้คุณสร้างและบำรุงรักษาระบบ Event-Driven ที่แข็งแกร่ง บรรลุวัตถุประสงค์ทางธุรกิจได้อย่างราบรื่นแม้อยู่ในสถานการณ์ที่ข้อความไหลเข้าแบบมหาศาลในปี 2026 และต่อไปในอนาคต