กุญแจเข้ารหัสของแต่ละลูกค้า — บทเรียนจากตอนที่หมุนกุญแจแล้วล้มกลางทาง
ระบบที่ดูแลฐานข้อมูลของลูกค้าหลายรายต้องเก็บรหัสผ่านสำหรับเชื่อมต่อฐานข้อมูลของแต่ละรายไว้ที่ไหนสักแห่ง
บทความนี้เล่าว่าเราเก็บมันอย่างไร ทำไมถึงเปลี่ยนวิธีกลางทาง และสองเหตุการณ์ที่สอนเรามากที่สุด
จุดเริ่มต้น — กุญแจเดียวทั้งระบบ
ตอนแรกเราเข้ารหัสรหัสผ่านฐานข้อมูลด้วยกุญแจดอกเดียวที่ใช้กับทุกลูกค้า ซึ่งดีกว่าเก็บเป็นข้อความธรรมดาอย่างเห็นได้ชัด และเป็นจุดเริ่มต้นที่สมเหตุสมผล
แต่พอมองต่ออีกขั้นจะเห็นปัญหา — ถ้ากุญแจดอกนั้นรั่ว ลูกค้าทุกรายอยู่ในความเสี่ยงพร้อมกัน และบริการของลูกค้ารายหนึ่งที่รันอยู่บนเครื่องเดียวกันก็ถอดรหัสของอีกรายได้ ทั้งที่ไม่ควรมีเหตุผลอะไรให้ทำได้
การเข้ารหัสที่ไม่มีการแยกขอบเขตจึงป้องกันได้แค่กรณีที่ฐานข้อมูลรั่วโดยไม่มีอย่างอื่นรั่วตามเท่านั้น ซึ่งเป็นสมมติฐานที่มองโลกในแง่ดีเกินไป
เปลี่ยนเป็นกุญแจแยกต่อโดเมน
เราเปลี่ยนให้แต่ละโดเมนมีกุญแจของตัวเอง โดยบริการของแต่ละโดเมนเห็นเฉพาะกุญแจของตัวเองเท่านั้น
ผลคือถ้าบริการของโดเมนหนึ่งถูกเจาะ ผู้บุกรุกก็ยังถอดรหัสข้อมูลของโดเมนอื่นไม่ได้ เพราะกุญแจของโดเมนอื่นไม่ได้อยู่ในมือถึงแม้จะอยู่บนเครื่องเดียวกัน
ทำให้ของเก่ายังใช้ได้ระหว่างเปลี่ยน
การเปลี่ยนวิธีเข้ารหัสในระบบที่มีข้อมูลอยู่แล้วมีข้อบังคับหนึ่งข้อ — ต้องอ่านของเก่าได้ระหว่างที่ยังทยอยเปลี่ยน
เราจึงออกแบบรูปแบบข้อมูลที่เข้ารหัสให้ระบุชื่อกุญแจที่ใช้ไว้ในตัวมันเอง และให้ตัวถอดรหัสดูจากรูปแบบว่าเป็นของยุคไหนแล้วเลือกกุญแจให้ถูก
ข้อมูลที่เข้ารหัสด้วยวิธีเก่าจึงยังอ่านออกทั้งหมด ไม่ต้องหยุดระบบเพื่อแปลงทีเดียวทั้งก้อน
บั๊กที่ซ่อนอยู่จนวันที่เริ่มเข้ารหัสจริง
ตอนที่เราเปิดให้สร้างกุญแจให้โดเมนใหม่อัตโนมัติตอนสมัคร ระบบเริ่มพังทันทีตอนสร้างลูกค้าใหม่
ข้อความที่ขึ้นคือข้อผิดพลาดทั่วไปของการบันทึกข้อมูล ซึ่งไม่ได้บอกอะไรเลย
สาเหตุจริง
ข้อมูลที่เข้ารหัสแล้วมีความยาวกว่าที่คอลัมน์รองรับ
เพราะรูปแบบใหม่ประกอบด้วยหลายส่วนต่อกัน ทั้งเครื่องหมายบอกรุ่น ชื่อกุญแจ และตัวข้อมูลที่เข้ารหัสพร้อมข้อมูลประกอบ รวมแล้วรหัสผ่าน 16 ตัวอักษรกลายเป็นข้อความยาวเกือบ 90 ตัวอักษร ในขณะที่คอลัมน์รองรับได้ 50
ทำไมถึงไม่เคยพังมาก่อน
นี่คือส่วนที่น่าสนใจที่สุด
ความยาวเกินมีมาตั้งแต่ก่อนหน้านี้แล้ว แต่ไม่เคยถูกกระตุ้น เพราะกุญแจตั้งต้นไม่เคยถูกตั้งค่าจริงบนเครื่องใช้งาน
ระบบมีเงื่อนไขว่า ถ้ายังไม่ได้ตั้งกุญแจ ให้เก็บรหัสผ่านแบบไม่เข้ารหัสไปก่อน ซึ่งสั้นพอจะใส่ลงคอลัมน์ได้ การเข้ารหัสจึงไม่เคยทำงานจริง และบั๊กจึงหลบอยู่ได้นาน
พอเปิดให้สร้างกุญแจอัตโนมัติ การเข้ารหัสก็ทำงานจริงเป็นครั้งแรก และบั๊กก็โผล่ทันที
สิ่งที่เราแก้ — สามอย่าง ไม่ใช่อย่างเดียว
- ขยายความกว้างคอลัมน์ ให้เผื่อไว้มากพอ โดยเขียนให้รันซ้ำได้ไม่เสียหาย
- ตรึงค่านี้ไว้ในตัวแบบข้อมูล เพื่อกันไม่ให้เครื่องมือสร้างโค้ดอัตโนมัติย้อนกลับไปเป็นค่าเดิมในอนาคต
- แก้ข้อความผิดพลาดให้บอกสาเหตุจริง — เดิมระบบแสดงเฉพาะข้อความชั้นนอกที่บอกแค่ว่า “เกิดข้อผิดพลาดขณะบันทึก” เราเปลี่ยนให้ไล่ดูสาเหตุที่ซ้อนอยู่ข้างในแล้วแสดงออกมาด้วย
ข้อที่สามไม่ได้แก้บั๊ก แต่แก้สิ่งที่ทำให้บั๊กนี้เสียเวลาไล่หานาน — และมันช่วยทุกบั๊กที่จะเกิดขึ้นในอนาคตบนเส้นทางเดียวกัน
เหตุการณ์ที่สอนมากที่สุด — หมุนกุญแจแล้วล้มกลางทาง
การหมุนกุญแจคือการเปลี่ยนไปใช้กุญแจดอกใหม่ ซึ่งต้องทำสองอย่างให้สำเร็จทั้งคู่
- เขียนไฟล์กุญแจใหม่ทับของเดิม
- ถอดรหัสข้อมูลเดิมด้วยกุญแจเก่าแล้วเข้ารหัสใหม่ด้วยกุญแจใหม่ แล้วบันทึก
ปัญหา
โค้ดเดิมทำข้อ 1 ก่อน แล้วค่อยทำข้อ 2
ระหว่างสองขั้นนั้นระบบอยู่ในสภาพที่ ไฟล์กุญแจเป็นดอกใหม่ แต่ข้อมูลในฐานข้อมูลยังเข้ารหัสด้วยดอกเก่า
ถ้าขั้นที่ 2 ล้มเหลวด้วยเหตุใดก็ตาม ระบบจะค้างอยู่ในสภาพนั้น — ทั้งโดเมนถอดรหัสรหัสผ่านฐานข้อมูลไม่ออก ซึ่งแปลว่าเชื่อมต่อฐานข้อมูลไม่ได้เลย
สิ่งที่เจ็บที่สุดของเรื่องนี้
โค้ดเดิมสำรองไฟล์กุญแจเก่าไว้แล้วตั้งแต่ต้น
แต่ไม่มีเส้นทางไหนในโปรแกรมที่เอามันกลับมาใช้เลย เมื่อเกิดข้อผิดพลาด ตัวดักจับชั้นนอกแค่แสดงข้อความแล้วจบ
พูดอีกอย่างคือ ระบบรู้วิธีกู้ตัวเอง แต่ไม่เคยกู้
เราคิดว่านี่เป็นบทเรียนที่ใช้ได้กว้างกว่าเรื่องกุญแจ — การสำรองข้อมูลที่ไม่มีเส้นทางกู้คืนที่ทดสอบแล้ว มีค่าน้อยกว่าที่ทุกคนคิด
วิธีแก้
- ครอบขั้นที่ 2 ด้วยตัวดักจับของตัวเอง — ถ้าล้มเหลว ให้คัดไฟล์สำรองทับไฟล์กุญแจกลับทันที แล้วค่อยแจ้งข้อผิดพลาดต่อ เพราะข้อมูลในฐานข้อมูลยังเป็นของกุญแจเก่า การคืนไฟล์เก่าจึงทำให้ระบบกลับมาอ่านออกได้จริง ไม่ใช่แค่ลดความเสียหาย
- ล้างข้อมูลที่ค้างอยู่ในหน่วยความจำก่อนกู้ — ไม่งั้นข้อมูลที่เข้ารหัสด้วยกุญแจใหม่ซึ่งค้างอยู่อาจหลุดไปถูกบันทึกในขั้นตอนถัดไปของคำขอเดียวกัน แล้วทำลายสิ่งที่เพิ่งกู้มา
- ถ้ากู้ไม่สำเร็จ ให้บอกเป็นขั้นตอนว่าต้องคัดไฟล์ไหนทับไฟล์ไหน พร้อมรหัสอ้างอิง แทนที่จะแสดงข้อผิดพลาดดิบ ๆ ที่คนอ่านแล้วทำอะไรต่อไม่ถูก
- ถ้าไม่มีไฟล์สำรอง (กรณีที่โดเมนนั้นไม่เคยมีกุญแจมาก่อน) ให้บอกตรง ๆ ว่าต้องไล่ตรวจข้อมูลที่ถอดรหัสไม่ออกเอง ไม่ใช่ปล่อยให้เข้าใจผิดว่ากู้ได้
สลับลำดับขั้นตอนอีกจุด
ระหว่างแก้เรื่องนี้เราพบอีกจุดหนึ่ง — ขั้นตอนเดิมรีสตาร์ตบริการก่อน แล้วค่อยนับว่ายังเหลือข้อมูลกี่รายการที่ยังใช้กุญแจเก่าอยู่
ผลคือคำเตือน “ยังมีข้อมูลอีก N รายการที่ถอดรหัสไม่ออก” โผล่มาหลังจากที่บริการถูกรีสตาร์ตไปแล้ว ซึ่งเป็นการกระทำที่ทำให้คำเตือนนั้นสำคัญ
สลับลำดับให้นับก่อนรีสตาร์ต คำเตือนจึงมาถึงตอนที่ยังตัดสินใจได้อยู่
ข้อความผิดพลาดที่ชี้ผิดทาง
ปัญหาที่ตามมาจากเรื่องกุญแจคือ เมื่อถอดรหัสไม่ได้ ระบบจะเข้าสู่สภาพที่แยกไม่ออกจากกรณีอื่นที่ต่างกันสิ้นเชิง
โค้ดเดิมยุบสามสาเหตุให้เหลือค่าว่างค่าเดียว — ไม่มีข้อมูลในระบบ · มีข้อมูลแต่ถอดรหัสไม่ได้ · ติดต่อฐานข้อมูลกลางไม่ได้เลย
หน้าจอจึงบอกทุกกรณีเหมือนกันว่า “ไม่พบข้อมูล กรุณาสร้างใหม่”
ซึ่งผิดและอันตราย ในกรณีที่พบบ่อยที่สุด — ข้อมูลมีอยู่แล้วแต่ถอดรหัสไม่ได้เพราะกุญแจถูกหมุนไป ผู้ดูแลที่ทำตามคำแนะนำบนหน้าจอจะสร้างข้อมูลใหม่ทับของเดิม
เราแยกให้เป็นสี่สาเหตุที่ต่างกัน พร้อมข้อความภาษาไทยที่บอกทางแก้ที่ตรงกับสาเหตุจริง — กรณีถอดรหัสไม่ได้จะบอกตรง ๆ ว่า “ข้อมูลมีอยู่แล้ว อย่าสร้างใหม่ ให้ไปที่หน้าจัดการกุญแจ”
สรุปสิ่งที่เราได้เรียนรู้
- การเข้ารหัสที่ไม่แยกขอบเขตป้องกันได้น้อยกว่าที่คิด — กุญแจดอกเดียวทั้งระบบแปลว่าความเสียหายจากการรั่วครั้งเดียวคือทุกราย
- โค้ดที่ไม่เคยทำงานจริงคือโค้ดที่ยังไม่รู้ว่าพัง — เส้นทางเข้ารหัสของเราถูกข้ามมาตลอดเพราะกุญแจไม่เคยถูกตั้ง บั๊กจึงรออยู่จนวันที่เปิดใช้จริง
- ขั้นตอนที่แก้ไขหลายอย่างต้องคิดเผื่อว่าล้มกลางทาง — และถ้าสำรองไว้แล้ว ต้องมีเส้นทางกู้คืนที่ทำงานจริง ไม่ใช่แค่มีไฟล์สำรองอยู่เฉย ๆ
- ข้อความผิดพลาดที่ชี้ผิดทางแย่กว่าไม่มีข้อความ — เพราะมันทำให้คนที่พยายามแก้ปัญหาลงมือทำสิ่งที่ทำให้แย่ลง
ทั้งสี่ข้อไม่ได้มาจากการอ่านคู่มือ แต่มาจากการที่ระบบพังจริงแล้วเราไล่ดูว่าทำไม
บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว