บทความ

บทนำ: ถ้าไม่มีเมตริก ปัญหาจะดูเหมือน "ของเราทุกอย่างช้าไปหมด"

ฉากที่คุ้นเคย ในแชททีมมีข้อความขึ้นว่า "พร็อกซีช้า" ห้านาทีต่อมา ข้อความที่สอง: "และ error เยอะด้วย" จากนั้นสิ่งที่วิศวกรเรียกกันเองว่าการขุดซากก็เริ่มขึ้น ใครสักคนเปิดคอนโซลแล้วรัน curl ผ่านที่อยู่สุ่มสามแห่งด้วยมือ ใครอีกคนดูกราฟการใช้งาน CPU ซึ่งไม่เกี่ยวข้องกับพร็อกซีเลย ใครอีกคนเขียนอีเมลถึงฝ่ายสนับสนุนของผู้ให้บริการในทำนองว่า "ของคุณมีอะไรไม่ถูกต้อง" โดยไม่มีตัวเลขสักตัวในมือ ผ่านไปหนึ่งชั่วโมง ปรากฏว่าเสื่อมประสิทธิภาพเพียงหนึ่งเซกเมนต์ของพูล ในประเทศเดียว และเริ่มมาตั้งแต่สองวันก่อน แค่ไม่มีใครสังเกต

ความต่างระหว่างทีมที่ใช้เวลาหนึ่งชั่วโมงกับเรื่องแบบนี้ กับทีมที่ใช้เวลาหนึ่งนาที ไม่ได้อยู่ที่ประสบการณ์หรือพรสวรรค์ ความต่างอยู่ที่ทีมหลังตัดสินใจล่วงหน้าว่าจะวัดอะไร จะเขียนอะไรลง log และจะตั้งการแจ้งเตือนที่จุดไหน เมื่อคุณมีกราฟอัตราความสำเร็จของคำขอแยกตามประเทศและโอเปอเรเตอร์ ประโยค "ทุกอย่างช้าไปหมด" จะกลายเป็นข้อความที่ชัดเจนว่า "ตั้งแต่ 14:32 น. ค่า p95 ของเวลาถึงไบต์แรกผ่านพร็อกซีมือถือของโอเปอเรเตอร์ A ในภูมิภาค B เพิ่มจาก 1.8 เป็น 6.4 วินาที อัตราข้อผิดพลาดไม่เปลี่ยน แต่อัตราการ retry เพิ่มขึ้นสามเท่า" ด้วยข้อความแบบนี้ก็ทำงานต่อได้แล้ว: กระจายโหลดใหม่ เปิด ticket ถึงผู้ให้บริการพร้อมข้อมูลแทนอารมณ์ หรือเข้าใจว่าปัญหาไม่ได้อยู่ที่พร็อกซีเลย แต่อยู่ที่ปลายทาง

ในคู่มือนี้ เราจะสำรวจการสังเกตการณ์รอบพูลพร็อกซีตั้งแต่ต้นจนจบ คุณจะได้รู้ว่าสัญญาณสี่อย่างให้ข้อมูลที่มีประโยชน์เกือบทั้งหมดอย่างไร ฟิลด์ใดต้องมีในทุกบรรทัดของ log และอะไรที่ไม่ควรอยู่ในนั้นเด็ดขาด ทำไมเวลาเฉลี่ยถึงโกหก และจะคำนวณเปอร์เซ็นไทล์ให้ถูกต้องได้อย่างไร จะติดป้ายข้อมูลตามมิติเพื่อมองเห็นความเสื่อมประสิทธิภาพของสไลซ์เฉพาะได้อย่างไร จะสร้างการแจ้งเตือนที่ทำงานน้อยแต่ตรงประเด็นได้อย่างไร และจะแยกสามรูปแบบปัญหาบนแดชบอร์ดได้ในหนึ่งนาทีได้อย่างไร ปิดท้ายด้วยสแตกเทคโนโลยีขั้นต่ำ เคสจริงพร้อมตัวเลข และตอบคำถามที่พบบ่อย

ขอบเขตชัดเจนตั้งแต่แรก การเลือก IP เจาะจงจากพูล การตรวจสอบสุขภาพพร็อกซี และการกักกันที่อยู่ที่เสียหาย เราจะไม่ลงรายละเอียดในที่นี้ เพราะมีบทความแยกต่างหาก ที่นี่พูดถึงเพียงสองเรื่อง: การวัดและการแจ้งเตือน ทุกสิ่งที่อธิบายด้านล่างใช้ได้กับสถานการณ์วิศวกรรมที่ถูกกฎหมาย: การมอนิเตอร์ราคาบนแพลตฟอร์มสาธารณะ การตรวจสอบการแสดงโฆษณาในแต่ละภูมิภาค การทดสอบเนื้อหาที่ขึ้นกับภูมิศาสตร์ของบริการตัวเอง และการเก็บข้อมูลเปิดโดยปฏิบัติตามกฎของแหล่งข้อมูล

พื้นฐานเล็กน้อย: การสังเกตการณ์ในบริบทของพร็อกซีคืออะไร

คำจำกัดความแบบคลาสสิกบอกว่า การสังเกตการณ์ (observability) คือคุณสมบัติของระบบที่ทำให้เข้าใจสถานะภายในจากเอาต์พุตภายนอกได้ แปลเป็นภาษา engineering: คุณสามารถถามคำถามใหม่กับระบบที่ไม่ได้เตรียมไว้ล่วงหน้า และได้คำตอบโดยไม่ต้อง deploy โค้ดใหม่ การมอนิเตอร์ตอบคำถามที่รู้คำตอบอยู่แล้ว ("บริการยังอยู่ไหม?") การสังเกตการณ์ตอบคำถามที่ไม่รู้ ("ทำไมคำขอไปยังโดเมนนี้ผ่านที่อยู่มือถือถึงช้าลงเฉพาะวันอังคาร?"))

เสาหลักสามอย่างของการสังเกตการณ์เป็นที่รู้จักดี: เมตริก (อนุกรมเวลาตัวเลข เก็บถูก รวมง่าย) log (เหตุการณ์พร้อมบริบท เก็บแพง แต่มีรายละเอียด) และ trace (เส้นทางของคำขอหนึ่งผ่านคอมโพเนนต์ต่างๆ) สำหรับพูลพร็อกซี ภาพจะเฉพาะตัวขึ้นเล็กน้อย ระหว่างโค้ดของคุณกับเว็บปลายทางมีโครงสร้างพื้นฐานภายนอกที่คุณควบคุมไม่ได้: ที่อยู่ขาออก ผู้ให้บริการเครือข่าย เส้นทางไปยังปลายทาง คุณไม่สามารถติดตั้ง agent บนเสาสัญญาณมือถือได้ ดังนั้นจุดเดียวที่คุณวัดทุกอย่างได้คือไคลเอนต์ของคุณเองที่ส่งคำขอผ่านพร็อกซี ทุกคำขอผ่านพูลควรทิ้งร่องรอยสองอย่างไว้: การเพิ่มขึ้นของเมตริกและบรรทัด log แบบมีโครงสร้าง trace มีประโยชน์แต่เกินความจำเป็นสำหรับทีมส่วนใหญ่ และเราจะกลับมาพูดถึงเฉพาะใน FAQ

ทำไมเรื่องนี้ถึงสำคัญเฉพาะกับพร็อกซี? เพราะความเสื่อมประสิทธิภาพของพร็อกซีแทบไม่เคยปรากฏเป็นการล่ม เซิร์ฟเวอร์ไม่ตอบสนอง — นั่นง่าย แต่พร็อกซี "ช้าลงเล็กน้อย" "ให้ connection error บ่อยขึ้นนิดหน่อย" "บางครั้งตัดการดาวน์โหลดกลางทาง" อาการแต่ละอย่างแยกกันต่ำกว่าเกณฑ์ที่คนจะสนใจ แต่รวมกันแล้วทำให้ต้นทุนการเก็บข้อมูลเพิ่มเป็นสองเท่าและทำลายกำหนดการ การสังเกตความเสื่อมประสิทธิภาพแบบช้าๆ ทำได้ด้วยการวัดเท่านั้น

สี่สัญญาณสำหรับพร็อกซี: ความสำเร็จ ความหน่วง การ retry ทราฟฟิก

ในทางปฏิบัติด้าน reliability engineering มีแนวคิด "สัญญาณทอง" (golden signals): ความหน่วง ทราฟฟิก ข้อผิดพลาด และความอิ่มตัว สำหรับพูลพร็อกซี เราปรับแนวคิดนี้และได้สี่สัญญาณที่เพียงพอสำหรับ 90% ของสถานการณ์ ทำไมต้องสี่ไม่ใช่ยี่สิบ? เพราะทุกกราฟที่เพิ่มบนแดชบอร์ดจะลดโอกาสที่เจ้าหน้าที่เวรจะดูกราฟที่ถูกต้อง สี่สัญญาณคือสิ่งที่พอดีกับหนึ่งบรรทัดบนหน้าจอและอ่านได้ในสิบวินาที

สัญญาณที่ 1: อัตราความสำเร็จของคำขอ

อัตราความสำเร็จ (success rate) คืออัตราส่วนของคำขอที่จบด้วยผลลัพธ์ที่มีประโยชน์ต่อจำนวนคำขอทั้งหมดในช่วงเวลาหนึ่ง คำสำคัญคือ "ที่มีประโยชน์" ความสำเร็จสำหรับพร็อกซีไม่ใช่แค่ HTTP 200 ความสำเร็จคือเมื่อปลายทางตอบสิ่งที่คุณคาดหวังและคำตอบมาครบ การตัด connection กลางเนื้อหาไม่ใช่ความสำเร็จ แม้ว่าเฮดเดอร์จะมีโค้ด 200 โค้ด 403 จากเว็บปลายทางก็ไม่ใช่ความสำเร็จ แต่นั่นอาจไม่ใช่ความผิดของพร็อกซี และเราแยกแยะด้วยการจำแนกผลลัพธ์

กฎปฏิบัติ: จำแนกผลลัพธ์ของทุกคำขอเป็นหมวดหมู่จำนวนไม่มาก ตัวอย่าง: ok (ได้คำตอบที่คาดหวัง) proxy_connect_error (เชื่อมต่อพร็อกซีไม่ได้) proxy_auth_error (พร็อกซีปฏิเสธการยืนยันตัวตน) upstream_timeout (พร็อกซีตอบ แต่ปลายทางไม่ตอบทันเวลา) upstream_4xx upstream_5xx truncated (connection ถูกตัดก่อนจบเนื้อหา) การจำแนกนี้เป็นฐานสำหรับการวิเคราะห์ทั้งหมดต่อไป อัตราความสำเร็จคำนวณเป็น ok ต่อทั้งหมด แต่แยกดูอัตราของแต่ละหมวดข้อผิดพลาด เพราะมันชี้ไปที่ผู้รับผิดชอบต่างกัน

สัญญาณที่ 2: ความหน่วงตามเปอร์เซ็นไทล์

ความหน่วงสำหรับพร็อกซีควรวัดสามจุด: เวลาสร้าง connection กับพร็อกซี เวลาถึงไบต์แรกของคำตอบจากปลายทาง (TTFB, time to first byte) และเวลารวมของคำขอ อย่างแรกบอกถึงความพร้อมใช้งานของโหนดพร็อกซี อย่างที่สองบอกถึงคุณภาพเส้นทางจากพร็อกซีไปยังปลายทางและความเร็วของปลายทาง อย่างที่สามบอกถึงแบนด์วิดท์ ถ้าแค่ตัวที่สามเพิ่มขึ้นแต่สองตัวแรกคงที่ ปัญหาอยู่ที่แบนด์วิดท์ ไม่ใช่ความพร้อมใช้งาน ถ้าตัวแรกเพิ่ม ปัญหาอยู่ระหว่างคุณกับพร็อกซี ความหน่วงต้องคำนวณเป็นเปอร์เซ็นไทล์ ไม่ใช่ค่าเฉลี่ย และมีหัวข้อแยกด้านล่าง

สัญญาณที่ 3: อัตราการ retry

อัตราการ retry (retry rate) เป็นสัญญาณที่ถูกมองข้ามมากที่สุด ไคลเอนต์ส่วนใหญ่เขียนไว้ให้ retry คำขอโดยอัตโนมัติผ่านที่อยู่อื่นเมื่อเกิดข้อผิดพลาด นั่นถูกต้องในแง่ reliability และแย่มากในแง่ observability: อัตราความสำเร็จคงอยู่ที่ 99% แต่ภายใต้ hood ทุกคำขอทำงานสามครั้ง คุณจ่ายค่าทราฟฟิกสามเท่า ปลายทางได้รับโหลดสามเท่า และบนแดชบอร์ดทุกอย่างเขียว อัตราการ retry เปิดเผยความเสื่อมประสิทธิภาพที่ซ่อนอยู่ก่อนจะกลายเป็นความเสื่อมที่มองเห็นได้ คำนวณเป็นอัตราส่วนจำนวน attempt ต่อจำนวนคำขอเชิงตรรกะ: ค่า 1.0 หมายถึงไม่มีการ retry, 1.5 หมายถึงทุกคำขอที่สองถูก retry

สัญญาณที่ 4: ปริมาณการใช้ทราฟฟิก

ปริมาณการใช้ทราฟฟิกเป็นสัญญาณที่เชื่อมโยงกับเงินโดยตรง เพราะหลายแพ็กเกจรวมถึงแพ็กเกจของ Proxeon คิดตามปริมาณข้อมูลที่ส่ง แต่มันยังมีบทบาทในการวินิจฉัย การเพิ่มขึ้นอย่างรวดเร็วของไบต์ต่อคำขอที่สำเร็จหนึ่งคำขอหมายความว่าปลายทางเริ่มส่งหน้าที่ต่างออกไป: หน้า placeholder หนักๆ การ redirect ไปส่วนอื่น โครงสร้างที่เปลี่ยนไป การลดลงอย่างรวดเร็วหมายความว่าแทนที่จะได้หน้าเต็ม คุณได้คำตอบข้อผิดพลาดสั้นๆ นับทราฟฟิกทั้งสองทิศทางและปรับด้วยจำนวนคำขอที่สำเร็จเสมอ: "กิโลไบต์ต่อคำขอที่สำเร็จ" เป็นเมตริกที่มีข้อมูลมากกว่า "กิกะไบต์ต่อชั่วโมง" มาก

หน้าตาในโค้ดเป็นอย่างไร

ด้านล่างเป็นชุดเมตริกขั้นต่ำสำหรับ Prometheus ใน Python สังเกตชุด label: pool, country, operator, proxy_type ที่อยู่ของพร็อกซีเฉพาะเจาะจงไม่อยู่ใน label โดยเจตนา และเราจะอธิบายในหัวข้อว่าด้วยมิติ

from prometheus_client import Counter, Histogram

LABELS = ["pool", "country", "operator", "proxy_type"]

REQUESTS = Counter("proxy_requests_total", "Логические запросы через пул", LABELS + ["outcome"])
ATTEMPTS = Counter("proxy_attempts_total", "Попытки, включая ретраи", LABELS + ["attempt_kind"])
TTFB = Histogram("proxy_ttfb_seconds", "Время до первого байта", LABELS, buckets=(0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 30))
CONNECT = Histogram("proxy_connect_seconds", "Установление соединения с прокси", LABELS, buckets=(0.05, 0.1, 0.25, 0.5, 1, 2, 5))
BYTES = Counter("proxy_bytes_total", "Байты через пул", LABELS + ["direction"])

def observe(ctx, outcome, ttfb, connect, bytes_in, bytes_out, attempt):
base = dict(pool=ctx.pool, country=ctx.country, operator=ctx.operator, proxy_type=ctx.proxy_type)
kind = "first" if attempt == 1 else "retry"
ATTEMPTS.labels(**base, attempt_kind=kind).inc()
if outcome in ("ok", "upstream_4xx", "upstream_5xx", "truncated", "upstream_timeout"):
TTFB.labels(**base).observe(ttfb)
CONNECT.labels(**base).observe(connect)
BYTES.labels(**base, direction="in").inc(bytes_in)
BYTES.labels(**base, direction="out").inc(bytes_out)
if outcome == "ok" or attempt == ctx.max_attempts:
REQUESTS.labels(**base, outcome=outcome).inc()

สังเกตรายละเอียด: ตัวนับคำขอเชิงตรรกะเพิ่มขึ้นเฉพาะเมื่อสำเร็จหรือ嘗試ครบแล้วเท่านั้น วิธีนี้อัตราความสำเร็จสะท้อนผลลัพธ์สำหรับผู้ใช้โค้ดของคุณ ส่วนตัวนับ attempt สะท้อนงานจริงของพูล ความต่างระหว่างทั้งสองคืออัตราการ retry

ตาราง: เมตริก ความหมายเมื่อเพิ่มขึ้น การดำเนินการแรก

เมตริกความหมายเมื่อเพิ่มขึ้นการดำเนินการแรก
อัตรา proxy_connect_errorโหนดพร็อกซีไม่พร้อมใช้หรือโอเวอร์โหลด ปัญหาระหว่างคุณกับผู้ให้บริการตรวจสอบช่องทางขาออกและ DNS ของตัวเอง แล้วเทียบตามสไลซ์: ถ้าทั้งพูล ให้หาสาเหตุที่ตัวเอง ถ้าแค่สไลซ์เดียว ให้แจ้งผู้ให้บริการพร้อมข้อมูล
อัตรา proxy_auth_errorสิทธิ์การเข้าถึงหมดอายุ ข้อมูลรับรองเปลี่ยน เกินขีดจำกัดการเชื่อมต่อตรวจสอบความถูกต้องของข้อมูลรับรองและขีดจำกัดในแผงผู้ให้บริการ อย่า retry คำขอแบบนี้
อัตรา upstream_timeoutปลายทางตอบช้าผ่านเส้นทางนี้ หรือพร็อกซีตามไม่ทันเทียบ TTFB ตามโฮสต์ปลายทาง: ถ้าโฮสต์เดียว ปัญหาปลายทาง ถ้าทั้งหมด ปัญหาเส้นทางพร็อกซี
อัตรา upstream_4xx (ยกเว้น 404)ปลายทางจำกัดความเข้มข้นหรือเปลี่ยนกฎการเข้าถึงลดความถี่คำขอไปยังแหล่งข้อมูล ตรวจสอบว่าสอดคล้องกับกฎการใช้งาน เพิ่ม retry ไม่ช่วย
อัตรา upstream_5xxปัญหาฝั่งปลายทางรอ ลดโหลด พร็อกซีไม่น่าเกี่ยว
อัตรา truncatedconnection ถูกตัดกลางทาง: ช่องทางไม่เสถียร read timeout สั้นเกินไปตรวจสอบ read timeout และขนาดคำตอบ เทียบตามโอเปอเรเตอร์
p95 เวลาสร้าง connection กับพร็อกซีความพร้อมใช้งานของโหนดพร็อกซีเสื่อม หรือ network stack ของคุณมีปัญหาตรวจสอบจำนวน connection พร้อมกันและ port exhaustion ฝั่งตัวเอง
p95 TTFB ขณะเวลาสร้าง connection คงที่เส้นทางจากพร็อกซีไปปลายทางช้าลง หรือปลายทางช้าเองแยกตามโฮสต์ปลายทางและตามสไลซ์พูล
p99 เวลารวม ขณะ TTFB คงที่แบนด์วิดท์ช่องทางพร็อกซีลดลง หรือคำตอบใหญ่ขึ้นดูไบต์ต่อคำขอ ถ้าขนาดไม่เปลี่ยน ปัญหาแบนด์วิดท์
อัตราการ retryความเสื่อมที่ซ่อนอยู่: ความสำเร็จคงไว้ด้วยการ retry ต้นทุนเพิ่มหาว่าหมวดข้อผิดพลาดใดทำให้เกิดการ retry แล้วจัดการ
ไบต์ต่อคำขอที่สำเร็จปลายทางส่งเนื้อหาอื่น: placeholder, redirect, โครงสร้างเปลี่ยนตรวจสอบเนื้อหาคำตอบในตัวอย่าง อัปเดต parser และ validation
ทราฟฟิกรวมขณะจำนวนคำขอคงที่การ retry หรือขนาดคำตอบเพิ่ม กระแทกงบประมาณโดยตรงแยกปัจจัยสองอย่าง: จำนวน attempt และไบต์ต่อ attempt

เขียนอะไรลง log ต่อคำขอ และเขียนอะไรไม่ได้

เมตริกตอบคำถาม "เกิดอะไรขึ้น" log ตอบคำถาม "กับอะไรกันแน่" เมื่อแดชบอร์ดแสดงข้อผิดพลาดเพิ่มในสไลซ์ "ประเทศ X โอเปอเรเตอร์ Y" คุณต้องเข้าใจว่าที่อยู่ทั้งหมดในสไลซ์นั้นผิด หรือแค่สองจากสามสิบ เมตริกที่มี label ตามที่อยู่จะระเบิดที่เก็บข้อมูล แต่ log ไม่ เพราะ log ไม่ได้ถูกรวมตลอดเวลาแต่ว่าง่ายต่อการกรองตามคำขอ

ฟิลด์บังคับของบรรทัด log

ทุก attempt ของคำขอผ่านพูลควรสร้างหนึ่ง record แบบมีโครงสร้าง ในรูปแบบ JSON จะดี นี่คือชุดฟิลด์ที่เราเห็นว่าเพียงพอขั้นต่ำ:

  • ts — timestamp ใน UTC พร้อมมิลลิวินาที ไม่ใช่เวลาท้องถิ่น ไม่มีข้อยกเว้น
  • request_id — ตัวระบุคำขอเชิงตรรกะ ใช้ร่วมกันทุก attempt ช่วยรวบรวมประวัติทั้งหมดของคำขอหนึ่งด้วยคำสั่งเดียว
  • attempt และ max_attempts — หมายเลข attempt และขีดจำกัด ถ้าไม่มีหมายเลข attempt คุณจะแยกความผิดพลาดครั้งแรกจากครั้งที่สามไม่ได้
  • proxy_id — ตัวระบุภายในของพร็อกซี ไม่ใช่ที่อยู่พร้อมพอร์ตและไม่ใช่ connection string ตัวระบุภายในที่คุณจับคู่กับที่อยู่จริงผ่านตารางที่ป้องกันแยกต่างหาก
  • proxy_type country operator pool — มิติเดียวกับในเมตริก ช่วยให้กระโดดระหว่างแดชบอร์ดและ log ได้โดยไม่ต้องคำนวณใหม่
  • target_host — ชื่อโดเมนของปลายทาง ไม่ใช่ URL เต็ม เพราะใน query string อาจมี token และข้อมูลส่วนบุคคล
  • method — HTTP method
  • status — HTTP code ของคำตอบ ถ้ามี ไม่งั้น null
  • outcome — การจำแนกผลลัพธ์ที่พูดถึงข้างบน
  • error_class — ชั้นของ exception หรือรหัสข้อผิดพลาดของ transport แบบสั้น ถ้าผลลัพธ์ไม่ใช่ ok
  • connect_ms ttfb_ms total_ms — ความหน่วงสามค่าในมิลลิวินาที
  • bytes_in bytes_out — ขนาดเนื้อหาคำตอบและขนาดคำขอ
  • session_id — ถ้าใช้ sticky session ตัวระบุ session จะเห็นว่าข้อผิดพลาดกระจุกตัวใน session ยาว
  • retry_reason — สำหรับ attempt ตั้งแต่ครั้งที่สอง: ผลลัพธ์ของ attempt ก่อน

ตัวอย่างบรรทัด:

{"ts":"2026-03-14T11:32:07.412Z","request_id":"7f3a9c1e","attempt":2,"max_attempts":3,"retry_reason":"upstream_timeout","proxy_id":"px-2841","proxy_type":"mobile","country":"RU","operator":"op_a","pool":"pricing","target_host":"shop.example","method":"GET","status":200,"outcome":"ok","error_class":null,"connect_ms":184,"ttfb_ms":1420,"total_ms":2310,"bytes_in":48211,"bytes_out":612,"session_id":"s-19b"}

หนึ่งบรรทัดแบบนี้หนักประมาณ 400 ไบต์ ที่ 50 คำขอต่อวินาที คิดเป็นประมาณ 1.7 กิกะไบต์ต่อวันโดยไม่บีบอัด และ 200–300 เมกะไบต์เมื่อบีบอัด จัดการได้สบายแม้กับงบประมาณจำกัด

สิ่งที่ต้องไม่มีใน log

ตรงนี้เราย้ายจากวิศวกรรมไปสู่ความรับผิดชอบ รายการสั้นแต่ทุกข้อสำคัญ:

  1. ข้อมูลรับรองการเข้าถึงพร็อกซี การรั่วไหลที่พบบ่อยที่สุดคือ connection string รูปแบบ http://login:password@host:port ที่ попадаетใน log ทั้งบรรทัด เพราะนักพัฒนาติ๊ก log config "เพื่อ debug" log ถูกคัดลอกไปใน ticket ส่งต่อให้เพื่อนร่วมงาน เก็บไว้นานหลายปี ข้อมูลรับรองในนั้นคือข้อมูลรับรองที่ทุกคนที่เคยเห็น log เข้าถึงได้
  2. เฮดเดอร์ Proxy-Authorization และเฮดเดอร์ยืนยันตัวตนอื่นๆ รวมถึงเฮดเดอร์ไปยังปลายทาง
  3. เนื้อหาคำตอบ มันใหญ่มาก อาจมีข้อมูลส่วนบุคคลของบุคคลที่สาม และแทบไม่เคยจำเป็นสำหรับการวินิจฉัยพร็อกซี ถ้าจำเป็นต้องรู้ว่าปลายทางส่งอะไร ให้เก็บ hash ของเนื้อหาและความยาว ส่วนคำตอบเองแยกไปที่เก็บต่างหากที่มีอายุสั้นและเก็บเฉพาะตัวอย่าง
  4. Cookies ทั้งสองทิศทาง
  5. URL เต็มพร้อม query parameters ชื่อโฮสต์ก็พอ และถ้าจำเป็น path ที่ไม่มี parameter ก็ได้
  6. ที่อยู่จริงของพร็อกซีในรูปแบบเปิดเผย ถ้า log ไปยังที่เก็บส่วนกลาง ตัวระบุภายในก็เพียงพอ

แนวปฏิบัติที่เราแนะนำ: implement การแก้ไขที่ระดับฟังก์ชันเดียวที่ทุก record ที่เกี่ยวกับพร็อกซีต้องผ่าน อย่าพึ่งวินัยของนักพัฒนา

import re

CRED_IN_URL = re.compile(r"(https?|socks5h?)://[^/@:]+:[^/@]+@")
SENSITIVE_HEADERS = {"proxy-authorization", "authorization", "cookie", "set-cookie"}

def redact(record: dict) -> dict:
for key, value in list(record.items()):
if key.lower() in SENSITIVE_HEADERS:
record[key] = "[redacted]"
elif isinstance(value, str):
record[key] = CRED_IN_URL.sub(lambda m: m.group(1) + "://[redacted]@", value)
record.pop("body", None)
return record

การสุ่มตัวอย่างและการเก็บรักษา

ต้องเขียนทุก attempt ไหม? ข้อผิดพลาด — ใช่ ทั้งหมด attempt ที่สำเร็จ — สุ่มได้: ที่ความเข้มข้นเกินร้อยคำขอต่อวินาที การเก็บ 5–10% ของบรรทัดที่สำเร็จก็เพียงพอสำหรับประเมินความหน่วง เพราะเมตริกมีภาพครบอยู่แล้ว สำคัญที่การตัดสินใจสุ่มต้องอิงตาม request_id ไม่ใช่สุ่มแยกแต่ละ attempt ไม่งั้นคุณจะได้ attempt ที่สองโดยไม่มีที่หนึ่ง อายุการเก็บ log รายละเอียดมัก 7–14 วัน ส่วน aggregate และเมตริกอยู่นานกว่า

เปอร์เซ็นไทล์แทนค่าเฉลี่ย: ทำไมค่าเฉลี่ยซ่อนปัญหา

ลองนึกภาพพูล 100 ที่อยู่ 90 แห่งตอบใน 0.8 วินาที 10 แห่งตอบใน 9 วินาที เพราะไปติดสถานีฐานที่โอเวอร์โหลด เวลาตอบเฉลี่ยคือ 1.62 วินาที เมื่อสัปดาห์ก่อนตอนทุกอย่างดี ค่าเฉลี่ยคือ 1.4 เพิ่มขึ้น 15% ใครจะตอบสนองต่อสิ่งนี้? ไม่มีใคร แต่ในขณะเดียวกัน ทุกคำขอที่สิบของคุณรอเก้าวินาที และถ้าคุณมี timeout ที่แปด ทุกคำขอที่สิบจะล้มเหลวด้วยข้อผิดพลาด

ค่าเฉลี่ยเลขคณิตอธิบายการกระจายที่คล้ายระฆังได้ดี ความหน่วงผ่านพร็อกซีไม่เคยคล้ายระฆัง มันคือการกระจายที่มีหางขวายาว: คำขอส่วนใหญ่เร็ว แต่บางส่วนช้ามาก และนั่นแหละที่กำหนดประสบการณ์ผู้ใช้และต้นทุน ยิ่งไปกว่านั้น ความหน่วงผ่านพูลมักเป็นแบบ bimodal: มี hump "ปกติ" และ hump "ที่อยู่มีปัญหา" ค่าเฉลี่ยตกลงระหว่างทั้งสอง ตรงจุดที่แทบไม่มีคำขอจริง

p50, p95, p99 คืออะไร

เปอร์เซ็นไทล์ คือค่าที่ต่ำกว่าซึ่งมีสัดส่วนที่กำหนดของ observations p50 (median) — ครึ่งหนึ่งของคำขอเร็วกว่าค่านี้ p95 — 95% เร็วกว่า 5% ช้ากว่า p99 — มีเพียงหนึ่งเปอร์เซ็นต์ที่ช้ากว่า สำหรับพูลพร็อกซีเราแนะนำดูสามเปอร์เซ็นไทล์ด้วยจุดประสงค์ต่างกัน:

  • p50 แสดงคำขอ "ทั่วไป" ถ้า p50 เพิ่ม แปลว่าส่วนใหญ่เสื่อมแล้ว นี่เป็นสัญญาณที่พบยากและรุนแรง
  • p95 — เมตริกทำงานสำหรับการแจ้งเตือน ไวต่อเซกเมนต์ที่มีปัญหา (ในตัวอย่างข้างบน p95 จะแสดง 9 วินาทีทันที) แต่ทนต่อ outlier เดี่ยวๆ
  • p99 — เมตริกสำหรับ timeout และการวางแผน timeout ของคุณควรสูงกว่า p99 ที่ "สุขภาพดี" เล็กน้อย ไม่งั้นคุณจะตัดคำขอปกติและเปลี่ยนมันเป็นการ retry

วิธีคำนวณแบบแม่นยำ

ถ้าข้อมูลน้อย (พันค่าในช่วง) คำนวณแม่นยำได้: เรียงลำดับแล้วเอาองค์ประกอบตามดัชนี

def percentile(values, p):
if not values:
return None
ordered = sorted(values)
k = (len(ordered) - 1) * p / 100.0
lo = int(k)
hi = min(lo + 1, len(ordered) - 1)
return ordered[lo] + (ordered[hi] - ordered[lo]) * (k - lo)

ttfb_window = [0.8, 0.9, 0.7, 9.1, 0.85, 8.7, 0.78, 0.92, 0.81, 0.79]
print(percentile(ttfb_window, 50), percentile(ttfb_window, 95))

การคำนวณแม่นยำไม่สเกล: การคำนวณ p95 ต่อสัปดาห์จากหลายล้านคำขอต้องเก็บค่าทั้งหมด ดังนั้นระบบอุตสาหกรรมใช้โครงสร้างแบบประมาณ

วิธีคำนวณในระดับสเกล: ฮิสโตแกรม

Prometheus และระบบที่เข้ากันได้เก็บฮิสโตแกรมเป็นชุดตัวนับต่อ bucket: มี observations กี่ตัวที่น้อยกว่า 0.1 วินาที น้อยกว่า 0.25 วินาที น้อยกว่า 0.5 วินาที เป็นต้น เปอร์เซ็นไทล์ถูกสร้างคืนด้วยการ interpolate เชิงเส้นภายใน bucket ความแม่นยำขึ้นกับการเลือกขอบเขต bucket สำหรับความหน่วงผ่านพร็อกซี สเกลลอการิทึมจาก 100 มิลลิวินาทีถึง 30 วินาทีใช้ได้ดี: 0.1; 0.25; 0.5; 1; 2; 4; 8; 16; 30 ถ้า p95 ที่สุขภาพดีของคุณประมาณ 2 วินาที และ timeout 15 ตารางนี้ให้ความคลาดเคลื่อนในระดับ 20–30% บนหาง ซึ่งเพียงพอสำหรับการแจ้งเตือนและการวินิจฉัย

histogram_quantile(0.95,
sum by (le, country, operator) (
rate(proxy_ttfb_seconds_bucket{pool="pricing"}[5m])
)
)

สังเกต sum by (le, ...) ข้างใน นี่เป็นรายละเอียดสำคัญยิ่ง: ฮิสโตแกรมของ instance ต่างๆ ของไคลเอนต์ต้องรวมกันก่อนแล้วจึงคำนวณ quantile การดำเนินการย้อนกลับ — คำนวณ p95 ที่แต่ละ instance แล้วเฉลี่ย — ไม่มีความหมายทางคณิตศาสตร์ ค่าเฉลี่ยของเปอร์เซ็นไทล์ไม่ใช่เปอร์เซ็นไทล์ นี่เป็นข้อผิดพลาดที่พบบ่อยที่สุดอย่างหนึ่ง และสามารถซ่อนปัญหาได้แนบเนียนพอๆ กับค่าเฉลี่ยธรรมดา

ถ้าต้องการความแม่นยำสูงบนหาง (เช่น ขาย SLA ความหน่วงให้ลูกค้าภายใน) ใช้โครงสร้างแบบ t-digest หรือ HDR histogram ที่ให้ความแม่นยำระดับเศษเสี้ยวเปอร์เซ็นต์ด้วยหน่วยความจำคงที่ สำหรับทีมส่วนใหญ่ ฮิสโตแกรมมาตรฐานก็เพียงพอ

การติดป้ายตามมิติ: ประเทศ โอเปอเรเตอร์ ประเภทพร็อกซี

นี่คือสถานการณ์ที่แสดงให้เห็นว่าทำไมต้องมีมิติ คุณมีพูล 400 ที่อยู่ 5% เป็นที่อยู่มือถือของโอเปอเรเตอร์เดียวในภูมิภาคเดียว เซกเมนต์นี้เสื่อมอย่างสมบูรณ์: อัตราความสำเร็จตกเหลือ 10% กราฟอัตราความสำเร็จรวมของพูลจะแสดงอะไร? ตกจาก 99% เป็น 94.5% สังเกตได้แต่ไม่วิกฤต เจ้าหน้าที่เวรจะคิดว่า "เน็ตมีปัญหา" แล้วไปต่อ แต่ลูกค้าข้อมูลจากภูมิภาคนี้จะได้ความว่างเปล่า

การติดป้ายตามมิติ (ในศัพท์ Prometheus เรียกว่า label) แก้ปัญหานี้ตรงๆ: ช่วยให้แยก aggregate เป็นสไลซ์และเห็นว่าปัญหาจำกัดอยู่ที่ไหน แต่ label มีราคา และต้องเข้าใจ

เลือกมิติใด

มิติที่ดีมีคุณสมบัติสองอย่าง: มีจำนวนค่าจำกัดไม่มาก และการแบ่งตามมิตินั้นตอบคำถามการวินิจฉัยจริง สำหรับพูลพร็อกซีเราแนะนำสี่มิติในเมตริก:

  • proxy_type — มือถือ, residential, data center ประเภทต่างๆ เสื่อมไม่เหมือนกันและมีต้นทุนต่างกัน
  • country — ประเทศของที่อยู่ขาออก โดยทั่วไป 1 ถึง 30 ค่า
  • operator — ผู้ให้บริการเครือข่ายหรือ autonomous system ที่ที่อยู่ผ่านออก ปัญหามักจำกัดอยู่ตรงนี้: สถานีฐานที่โอเวอร์โหลดหนึ่งแห่ง หรือ peering ที่แย่ของโอเปอเรเตอร์หนึ่ง
  • pool — วัตถุประสงค์เชิงตรรกะ: ผลิตภัณฑ์หรือ task ใดใช้เซกเมนต์นี้ ช่วยแยกเหตุการณ์ตามผลกระทบทางธุรกิจ

มิติที่ห้าที่ควรคิดถึงคือ target_host โฮสต์ปลายทาง จำเป็นเพื่อแยกปัญหาพร็อกซีจากปัญหาปลายทาง แต่ถ้าคุณไปยังหลายพันโดเมน cardinality จะระเบิด ทางประนีประนอม: เก็บใน label เฉพาะ 20 โฮสต์อันดับต้นตามปริมาณ ส่วนที่เหลือรวมเป็นค่า other

เลขคณิตของ cardinality

ทุกชุดค่าผสมที่ไม่ซ้ำกันของค่า label คืออนุกรมเวลาแยก ลองคำนวณ: 3 ประเภท × 10 ประเทศ × 15 โอเปอเรเตอร์ × 4 พูล × 7 outcome = 12,600 อนุกรมแค่สำหรับตัวนับคำขอ ฮิสโตแกรมที่มี 9 bucket จะคูณจำนวนอนุกรมของมันด้วย 10 รวมประมาณ 50–60 พันอนุกรม สำหรับ Prometheus สบายมาก ทีนี้ลองนึกว่าคุณเพิ่ม label ที่อยู่พร็อกซี (400 ค่า) จำนวนอนุกรมโตขึ้น 400 เท่า — เป็น 20 ล้าน นั่นไม่ใช่ "สบาย" นั่นคือความตายของที่เก็บข้อมูล

จากตรงนี้กฎคือ: ที่อยู่ของพร็อกซีเฉพาะอยู่ใน log ไม่ใช่ในเมตริก เมตริกตอบคำถาม "สไลซ์ไหนเสื่อม" log ตอบ "ที่อยู่ไหนกันแน่ในสไลซ์" การแบ่งงานแบบนี้ให้ทั้งความเร็วและรายละเอียดโดยไม่ทำให้ต้นทุนพุ่ง

Query ที่หาสไลซ์

คำถามหลักของการวินิจฉัย: "ที่ไหนแย่ที่สุด?" ตัวที่ตอบคือการจัดเรียงสไลซ์ตามอัตราความสำเร็จ:

bottomk(5,
sum by (country, operator, proxy_type) (rate(proxy_requests_total{outcome="ok"}[10m]))
/
sum by (country, operator, proxy_type) (rate(proxy_requests_total[10m]))
)

คำถามที่สอง: "นี่เป็นปัญหาใหม่หรือเป็นมาตลอด?" ตัวที่ตอบคือการเทียบกับอดีต:

(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m])) / sum by (operator) (rate(proxy_requests_total[10m]))
)
-
(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m] offset 1d)) / sum by (operator) (rate(proxy_requests_total[10m] offset 1d))
)

ค่าลบแสดงว่าสไลซ์แย่ลงกว่าหนึ่งวันก่อนแค่ไหน กราฟแบบนี้บนแดชบอร์ดเปลี่ยน "มีอะไรไม่ถูกที่ไหนสักแห่ง" เป็น "โอเปอเรเตอร์ B ในประเทศ C ลบ 12 จุดเทียบกับเมื่อวาน"

ค่าของมิติมาจากไหน

ประเทศและประเภทพร็อกซีมักรู้จาก config ของพูล โอเปอเรเตอร์ได้จาก metadata ของผู้ให้บริการ: เช่น ในแผงและ API ของ Proxeon ที่อยู่มีข้อมูลภูมิศาสตร์และโอเปอเรเตอร์ ซึ่งคุณดึงตอน initialize พูลและ cache ไว้ข้างตัวระบุภายในของพร็อกซี การระบุโอเปอเรเตอร์ ณ เวลาคำขอผ่านฐานข้อมูลภายนอกไม่ควรทำ: จะเพิ่มความหน่วงและสร้าง dependency เกินจำเป็น

การแจ้งเตือนที่ไม่รบกวน: เกณฑ์ หน้าต่าง และ hysteresis

สิ่งที่แย่ที่สุดที่อาจเกิดกับระบบแจ้งเตือนไม่ใช่เหตุการณ์ที่พลาดไป สิ่งที่แย่ที่สุดคือเมื่อเจ้าหน้าที่เวรได้รับ 40 การแจ้งเตือนต่อวัน ซึ่ง 39 ไม่มีอะไร และเลิกอ่านอันที่สี่สิบ การแจ้งเตือนที่ถูกเพิกเฉยแย่กว่าการไม่มีการแจ้งเตือน เพราะสร้างภาพลวงของการควบคุม

หลักการที่ 1: แจ้งเตือนที่อาการ ไม่ใช่ที่สาเหตุ

อย่าตั้งการแจ้งเตือนที่ "พร็อกซี px-2841 ตอบข้อผิดพลาด" ที่อยู่เดียวมีสิทธิ์ไม่สบาย และกลไกกักกันภายในพูลรับผิดชอบเรื่องนั้น ซึ่งเราไม่พูดถึงที่นี่ ตั้งการแจ้งเตือนที่สิ่งที่ผู้บริโภคข้อมูลของคุณรู้สึก: อัตราความสำเร็จของพูลต่ำกว่าเป้า p95 สูงกว่าเป้า retry สูงกว่าเป้า สาเหตุจะหาได้จากแดชบอร์ดในหนึ่งนาที ถ้าทำมิติถูก หน้าที่ของการแจ้งเตือนคือปลุก ไม่ใช่อธิบาย

หลักการที่ 2: ปริมาณทราฟฟิกขั้นต่ำ

อัตราความสำเร็จ 50% กับสองคำขอในห้านาที คือคำขอที่ล้มเหลวหนึ่งครั้ง ไม่ใช่เหตุการณ์ กฎใดที่ว่าด้วยอัตราต้องมีเงื่อนไขปริมาณคำขอขั้นต่ำในช่วงเวลา ไม่งั้นกลางคืนตอนทราฟฟิกลดลง คุณจะจมอยู่กับ false positive

หลักการที่ 3: หน้าต่างและอัตราการเผางบประมาณ

ถ้าคุณกำหนดระดับ reliability เป้าหมาย (เช่น 98% คำขอสำเร็จต่อเดือน) คุณมี error budget: 2% ของคำขอ การแจ้งเตือนที่ดีตอบสนองต่อ อัตราการใช้งบประมาณ ไม่ใช่ค่าทันที แนวปฏิบัติที่เป็นมาตรฐานในวงการ reliability คือการแจ้งเตือนแบบหลายหน้าต่าง: หน้าต่างสั้น (5 นาที) จับการล่มรุนแรง หน้าต่างยาว (1 ชั่วโมง) จับการเสื่อมช้าๆ และทั้งสองต้องเกินพร้อมกันเพื่อตัด spike สั้นๆ ออก

หลักการที่ 4: hysteresis

Hysteresis คือเกณฑ์ที่ต่างกันสำหรับการ trigger และการ clear ถ้าการแจ้งเตือน trigger ที่อัตราต่ำกว่า 95% และ clear เมื่อกลับขึ้นเหนือ 95% เมตริกที่แกว่งรอบ 95% จะสร้างชุดการแจ้งเตือน "trigger–clear–trigger" ทุกสองสามนาที ทางออก: trigger ที่ 95% clear ที่ 97% หรือพูดง่ายกว่า ต้องให้เงื่อนไขคงอยู่ต่อเนื่องตามเวลาที่กำหนด (ใน Prometheus คือ keyword for) และกำหนดดีเลย์การ clear

หลักการที่ 5: ระดับความรุนแรงและการ routing

ไม่ทุกอย่างต้องส่งเป็นสายด่วน แบ่งการแจ้งเตือนเป็นสองสามระดับ: วิกฤต (กระทบผู้บริโภคข้อมูลตอนนี้ — โทรหรือ push ถึงเจ้าหน้าที่เวร) เตือน (สไลซ์เสื่อม retry เพิ่ม ความผิดปกติทราฟฟิก — ข้อความไปช่อง รอจัดการในเวลาทำงาน) ข้อมูล (instance ไม่ส่งข้อมูล ใกล้ถึงขีดจำกัดทราฟฟิก — สรุปรายวัน)

ตัวอย่างชุดกฎ

groups:
- name: proxy-pool
rules:
- record: pool:success_ratio:5m
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[5m])) / sum by (pool) (rate(proxy_requests_total[5m]))
- record: pool:success_ratio:1h
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[1h])) / sum by (pool) (rate(proxy_requests_total[1h]))
- record: pool:request_rate:5m
expr: sum by (pool) (rate(proxy_requests_total[5m]))

- alert: ProxyPoolSuccessLow
expr: pool:success_ratio:5m < 0.95 and pool:success_ratio:1h < 0.97 and pool:request_rate:5m > 0.5
for: 5m
labels: { severity: critical }
annotations:
summary: "Доля успеха по пулу {{ $labels.pool }} ниже цели"

- alert: ProxyPoolLatencyHigh
expr: histogram_quantile(0.95, sum by (le, pool) (rate(proxy_ttfb_seconds_bucket[10m]))) > 4 and pool:request_rate:5m > 0.5
for: 10m
labels: { severity: warning }

- alert: ProxyPoolRetryRateHigh
expr: sum by (pool) (rate(proxy_attempts_total[15m])) / sum by (pool) (rate(proxy_requests_total[15m])) > 1.4
for: 15m
labels: { severity: warning }

- alert: ProxyPoolSliceDegraded
expr: (sum by (pool, country, operator) (rate(proxy_requests_total{outcome="ok"}[15m])) / sum by (pool, country, operator) (rate(proxy_requests_total[15m]))) < 0.85 and sum by (pool, country, operator) (rate(proxy_requests_total[15m])) > 0.2
for: 15m
labels: { severity: warning }

- alert: ProxyPoolNoData
expr: absent(proxy_requests_total) or pool:request_rate:5m == 0
for: 10m
labels: { severity: warning }

สังเกตกฎสุดท้าย การไม่มีข้อมูลคือความล้มเหลวที่ร้ายกาจที่สุด: ไคลเอนต์ล่ม ไม่มีเมตริก กราฟแบนและเขียว ทุกคนสบายใจ การแจ้งเตือนความเงียบเป็นสิ่งจำเป็น

Checklist สำหรับการแจ้งเตือนใหม่ทุกครั้ง

  1. เขียนใน annotation หรือยังว่าผู้รับควรทำอะไร? ถ้าไม่ กฎยังไม่พร้อม
  2. มีเงื่อนไขปริมาณทราฟฟิกขั้นต่ำไหม?
  3. มี for หรือ hysteresis ไหม?
  4. ทดสอบกฎกับประวัติเดือนที่แล้วหรือยัง: จะ trigger กี่ครั้ง และมีกี่ครั้งที่เป็นเหตุการณ์จริง?
  5. ชัดเจนไหมว่าอยู่ระดับความรุนแรงใดและ route ไปไหน?
  6. มีกระบวนการย้อนกลับไหม: ถ้าแจ้งเตือนแล้วเป็น false positive ใครจะทบทวนเมื่อไร?

วินิจฉัยเร็วบนแดชบอร์ด: สามรูปแบบที่พบบ่อย

แดชบอร์ดไม่ใช่หอศิลป์ของกราฟ แต่เป็นเครื่องมือตัดสินใจ แดชบอร์ดที่ดีสำหรับพูลพร็อกซีถูกจัดเป็นกรวย แถวบน: สัญญาณสี่อย่างของทั้งพูลใน 6 ชั่วโมงล่าสุด ซ้อนทับกับเมื่อวานด้วยเส้นบาง แถวที่สอง: สัญญาณเดิม แยกตามประเทศ โอเปอเรเตอร์ และประเภท — ในรูปตารางหรือ heatmap เรียงตามค่าที่แย่ที่สุด แถวที่สาม: อัตราความสำเร็จและ TTFB ของ 10 โฮสต์ปลายทางอันดับต้น แถวที่สี่: อัตราส่วนเวลาสร้าง connection, TTFB และเวลารวม บวก marker แนวตั้งของการ deploy และการเปลี่ยน config ของพูล

ด้วยแดชบอร์ดแบบนี้ การวินิจฉัยคือการตอบสามคำถาม: "ทั้งพูลหรือสไลซ์เสื่อม?" "ทุกปลายทางหรือหนึ่งปลายทาง?" "ความหน่วงในสามค่าตัวไหนเพิ่ม?" คำตอบรวมกันได้สามรูปแบบ

รูปแบบที่ 1: ทุกสไลซ์เสื่อมพร้อมกัน

ทุกประเทศ ทุกโอเปอเรเตอร์ ทุกประเภทพร็อกซีแสดงข้อผิดพลาดหรือความหน่วงเพิ่มพร้อมกัน ระดับนาที ผู้ให้บริการภายนอกและโอเปอเรเตอร์ไม่พังแบบนี้: พวกเขาไม่มีจุดล้มเหลวร่วมกับฝั่งคุณ แสดงว่าปัญหาอยู่ที่คุณ: ช่องทางขาออก DNS การหมด file descriptor หรือ local port การ deploy ล่าสุดที่เปลี่ยน timeout หรือขนาดพูล connection การแย่ง CPU กับ process อื่นบนเครื่องเดียว การดำเนินการแรก: ดู marker ของ deploy และเวลาสร้าง connection กับพร็อกซี ถ้าเวลาสร้าง connection เพิ่มแต่ TTFB (หัก connection แล้ว) คงที่ ปัญหาอยู่ระหว่างคุณกับโหนดพร็อกซี และเกือบแน่นอนว่าอยู่ฝั่งคุณหรือ ISP ของคุณ

รูปแบบที่ 2: สไลซ์เดียวเสื่อม

โอเปอเรเตอร์เดียวในประเทศเดียว หรือประเภทพร็อกซีเดียว แสดงการตก ในขณะที่สไลซ์อื่นคงที่ นี่คือรูปแบบที่พบบ่อยที่สุด สาเหตุอยู่ที่โอเปอเรเตอร์ (ความแออัดของเซกเมนต์เครือข่าย งานบำรุงรักษาตามกำหนด) หรือที่เซกเมนต์โครงสร้างพื้นฐานของผู้ให้บริการพร็อกซี การดำเนินการแรก: ลดน้ำหนักสไลซ์นี้ในการกระจายโหลดชั่วคราว (กลไกอยู่นอกบทความนี้) และเขียนถึงฝ่ายสนับสนุนของผู้ให้บริการด้วยข้อมูลเฉพาะ: สไลซ์ เวลาเริ่ม เมตริกก่อนและหลัง ตัวอย่าง request_id ฝ่ายสนับสนุนของ Proxeon ตอบสนองต่อคำขอแบบนี้เร็วกว่า "ของคุณแย่หมด" เพราะได้รับการระบุตำแหน่งที่พร้อมใช้ การดำเนินการที่สอง: ดู log ของสไลซ์ เพื่อเข้าใจว่าที่อยู่ทั้งหมดในสไลซ์เสื่อม หรือแค่บางส่วน

รูปแบบที่ 3: อัตราความสำเร็จตกสำหรับโฮสต์ปลายทางเดียว ความหน่วงปกติ

ทุกสไลซ์ของพูลยังไปยังทุกโฮสต์ได้ดี ยกเว้นโฮสต์เดียว สำหรับโฮสต์นี้อัตรา upstream_4xx เพิ่มขึ้น หรือปริมาณไบต์ต่อคำขอเปลี่ยน พร็อกซีไม่ผิดเลย ปลายทางเปลี่ยนพฤติกรรม: กำหนดข้อจำกัดความถี่ เปลี่ยนโครงสร้างหน้า เริ่มส่งเนื้อหาต่างสำหรับบางคำขอ การดำเนินการแรก: ลดความเข้มข้นคำขอไปยังแหล่งนี้และตรวจสอบว่าการเก็บข้อมูลของคุณสอดคล้องกับกฎการใช้งานหรือไม่ การดำเนินการที่สอง: ตรวจสอบเนื้อหาในตัวอย่างและอัปเดต validation คำตอบ ในสถานการณ์นี้ไม่ควรเพิ่มจำนวน retry หรือเปลี่ยนที่อยู่บ่อยขึ้นเด็ดขาด — นั่นจะเพิ่มโหลดให้แหล่งข้อมูลและต้นทุนทราฟฟิกของคุณ

รูปแบบที่ 4 (โบนัส): retry เพิ่ม ความสำเร็จคงที่

อัตราความสำเร็จ 99% ความหน่วงพอรับได้ แต่อัตราการ retry เพิ่มจาก 1.1 เป็น 1.8 นี่คือความเสื่อมที่ซ่อนอยู่ มันกำลังกินเงินคุณอยู่ตอนนี้ และจะกลายเป็นความเสื่อมที่มองเห็นได้ทันทีที่จำนวน attempt หมด การดำเนินการแรก: ดูว่า outcome ใดเป็นสาเหตุของการ retry (ฟิลด์ retry_reason ใน log) แล้วเดินตามหนึ่งในสามรูปแบบแรก

Checklist วินิจฉัยใน 60 วินาที

  1. วินาทีที่ 0–10: แถวบนของแดชบอร์ด สัญญาณใดในสี่ออกจากเส้นเมื่อวาน? เมื่อไรกันแน่?
  2. วินาทีที่ 10–25: ตารางตามสไลซ์ สไลซ์เดียวแดงหรือทั้งหมด? ถ้าทั้งหมด — รูปแบบที่ 1 ไปที่ marker ของ deploy และโครงสร้างพื้นฐานของตัวเอง
  3. วินาทีที่ 25–40: ตารางตามโฮสต์ปลายทาง หนึ่งหรือทั้งหมด? ถ้าหนึ่ง — รูปแบบที่ 3 พร็อกซีไม่เกี่ยว
  4. วินาทีที่ 40–50: อัตราส่วนความหน่วง เพิ่มที่ connection, TTFB หรือเวลารวม?
  5. วินาทีที่ 50–60: ตั้งประโยคเดียว: "ตั้งแต่ [เวลา] ในสไลซ์ [สไลซ์] สำหรับ [ปลายทาง] [เมตริก] เพิ่มจาก [ก่อน] เป็น [หลัง] สันนิษฐานว่าเกิดจาก [สมมติฐาน]" ประโยคนี้ส่งไปช่องเหตุการณ์และ ticket ถึงผู้ให้บริการ

ข้อผิดพลาดที่พบบ่อยซึ่งลบล้างประโยชน์ของการสังเกตการณ์

เราเห็นการนำการมอนิเตอร์รอบพร็อกซีมาหลายสิบครั้ง ข้อผิดพลาดซ้ำด้วยความคงเส้นคงวาน่าทึ่ง

  • เวลาเฉลี่ยเป็นเมตริกหลัก เราพูดแล้ว: ค่าเฉลี่ยซ่อนหาง และหางคือปัญหาจริง
  • เฉลี่ยเปอร์เซ็นไทล์ระหว่าง instance รวมฮิสโตแกรม ไม่ใช่ p95 ที่คำนวณแล้ว
  • ที่อยู่พร็อกซีเป็น label ของเมตริก cardinality ระเบิดและที่เก็บข้อมูลล่มในเดือนหน้า
  • ข้อมูลรับรองการเข้าถึงใน log บรรทัดเดียว "for debug" — และรหัสผ่านอยู่ในที่เก็บ log ส่วนกลางเป็นปี
  • การ retry ถูกนับเป็นความสำเร็จ ถ้านับเฉพาะผลลัพธ์สุดท้าย คุณจะไม่เห็นว่าพูลทำงานหนักเป็นสามเท่าของที่ควร
  • โค้ด 429 และ 403 จากปลายทางถูกบันทึกเป็นข้อผิดพลาดพร็อกซี นี่คือข้อผิดพลาดในการจำแนกที่พาการสืบสวนไปผิดทางและทำลายความสัมพันธ์กับผู้ให้บริการ
  • ความหน่วงเดียวแทนที่จะเป็นสาม โดยไม่แยกวัด connection, TTFB และเวลารวม คุณจะแยกปัญหาความพร้อมใช้งานจากแบนด์วิดท์ไม่ได้
  • การแจ้งเตือนที่ไม่มีปริมาณทราฟฟิกขั้นต่ำ เสียงรบกวนกลางคืนและสูญเสียความเชื่อถือต่อการแจ้งเตือน
  • การแจ้งเตือนทุกที่อยู่เดียว นั่นเป็นงานของการกักกัน ไม่ใช่การปลุกคน
  • ไม่มีการแจ้งเตือนเมื่อไม่มีข้อมูล ไคลเอนต์ล่ม — กราฟเขียว
  • เวลาท้องถิ่นใน log การเทียบกับข้อมูลผู้ให้บริการและเพื่อนร่วมทีมใน timezone อื่นกลายเป็นบทลงทัณฑ์
  • แดชบอร์ดที่ไม่มี marker ของ deploy ครึ่งหนึ่งของเหตุการณ์เกิดขึ้นพร้อมกับการเปลี่ยนโค้ดหรือ config ถ้าไม่มี marker คุณจะหาสาเหตุจากภายนอกทั้งที่อยู่ภายใน
  • เก็บ log รายละเอียดตลอดไป แพงและไร้ความหมาย: ผ่านไปสองสัปดาห์รายละเอียดไม่จำเป็น ส่วน aggregate มีในเมตริกแล้ว

สแตกขั้นต่ำโดยไม่ซับซ้อนเกินไป

การสังเกตการณ์กลายเป็นโปรเจกต์เพื่อโปรเจกต์ได้ง่ายๆ ทีมสามคน deploy message queue แบบกระจาย คลัสเตอร์ฐานข้อมูลคอลัมน์ และระบบ trace — สำหรับไคลเอนต์ที่ทำ 20 คำขอต่อวินาที ไม่ต้องทำอย่างนั้น สแตกต้องเหมาะกับขนาด

ระดับ 1: บริการเดียว ไม่เกิน 50 คำขอต่อวินาที

เพียงพอด้วย JSON log แบบมีโครงสร้างไปยังไฟล์หรือ standard output และสคริปต์หนึ่งที่รวมทุกนาทีและเขียนสี่สัญญาณลงฐานข้อมูลง่ายๆ หรือแม้แต่ log เดียวกัน แดชบอร์ด — เครื่องมือใดก็ได้ที่วาดกราฟจากตารางได้ การแจ้งเตือน — สคริปต์ cron ที่เทียบ aggregate กับเกณฑ์และส่งข้อความไปยัง messenger สแตกแบบนี้ตั้งได้ในหนึ่งวันและครอบคลุม 80% ของความต้องการ

ระดับ 2: หลายบริการ 50–1000 คำขอต่อวินาที

ชุดมาตรฐาน: Prometheus สำหรับเมตริก Grafana สำหรับแดชบอร์ด Alertmanager สำหรับ routing การแจ้งเตือน Loki หรือที่เก็บ log ที่เทียบเท่าพร้อม index ตาม label ไคลเอนต์ส่งออกเมตริกผ่าน HTTP endpoint ในตัวและเขียน JSON log ไปยัง standard output; agent เก็บรวบรวม recording rules คำนวณอัตราความสำเร็จล่วงหน้าเพื่อให้แดชบอร์ดเปิดทันที นี่คือ config ที่อธิบายในตัวอย่างข้างบน และครอบคลุมทีมส่วนใหญ่

ระดับ 3: หลายสิบบริการ หลายพันคำขอต่อวินาที

ตรงนี้จึงมีเหตุผลที่จะใช้ OpenTelemetry เป็นชั้นเก็บรวบรวมเดียว ที่เก็บ log แบบคอลัมน์ที่รองรับ query เชิงวิเคราะห์ตามอำเภอใจบนหลายพันล้านแถว และอาจ trace สำหรับ chain ที่ซับซ้อน แต่แม้ในระดับนี้ สี่สัญญาณและโครงสร้าง log ยังไม่เปลี่ยน เปลี่ยนแค่ transport และการเก็บ

ข้อมูลจากผู้ให้บริการเป็นส่วนเสริม

ผู้ให้บริการพร็อกซีเห็นฝั่งของเขา: ปริมาณการใช้ทราฟฟิกต่อพอร์ต จำนวน connection ที่ใช้งาน เวลาหมุนเวียนล่าสุด แผงและ API ของ Proxeon ให้สถิติเหล่านี้ และมีประโยชน์ที่จะดึงมาไว้ข้างเมตริกของตัวเอง — อย่างน้อยเพื่อเทียบปริมาณทราฟฟิก: ถ้าตัวนับไบต์ของคุณกับของผู้ให้บริการต่างกันเกิน 10–15% คุณมีปัญหาการวัด (บ่อยที่สุดคือไม่ได้นับ header, TLS handshake หรือคำตอบที่ถูกตัด) แต่ข้อมูลผู้ให้บริการไม่แทนที่ของคุณ: มีเพียงคุณที่รู้ว่าคำขอใดสำเร็จในแง่ของ task

Wrapper ของไคลเอนต์เป็นจุดวัดเดียว

ไม่ว่าจะระดับไหนของสแตก การวัดทั้งหมดต้องผ่าน wrapper เดียวเหนือ HTTP client ไม่มี "ตรงนี้วัด ตรงนั้นลืม" แบบแผน:

import time, json, sys, requests

def fetch_via_proxy(ctx, url, timeout=(5, 20)):
t0 = time.perf_counter()
outcome, status, err = "ok", None, None
ttfb = connect = total = 0.0
bytes_in = 0
try:
session = ctx.session # уже настроена на прокси по внутреннему proxy_id
resp = session.get(url, timeout=timeout, stream=True)
ttfb = time.perf_counter() - t0
status = resp.status_code
body = resp.content
bytes_in = len(body)
total = time.perf_counter() - t0
if status >= 500: outcome = "upstream_5xx"
elif status >= 400: outcome = "upstream_4xx"
elif not ctx.validate(body): outcome = "invalid_content"
except requests.exceptions.ProxyError as e:
outcome, err = "proxy_connect_error", type(e).__name__
except requests.exceptions.ReadTimeout as e:
outcome, err = "upstream_timeout", type(e).__name__
except requests.exceptions.ChunkedEncodingError as e:
outcome, err = "truncated", type(e).__name__
finally:
total = total or (time.perf_counter() - t0)
observe(ctx, outcome, ttfb, connect, bytes_in, len(url), ctx.attempt)
record = redact({"ts": ctx.now_utc(), "request_id": ctx.request_id, "attempt": ctx.attempt,
"proxy_id": ctx.proxy_id, "proxy_type": ctx.proxy_type, "country": ctx.country,
"operator": ctx.operator, "pool": ctx.pool, "target_host": ctx.host(url),
"status": status, "outcome": outcome, "error_class": err,
"ttfb_ms": int(ttfb * 1000), "total_ms": int(total * 1000), "bytes_in": bytes_in})
sys.stdout.write(json.dumps(record, ensure_ascii=False) + chr(10))
return outcome, status

สังเกต outcome invalid_content: คำตอบที่ code 200 แต่ไม่ผ่าน validation ของคุณ (เช่น หน้าที่ไม่มีบล็อกข้อมูลที่คาดหวัง) ไม่ใช่ความสำเร็จ คำตอบแบบนี้มักปกปิดการเปลี่ยนพฤติกรรมของปลายทางบ่อยที่สุด

เคส: การสังเกตการณ์ในทางปฏิบัติเป็นอย่างไร

เคส 1: โอเปอเรเตอร์เดียว ประเทศเดียว ลบสองชั่วโมง downtime ต่อสัปดาห์

ทีมมอนิเตอร์ราคาบนแพลตฟอร์มการค้าสาธารณะทำงานผ่านพูลประมาณ 300 ที่อยู่มือถือและ residential ที่ความเข้มข้นประมาณ 40 คำขอต่อวินาที ก่อนใช้การติดป้ายตามโอเปอเรเตอร์ พวกเขาเห็นอย่างเดียว: p95 TTFB รวมกระโดดจาก 1.6 เป็น 3–4 วินาทีเป็นครั้งคราวหลายชั่วโมง อัตราความสำเร็จลดลง 3–5 จุด การสืบสวนแต่ละครั้งใช้เวลาหนึ่งถึงสองชั่วโมง และมักจบด้วยคำว่า "หายเอง" หลังเพิ่ม label country และ operator เหตุการณ์แรกถูกระบุตำแหน่งในสองนาที: ที่อยู่มือถือของโอเปอเรเตอร์เดียวในภูมิภาคเดียว p95 ตรงนั้น 11 วินาที เทียบกับปกติ 2 ที่เหลือ 85% ของพูลปกติทั้งหมด ทีมลดน้ำหนักสไลซ์ในการกระจาย ส่งรายงานให้ผู้ให้บริการพร้อมกรอบเวลาและเมตริก และอีกไม่กี่ชั่วโมงต่อมาได้รับการยืนยันว่ามีงานบำรุงรักษาตามกำหนดของโอเปอเรเตอร์ ผลลัพธ์ในไตรมาส: เวลาวินิจฉัยลดจาก 90 นาทีเหลือ 5 จำนวนเหตุการณ์ไม่เปลี่ยน แต่ผลกระทบต่อ p95 รวมกลายเป็นแยกไม่ออก

เคส 2: retry ที่ซ่อนอยู่และ +38% ในบิลค่าทราฟฟิก

อีกทีมพบปริมาณทราฟฟิกเพิ่มขึ้น 38% ในหนึ่งเดือนขณะจำนวน task ไม่เปลี่ยนและอัตราความสำเร็จคงที่ 98.5% สมมติฐานแรกว่า หน้าปลายทางหน้าใหม่หนักขึ้น เมตริกไบต์ต่อคำขอที่สำเร็จหักล้าง: 46 กิโลไบต์เหมือนเดิม แต่กลับพบว่าอัตราการ retry ซึ่งไม่มีใครดูมาก่อน เพิ่มจาก 1.08 เป็น 1.52 การวิเคราะห์ฟิลด์ retry_reason ใน log แสดงว่าเกือบทุกการ retry เกิดจาก outcome upstream_timeout สำหรับโฮสต์ปลายทางเดียวที่ตอบช้าลงแต่ทัน timeout ในครั้งที่สอง พร็อกซีไม่ใช่สาเหตุเลย ทีมเพิ่ม read timeout สำหรับโฮสต์นี้จาก 8 เป็น 15 วินาที (p99 ที่สุขภาพดีคือ 11) ลดความเข้มข้นคำขอไปยังโฮสต์ และคืนปริมาณทราฟฟิกสู่ระดับเดิมภายในสามวัน บทเรียน: อัตราการ retry คือเมตริกว่าด้วยเงิน

เคส 3: จาก 200 การแจ้งเตือนต่อสัปดาห์เหลือหก

ทีมที่สามรับช่วงระบบแจ้งเตือนที่มีกฎ "อัตราความสำเร็จของสไลซ์ใดก็ตามใน 1 นาทีต่ำกว่า 90%" โดยไม่มีข้อจำกัดทราฟฟิก กลางคืน เมื่อบางสไลซ์มีสองสามคำขอต่อนาที กฎ trigger หลายสิบครั้ง เจ้าหน้าที่เวรปิดเสียง มีเหตุการณ์จริงหนึ่งครั้ง — ความพร้อมใช้งานของเซกเมนต์ทั้งหมดล่มสี่ชั่วโมง — ถูกพบเช้าวันถัดไปจากคำร้องเรียน การปรับกฎใช้เวลาหนึ่งวัน: เพิ่มเงื่อนไขทราฟฟิกขั้นต่ำ ใช้สองหน้าต่าง (5 นาทีและ 1 ชั่วโมง) เพิ่ม for 10 นาที ย้ายสไลซ์ไประดับ warning และเหลือ critical เดียวที่ระดับ pool ผลลัพธ์: ประมาณหกการแจ้งเตือนต่อสัปดาห์ สี่ถึงห้าต้องลงมือจริง เจ้าหน้าที่เวรเปิดเสียงอีกครั้ง

FAQ: คำถามที่พบบ่อยเกี่ยวกับการสังเกตการณ์พูลพร็อกซี

ต้องใช้ trace ไหมถ้ามีเมตริกและ log แล้ว?

สำหรับทีมส่วนใหญ่ — ไม่ คำขอผ่านพร็อกซีไม่ได้ผ่านสิบ microservice มันมี transition เดียว บทบาทของ trace ที่นี่ทำโดย request_id ใน log: ใช้รวบรวมทุก attempt ของคำขอเดียว trace มีประโยชน์เมื่อพร็อกซีไคลเอนต์เป็นเพียงส่วนหนึ่งของ chain ยาว (task queue, parser, validator, loader เข้าฐานข้อมูล) และคุณต้องเข้าใจว่าเวลาหายไปที่ไหนกันแน่

จะวัด TTFB อย่างไรถ้า HTTP client ไม่ให้มาโดยตรง?

ไคลเอนต์ส่วนใหญ่คืนการควบคุมหลังได้ header ถ้าขอ stream ช่วยอ่าน (stream=True หรือเทียบเท่า) ขณะที่คืนคือการประมาณ TTFB เวลาสร้าง connection วัดได้แม่นกว่าที่ระดับ transport: หลายไลบรารีมี hook ต่อ event ของ connection บางตัวมีข้อมูล phase ของคำขอพร้อม ถ้าไม่มี hook สามารถวัดเวลาถึงการส่งไบต์แรกของคำขอแยกด้วยการวัดระดับต่ำ แต่ในทางปฏิบัติการประมาณผ่าน stream เพียงพอ

จะแยกข้อผิดพลาดพร็อกซีจากข้อผิดพลาดปลายทางอย่างไร?

ตามตำแหน่งที่เกิด ถ้า connection กับพร็อกซีสร้างไม่ได้ หรือพร็อกซีส่ง code ของตัวเอง (เช่น 407 เมื่อมีปัญหาการยืนยันตัวตน หรือคำตอบ gateway เฉพาะ) นี่คือข้อผิดพลาดพร็อกซี ถ้าพร็อกซีรับคำขอแล้วได้ code จากปลายทางหรือ timeout รอคำตอบ นี่คือข้อผิดพลาดระหว่างพร็อกซีกับปลายทางหรือของปลายทางเอง จำแนกที่ขอบเขตนี้ แล้วเทียบตามสไลซ์: ข้อผิดพลาดปลายทางปรากฏพร้อมกันทุกสไลซ์ ข้อผิดพลาดพร็อกซีปรากฏเฉพาะบางสไลซ์

อัตราการ retry ที่สุขภาพดีควรเป็นเท่าไร?

ขึ้นกับเป้าหมายและประเภทพร็อกซี สำหรับที่อยู่มือถือ 1.05–1.15 ถือว่าปกติ: เครือข่ายไร้สายโดยธรรมชาติคาดเดาได้น้อยกว่า สำหรับ data center — 1.01–1.05 สิ่งสำคัญกว่าไม่ใช่ค่าสัมบูรณ์ แต่ความ穩定: การเพิ่ม 30% เทียบกับ baseline รายสัปดาห์เป็นเหตุให้ตรวจสอบ แม้ค่าสัมบูรณ์ยังดูเล็กน้อย

จะเลือกเกณฑ์การแจ้งเตือนอย่างไรถ้าไม่มีประวัติ?

เริ่มด้วยการสังเกตโดยไม่แจ้งเตือน: เก็บข้อมูลสองสัปดาห์ ดูการกระจายอัตราความสำเร็จและ p95 ตามวันและชั่วโมง ตั้งเกณฑ์ critical ให้ในช่วงสองสัปดาห์นี้ trigger ไม่เกินหนึ่งถึงสองครั้ง และทั้งสองครั้งคุณเห็นด้วยว่าเป็นเหตุการณ์ เกณฑ์ warning — ให้ trigger สองสามครั้งต่อสัปดาห์ แล้วทบทวนทุกเดือน: การแจ้งเตือนที่ไม่เคยนำไปสู่การลงมือ ต้องเข้มขึ้นหรือลบทิ้ง

ควรเก็บเนื้อหาคำตอบอย่างน้อยสำหรับข้อผิดพลาดไหม?

ข้อผิดพลาด transport ไม่มีเนื้อหา สำหรับคำตอบที่มีเนื้อหาไม่คาดคิด (invalid_content) มีประโยชน์ที่จะเก็บสองสามร้อยไบต์แรก หรือ hash และความยาว ไปยังที่เก็บแยกที่มีอายุหนึ่งถึงสองวัน พร้อมลบข้อมูลส่วนบุคคลเสมอ ไม่จำเป็นต้องเก็บเนื้อหาเต็ม: หนัก อาจมีข้อมูลบุคคลที่สาม และสำหรับการวินิจฉัยพร็อกซีมักพอรู้ว่าหน้าประเภทไหน

จะเทียบบัญชีทราฟฟิกของตัวเองกับข้อมูลผู้ให้บริการอย่างไร?

นับไบต์ทั้งสองทิศทาง รวม header และเทียบยอดรายวันต่อพอร์ตหรือ session กับที่แผงผู้ให้บริการแสดง ความต่าง 5–10% ปกติ: ฝั่งคุณไม่นับค่าใช้จ่าย TLS และ connection ที่ถูกตัด ความต่างเกิน 20% เป็นเหตุให้ตรวจว่าคุณนับเฉพาะเนื้อหาคำตอบและเสีย attempt ที่ล้มเหลวก่อนอ่านเนื้อหาหรือไม่

ใช้เวลานานเท่าไรในการนำวิธีนี้มาใช้จากศูนย์?

สำหรับไคลเอนต์เดียวที่มี wrapper เหนือ HTTP: หนึ่งวันสำหรับเมตริกและโครงสร้าง log หนึ่งวันสำหรับแดชบอร์ด หนึ่งวันสำหรับกฎแจ้งเตือน และอีกสองสัปดาห์สำหรับสังเกตก่อนเปิด critical ถ้ามีหลายไคลเอนต์ในภาษาต่างกัน เวลาส่วนใหญ่อยู่ที่การตกลงชุด outcome และ label ร่วม — ตรงนี้อย่ารีบ เพราะการเปลี่ยนชื่อ label หลังเก็บประวัติมาปีเป็นเรื่องเจ็บปวดมาก

ทำอย่างไรถ้าข้อมูลโอเปอเรเตอร์จากผู้ให้บริการไม่มีสำหรับบางที่อยู่?

ใช้ค่า unknown และติดตามสัดส่วน ถ้า unknown เกิน 10–15% ของพูล การแบ่งสไลซ์ตามโอเปอเรเตอร์จะหมดความหมาย — ขอ metadata ครบจากผู้ให้บริการ หรือติดป้ายที่อยู่ด้วยมือครั้งเดียวตอนเพิ่มเข้าพูล

บทสรุป: หนึ่งนาทีแทนหนึ่งชั่วโมง

กลับไปที่ฉากตอนต้น "พร็อกซีช้า" เป็นประโยคที่ไม่มีข้อมูล ทุกสิ่งที่อธิบายในคู่มือนี้รับใช้เป้าหมายเดียว: เปลี่ยนมันให้เป็นประโยคที่มีตัวเลข สไลซ์ เวลา และสมมติฐาน สี่สัญญาณ — อัตราความสำเร็จ ความหน่วงตามเปอร์เซ็นไทล์ อัตราการ retry ปริมาณทราฟฟิก — ให้ภาพสถานะของพูล log แบบมีโครงสร้างพร้อม request_id, proxy_id, หมายเลข attempt, code, ความหน่วงสามค่า และขนาด — ให้รายละเอียดสำหรับการสืบสวน เปอร์เซ็นไทล์แทนค่าเฉลี่ยแสดงหางที่มีปัญหาจริง การติดป้ายตามประเทศ โอเปอเรเตอร์ และประเภท ระบุตำแหน่งความเสื่อมถึงระดับสไลซ์ การแจ้งเตือนที่มีทราฟฟิกขั้นต่ำ สองหน้าต่าง และ hysteresis trigger น้อยและตรงประเด็น แดชบอร์ดแบบกรวยตอบสามคำถามในหนึ่งนาที

เริ่มจากอะไรพรุ่งนี้? ขั้นแรก — wrapper เหนือไคลเอนต์ที่มีการจำแนก outcome ร่วมและฟังก์ชันบันทึกเดียว ขั้นที่สอง — ฮิสโตแกรม TTFB และตัวนับ attempt พร้อม label ตามประเทศ โอเปอเรเตอร์ และประเภท ขั้นที่สาม — ฟังก์ชันแก้ไข log ที่ทุกอย่างต้องผ่านโดยไม่มีข้อยกเว้น ขั้นที่สี่ — แดชบอร์ดสี่กราฟและตารางตามสไลซ์หนึ่งตาราง ขั้นที่ห้า — สังเกตสองสัปดาห์แล้วจึงค่อยแจ้งเตือน อีกหนึ่งเดือนคุณจะดูกราฟอัตราการ retry และอาจประหลาดใจ: พูลทำงานหนักกว่าที่คิดมาก และในหนึ่งไตรมาส ประโยค "ของเราทุกอย่างช้าไปหมด" จะหายไปจากแชทของคุณ — ไม่ใช่เพราะปัญหาไม่มี แต่เพราะคุณจะเรียกแต่ละปัญหาด้วยชื่อของมัน