14 กันยายน
เราเฝ้ารอวันนี้มาหลายสัปดาห์ ผู้ให้บริการพร็อกซี่แบบ residential ของเราหยุดให้บริการไปตั้งแต่เดือนกรกฎาคมเพราะปัญหาการเก็บเงิน และตลอดสองเดือนที่ผ่านมา สัดส่วนที่เพิ่มขึ้นเรื่อย ๆ จากทั้งหมด 1,375 แหล่งข้อมูลของเราก็ครอว์ลแบบลดคุณภาพหรือไม่ครอว์ลเลย — การดึงข้อมูลที่ไทม์เอาต์ ไอพีที่ถูกบล็อกกลางการทำงาน เป็นการไหลเลือดช้า ๆ แบบที่ไม่แสดงออกมาเป็นเหตุขัดข้องชัดเจน แต่เป็นตัวเลขที่ค่อย ๆ หดลงโดยไม่มีใครสังเกตจนกว่ามันจะน่าอายพอ เราต่ออายุสัญญา พูลใหม่ มีไลน์เซสชัน residential แบบหมุนไอพี 20,000 ไลน์ ตรวจสอบ exit ทุกตัวก่อนปล่อยให้เข้าใกล้โปรดักชัน การตรวจสอบผ่าน 100% ทั้ง AE, DE, JP — ทำงานได้ปกติทั้งหมด
เราจึงรันการครอว์ลแบบมีคนเฝ้าดู คือแบบที่มีคนจริง ๆ จับตาดูแดชบอร์ดแทนการเชื่อ cron อย่างเดียว ผมคาดว่าจะได้ผลตรงข้ามกับหายนะ
แต่สิ่งที่เราได้คือฐานข้อมูลที่เสียข้อมูลไป 84,401 แถวในการรันเพียงครั้งเดียว โดยมี 102 แหล่งข้อมูลที่ลดลงมากกว่า 25% จากสแนปช็อตก่อนหน้า แย่กว่าสองเดือนที่ขาดพร็อกซี่ที่เราเพิ่งแก้ไปเสียอีก
สมมติฐานที่ผิด แบบสั้น ๆ
สัญชาตญาณแรกของผม — และผมจะยอมรับตรง ๆ — คือคิดว่าพูลใหม่นั้นแย่กว่าที่การตรวจสอบบอกไว้ไม่ทางใดก็ทางหนึ่ง บางทีไอพีของเซสชันอาจถูกจำกัดอัตรา (rate-limited) กลางการครอว์ลในแบบที่การตรวจสอบครั้งเดียวจับไม่ได้ เป็นสมมติฐานที่สมเหตุสมผล แต่ก็ผิด และการเชื่อแบบนั้นแม้เพียงครึ่งวันก็ทำให้เราเสียเวลาที่เราไม่มีให้เสีย เพราะ cron ของการครอว์ลจะถูกหยุดพักไว้ตลอดช่วงที่คุณกำลังไล่ตรวจสมมติฐานแบบนั้น — คุณไม่อยากให้การรันที่พังไปเขียนทับสแนปช็อตที่ดีอยู่ขณะที่คุณยังเดากันอยู่
สัญญาณที่ทำให้สมมติฐานนั้นพังคือ dubizzle หนึ่งในแหล่งข้อมูล UAE ที่มีปริมาณสูงสุดของเรา จริง ๆ แล้วมันดึงข้อมูลได้ 32,595 รายการในการรันครั้งนั้น — ตัวเลขที่ปกติดีมาก พร็อกซี่ทำงานได้ชัดเจน — แล้วก็ทิ้งข้อมูลไปเกือบทั้งหมด ไม่ใช่ดึงไม่สำเร็จ แต่ดึงมาได้แล้วทิ้งไป
16 กันยายน: ตามหาบั๊กตัวจริง
นั่นคือจุดที่เรื่องนี้เลิกเป็นเรื่องพร็อกซี่และกลายเป็นเรื่องการแทนที่แถวข้อมูล (row-replacement) เลเยอร์การจัดเก็บข้อมูลของเรานำสแนปช็อตของแต่ละแหล่งข้อมูลมาเขียนทับทั้งไฟล์ <site>_cars_for_sale.csv ทุกครั้งที่รัน — เฉพาะรายการที่ยังขายอยู่ เขียนทับทั้งตาราง นั่นคือพฤติกรรมที่ถูกต้องเมื่อการครอว์ลเสร็จสมบูรณ์ แต่มันคือหายนะเมื่อการครอว์ลไทม์เอาต์ไปครึ่งทางและสคริปต์เขียนข้อมูลชุดที่ดึงมาได้บางส่วน เสมือนว่ามันคือสแนปช็อตทั้งหมด แหล่งข้อมูลที่ปกติมีรายการขายอยู่ 9,000 รายการ ถูกเขียนทับด้วยแค่ 1,200 รายการที่ดึงได้ก่อนเวลาจะหมด แล้วอีก 7,800 รายการก็หายไป — ไม่ใช่เพราะรถถูกขายไปแล้ว แต่เพราะโค้ดของเราเองตัดสินว่าการดึงข้อมูลที่ไม่สมบูรณ์คือสแนปช็อตที่สมบูรณ์
พร็อกซี่ไม่ได้โกหกเรา ตรรกะการลองซ้ำ (retry logic) คือตัวปัญหา และมันแย่แบบเงียบ ๆ มานานแล้ว เพียงแต่ถูกกลบไว้ด้วยพูลที่สุขภาพดีกว่าซึ่งเคยดึงข้อมูลเสร็จก่อนที่การลองซ้ำจะมีผล
นี่คือต้นทุนจริงของรอบการลองซ้ำนั้น เมื่อเราลองคำนวณดู
| รอบลองซ้ำแบบเดิม | รอบที่แก้ไขแล้ว | |
|---|---|---|
| จำนวนครั้งลองซ้ำต่อพร็อกซี่ | 3 | 1 |
| ระยะหน่วงต่อครั้ง | 1 วินาที | — |
| เพดานเวลารวมสูงสุดต่อพูล 50 พร็อกซี่ | ~56 นาทีต่อ URL | งบเวลารวม 120 วินาที |
| พฤติกรรมเมื่อไทม์เอาต์ | เขียนชุดข้อมูลบางส่วนเป็นสแนปช็อตเต็ม | ข้ามแหล่งข้อมูลนั้น เก็บสแนปช็อตก่อนหน้าไว้ |
56 นาทีกว่าจะยอมแพ้กับ URL เดียว ลองคูณด้วยการครอว์ลที่แตะแหล่งข้อมูลกว่าพันแหล่ง แล้วคุณก็จะได้ความล้มเหลวแบบเงียบ ๆ และกระจายไม่เท่ากันแบบที่เราเจอพอดี — บางแหล่งข้อมูลปกติดี 102 แหล่งถูกเฉือนเนื้อไป ไม่มีอะไรในล็อกที่ตะโกนว่า "พร็อกซี่ล่ม" เพราะในทางเทคนิคแล้ว มันไม่ได้ล่ม
25 กันยายน: การแก้ไข และส่วนที่สำคัญกับคุณ
แพตช์ที่ปล่อยออกมาเก้าวันต่อมาทำสองอย่างที่ไม่หวือหวาแต่ได้ผล มันจำกัดเพดานการคำนวณรอบลองซ้ำให้การดึงข้อมูลที่ติดขัดล้มเหลวเร็วขึ้นแทนที่จะกินเวลาเกือบทั้งชั่วโมง และ — นี่คือส่วนที่ป้องกันการสูญเสียข้อมูลจริง ๆ — การดึงข้อมูลบางส่วนจะไม่เขียนทับชุดแถวข้อมูลทั้งหมดอีกต่อไป ถ้าแหล่งข้อมูลหนึ่งดึงได้แค่ 1,200 จาก 9,000 รายการตามปกติ เราจะเก็บสแนปช็อตที่สมบูรณ์ก่อนหน้าไว้ แทนที่จะเขียนทับด้วยข้อมูลเสี้ยวเดียว ในวันเดียวกันนั้น การเปลี่ยนแปลงอีกอย่างก็ถูกนำมาใช้ คือการเขียนแฟล็ก CRAWL_PAUSED ที่มองเห็นได้ ซึ่งระบบมอนิเตอร์ของเราจะตรวจสอบก่อนจะแจ้งเตือนใครเกี่ยวกับการลดลงของข้อมูล — เพื่อให้การหยุดพักที่ตั้งใจทำถูกอ่านว่าเป็นการหยุดพัก ไม่ใช่การแจ้งเตือนเหตุขัดข้อง
ทำไมผู้อ่านที่กำลังมองหารถ Hyundai มือสองควรสนใจเรื่องนี้ด้วย? เพราะป้ายบอกราคาต่ำกว่าตลาด (below-market flags) ที่เราเผยแพร่ — ซึ่งมีอยู่ 12,823 ป้ายที่ใช้งานจริงบนเว็บไซต์ ณ สัปดาห์นี้ — จะซื่อตรงได้ก็ต่อเมื่อกลุ่มข้อมูลที่เรานำรายการนั้นไปเทียบ เป็นตลาดระดับภูมิภาคที่แท้จริงและสมบูรณ์ ไม่ใช่เศษข้อมูลที่ถูกตัดทิ้งจากการไทม์เอาต์ ป้ายดีลที่สร้างขึ้นจากสแนปช็อตที่สูญเสียกลุ่มเปรียบเทียบไปสามในสี่ ไม่ใช่ป้ายดีล มันคือสัญญาณรบกวนที่สวมป้ายสีเขียว เราขอหยุดการครอว์ลไว้เก้าวันดีกว่าที่จะเผยแพร่แบบนั้น
วันนี้ชุดข้อมูลของเรามีรายการอยู่ 1,108,535 รายการ ครอบคลุม 73 ภูมิภาค จาก 1,375 แหล่งข้อมูล — ใหญ่กว่าก่อนที่เรื่องทั้งหมดนี้จะเริ่มต้นเสียอีก พร็อกซี่ได้รับการต่ออายุและบั๊กการแทนที่แถวข้อมูลก็ถูกปิดไปแล้ว แต่ตัวเลขที่ผมยังคงกลับมานึกถึงอยู่เสมอไม่ใช่ตัวเลขรวมนั้น มันคือ 84,401 — ช่องว่างระหว่างการครอว์ลที่ดูเหมือนปกติดีในระดับพร็อกซี่ กับการครอว์ลที่ปกติดีจริง ๆ



