นี่คือความล้มเหลวที่ผมเห็นเกิดสองครั้งในสองเอนจินที่ต่างกัน การส่งออกทำงานตอน 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

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

การลบต้องมีบันทึก ไม่ใช่ข้อสรุป

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

การเก็บรักษาเป็นการตัดสินใจประเภทเดียวกัน ฟีดการเปลี่ยนแปลงที่รองรับทุกตัวมีหน้าต่าง ระบุไว้ในสัญญาและเตือนเมื่อจุดตรวจสอบของผู้บริโภคเข้าใกล้มัน ผู้บริโภคที่ตื่นหลังการหยุดชะงักสองสัปดาห์ควรอ่านข้อผิดพลาดที่ชัดเจนว่า "ต้องเริ่มใหม่" แทนที่จะได้รับแบตช์บางส่วนอย่างเงียบ ๆ

จากนั้นวัดว่าเดลตาคุ้มค่าหรือไม่

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

คอลัมน์การแก้ไขล่าสุดคือเบาะแส เคอร์เซอร์ที่อยู่รอดจากคอมมิตสาย การลบ และการหยุดชะงักยาวนานคือสัญญา

บทความที่เกี่ยวข้อง