ฟีดเดลตาของคุณพลาดคอมมิตที่มาสาย: สร้างเคอร์เซอร์ที่เชื่อถือได้
แถวหนึ่งมาถึงช้าเพราะเครื่องหมายน้ำไม่ใช่ขอบเขตคอมมิต ติดตามความล้มเหลว แล้วกำหนดความครอบคลุม ขอบเขต การเก็บรักษา และการกลับมาเริ่มใหม่ที่ปลอดภัย
นี่คือความล้มเหลวที่ผมเห็นเกิดสองครั้งในสองเอนจินที่ต่างกัน การส่งออกทำงานตอน 10:00 และบันทึกตราเวลาการแก้ไขล่าสุดที่สูงที่สุดที่เห็น ธุรกรรมหนึ่งที่เริ่มตอน 09:58 ยังเปิดอยู่ คอมมิตตอน 10:03 และมีตราเวลาจากตอนที่มันเริ่ม การส่งออกครั้งถัดไปขอเฉพาะแถวที่ใหม่กว่า 10:00 และไม่เคยเห็นมันเลย ขนาดการถ่ายโอนยอดเยี่ยมและข้อมูลผิด
ติดตามการส่งออกที่ทำแถวหนึ่งหาย
ติดตามลำดับเหตุการณ์ ไม่ใช่ SQL แอปพลิเคชันเขียนเวลาการแก้ไขของแถวเมื่อธุรกรรมเริ่ม ไม่ใช่ตอนคอมมิต ตัวส่งออกอ่านเครื่องหมายน้ำของนาฬิกาแขวนขณะธุรกรรมนั้นยังเปิดอยู่ จากนั้นธุรกรรมคอมมิตด้วยเวลาการแก้ไขที่เก่ากว่าเครื่องหมายน้ำที่บันทึกไว้ แบตช์ถัดไปของผู้บริโภคเริ่มหลังเครื่องหมายน้ำ แถวจึงอยู่หลังเคอร์เซอร์และจะไม่ถูกเลือกอีกเลย
ไม่มีอะไรบันทึกข้อผิดพลาด เพราะไม่มีอะไรล้มเหลว นั่นคือสิ่งที่ทำให้บั๊กประเภทนี้มีราคาแพง ฟีดเดลตารวดเร็ว จำนวนแถวดูสมเหตุสมผล และแถวที่หายไปโผล่ขึ้นมาหลายสัปดาห์ให้หลังเป็นตั๋วกระทบยอดที่ไม่มีใครทำซ้ำได้
หมายเลขลำดับมีรูปแบบข้อบกพร่องเดียวกันด้วยเหตุผลอื่น ID แบบโมโนโทนิกบอกลำดับการจัดสรรค่า ไม่ใช่ลำดับที่ธุรกรรมคอมมิต ธุรกรรมที่หยิบหมายเลขต่ำอาจคอมมิตหลังจากที่หยิบหมายเลขสูง เคอร์เซอร์ที่อิงลำดับการจัดสรรจึงอาจทิ้งรูไว้ได้เช่นกัน
ห้าคำสัญญาที่ฟีดต้องให้
ก่อนเลือกการขนส่ง ขอสัญญาห้าข้อจากระบบต้นทาง หากข้อใดหายไป คุณมีฮิวริสติก ไม่ใช่ฟีดการเปลี่ยนแปลง
- ความครอบคลุม: การแทรก การอัปเดต และการลบ แต่ละอย่างพร้อมคีย์ที่เสถียรซึ่งจำเป็นต่อการนำไปใช้
- ขอบเขต: ค่าเคอร์เซอร์หมายถึงอะไรและแบตช์ที่สอดคล้องกันมีอะไรบ้าง
- ความสอดคล้อง: วิธีอ่านตารางที่เกี่ยวข้องโดยไม่ผสมสถานะต้นทางที่เข้ากันไม่ได้
- การเก็บรักษา: ผู้บริโภคที่ตัดการเชื่อมต่อกลับมาเริ่มได้นานเท่าใดจากตำแหน่งที่บันทึกไว้
- การเริ่มใหม่: สแนปช็อตเต็มที่รองรับเมื่อตำแหน่งนั้นไม่ใช้ได้อีก
กลไกที่รองรับสามารถตอบทั้งห้าได้ เอกสารของ SQL Server เกี่ยวกับการติดตามการเปลี่ยนแปลง เป็นตัวอย่างที่เผยแพร่จริงตัวอย่างหนึ่ง: กำหนดเวอร์ชันต่ำสุดที่ใช้ได้ บันทึกการลบพร้อมกับการเปลี่ยนแปลง และอธิบายธุรกรรมสแนปช็อตที่ครอบคลุมการตรวจสอบความถูกต้อง การจับเวอร์ชัน และคำค้นการเปลี่ยนแปลงที่เกี่ยวข้อง นี่คือตัวอย่างสิ่งที่ควรเรียกจากต้นทาง ไม่ใช่ไวยากรณ์ที่นำไปวางในเอนจินอื่น
ออกแบบจุดตรวจสอบเป็นส่วนหนึ่งของไปป์ไลน์
ตำแหน่งที่บันทึกไว้ของผู้บริโภคคือเครื่องสถานะจริง จึงเขียนมันเช่นนั้น เคอร์เซอร์เลื่อนไปข้างหน้าได้เฉพาะผ่านเวิร์กโฟลว์ที่นำแบตช์ไปใช้และคอมมิตธุรกรรมเป้าหมายแล้วเท่านั้น
cursor = durable_checkpoint() # last fully applied position
batch = extract(cursor, upper_bound) # one consistent batch
apply(batch) # idempotent at the target
commit()
checkpoint(batch.upper_bound) # only now
หากการส่งล้มเหลวกลางทาง ให้กระทบยอดแบตช์เดิมแทนการคิดตำแหน่งต้นทางใหม่ การดึงซ้ำด้วยขอบเขตบนที่ใหม่กว่าคือหนทางที่การนำไปใช้บางส่วนกลายเป็นช่องว่างถาวร กฎกระจกใช้ได้กับอีกด้านเช่นกัน: การนำไปใช้แบบอิดัมโพเทนต์ที่ปลายทางป้องกันข้อมูลซ้ำและไม่ทำอะไรกับการเปลี่ยนแปลงที่การดึงไม่เคยเห็น
การลบต้องมีบันทึก ไม่ใช่ข้อสรุป
การเปรียบเทียบแถวที่ยังมีอยู่ไม่อาจบอกผู้บริโภคได้ว่าคีย์ใดหายไป ต้นทางที่ไม่เคยบันทึกการลบเหลือทางเลือกที่ไม่สมบูรณ์สองทาง: เก็บประวัติคีย์ไว้นานพอที่จะตรวจความต่าง หรือเรียกกระทบยอดเต็มรูปแบบอย่างชัดเจนตามกำหนดเวลา ทั้งคู่เป็นการตัดสินใจออกแบบที่มีต้นทุน และไม่มีสักทางหากไม่มีใครเป็นเจ้าของ
การเก็บรักษาเป็นการตัดสินใจประเภทเดียวกัน ฟีดการเปลี่ยนแปลงที่รองรับทุกตัวมีหน้าต่าง ระบุไว้ในสัญญาและเตือนเมื่อจุดตรวจสอบของผู้บริโภคเข้าใกล้มัน ผู้บริโภคที่ตื่นหลังการหยุดชะงักสองสัปดาห์ควรอ่านข้อผิดพลาดที่ชัดเจนว่า "ต้องเริ่มใหม่" แทนที่จะได้รับแบตช์บางส่วนอย่างเงียบ ๆ
จากนั้นวัดว่าเดลตาคุ้มค่าหรือไม่
การกลับไปเปรียบเทียบคีย์และแฮชทุกตัวบางครั้งก็ถูก และควรรู้ว่ามันมีต้นทุนเท่าไร คุณยังถ่ายโอนข้อมูลของทุกแถว แฮชไม่ได้ให้สแนปช็อตที่สอดคล้องกันข้ามตาราง และรูปแบบแฮชก็นำคำถามของตัวเองเรื่องการแทนค่าและการชนกันมา ด้วยไบต์ที่น้อยลงพร้อมขอบเขตที่ตรวจสอบได้คือเป้าหมาย ไบต์ที่น้อยลงแต่ทิ้งการเปลี่ยนแปลงที่คอมมิตแล้วอย่างเงียบ ๆ ไม่ใช่เดลตาเลย
คอลัมน์การแก้ไขล่าสุดคือเบาะแส เคอร์เซอร์ที่อยู่รอดจากคอมมิตสาย การลบ และการหยุดชะงักยาวนานคือสัญญา