ย้ายจาก SQL Server มา PostgreSQL — บันทึกจากหน้างานจริง
ปี 2026 เราย้าย Freeable ออกจาก SQL Server มาอยู่บน PostgreSQL ทั้งระบบ
บทความนี้ไม่ได้เขียนเพื่อบอกว่าฐานข้อมูลไหนดีกว่า แต่บันทึกว่างานย้ายจริงเจออะไรบ้าง — โดยเฉพาะปัญหาที่ไม่มีในคู่มือ เพราะมันไม่ได้เกิดจากฐานข้อมูล แต่เกิดจากสิ่งที่ฐานข้อมูลเดิมเคยกลบไว้ให้
ทำไมถึงย้าย
เหตุผลหลักคือต้นทุน ค่าลิขสิทธิ์เป็นค่าใช้จ่ายคงที่ต่อเครื่อง และโมเดลธุรกิจของเราคือรับลูกค้าหลายรายบนหลายเครื่อง ต้นทุนนี้จึงคูณตามการเติบโต ไม่ใช่หารเฉลี่ย
งานนี้เป็นชิ้นที่สองต่อจากการย้ายมารันบน Linux เมื่อรีลีสก่อน พอครบสองชิ้นก็ได้ระบบที่ไม่เหลือค่าลิขสิทธิ์อยู่เลย
ส่วนที่ง่ายกว่าที่คิด
การเปลี่ยนตัวเชื่อมฐานข้อมูลตรงไปตรงมา — เปลี่ยนแพ็กเกจใน 8 โปรเจกต์ เปลี่ยนจุดตั้งค่าการเชื่อมต่อ 38 จุด และแก้ชื่อคีย์ในสตริงเชื่อมต่อที่เรียกไม่เหมือนกัน
งานส่วนนี้เยอะแต่คอมไพเลอร์ช่วยหาให้เกือบหมด ใช้เวลาสองวันก็คอมไพล์ผ่าน
ปัญหาคือ “คอมไพล์ผ่าน” ไม่ได้แปลว่าใช้งานได้
แนวคิดที่ไม่มีในอีกฝั่ง
การกันแก้ข้อมูลชนกัน
SQL Server มีคอลัมน์พิเศษที่เปลี่ยนค่าทุกครั้งที่แถวถูกแก้ ใช้ตรวจว่ามีคนอื่นแก้ไปแล้วหรือยัง PostgreSQL ไม่มีในรูปแบบเดียวกัน แต่มีคอลัมน์ภายในที่ทำหน้าที่คล้ายกัน เราจึงผูกการตรวจสอบเข้ากับคอลัมน์นั้นแทน โดยไม่ต้องแก้โค้ดที่เรียกใช้
ชนิดข้อมูลวันเวลา 660 คอลัมน์
PostgreSQL แยกชัดระหว่างวันเวลาที่มีเขตเวลากำกับกับที่ไม่มี ซึ่งเป็นการออกแบบที่ถูก แต่ระบบเดิมเก็บเวลามาตรฐานสากลไว้ในคอลัมน์ที่ไม่มีเขตเวลา แล้วจัดการเรื่องเขตเวลาที่ชั้นแอปพลิเคชัน
เราเลือกคงพฤติกรรมเดิมไว้ เพราะการเปลี่ยนวิธีเก็บเวลาพร้อมกับการย้ายฐานข้อมูล แปลว่าถ้าเกิดปัญหาจะแยกไม่ออกว่ามาจากเรื่องไหน งานย้ายควรเปลี่ยนทีละเรื่อง
เครื่องมือย้ายข้อมูลมาตรฐานใช้ไม่ได้
แผนเดิมคือใช้เครื่องมือย้ายข้อมูลสำเร็จรูปที่เป็นตัวเลือกมาตรฐาน แต่ใช้ไม่ได้ เพราะตัวเชื่อมต่อของมันคุยกับ SQL Server รุ่นใหม่ไม่ได้ ติดที่การเชื่อมต่อแบบเข้ารหัสซึ่งรุ่นใหม่บังคับ
เราเปลี่ยนวิธี — สร้างโครงสร้างฐานข้อมูลจากตัวแบบข้อมูลของโปรแกรมโดยตรง แทนการแปลงจากโครงสร้างเดิม แล้วเขียนเครื่องมือย้ายข้อมูลของเราเองแยกต่างหาก
ผลกลับดีกว่าแผนเดิม เพราะได้โครงสร้างที่ตรงกับที่โปรแกรมคาดหวังจริง ๆ ไม่ใช่โครงสร้างเก่าพร้อมความเพี้ยนที่สะสมมาหลายปี และได้ตั้งต้นการนับเวอร์ชันโครงสร้างใหม่ — จากเดิมที่ลูกค้าใหม่ต้องไล่รันการแก้โครงสร้าง 346 ขั้น เหลือขั้นตอนเดียว
บั๊กที่ทำให้ล็อกอินพังทุกบัญชี
นี่คือปัญหาที่ใหญ่ที่สุดของทั้งงาน และเป็นบทเรียนที่ดีที่สุดด้วย
อาการ
ย้ายเสร็จ ระบบขึ้น หน้าล็อกอินแสดงผลปกติ แต่ล็อกอินไม่ได้เลยสักบัญชี
สาเหตุ
ฐานข้อมูลเดิมของเราตั้งค่าให้ไม่สนตัวพิมพ์เล็กพิมพ์ใหญ่ทั้งฐานข้อมูล ส่วน PostgreSQL เทียบข้อความแบบตรงตัวเป๊ะเสมอ
ระบบยืนยันตัวตนของเราแปลงชื่อผู้ใช้เป็นตัวพิมพ์ใหญ่ทุกเส้นทางที่สร้างผู้ใช้ และตอนล็อกอินก็แปลงเป็นตัวพิมพ์ใหญ่ก่อนค้นหา
แต่ขั้นตอนสร้างลูกค้าใหม่เก็บรหัสผู้ใช้เป็นตัวพิมพ์เล็ก
จุดสำคัญคือ — นี่ไม่ใช่ปัญหาของ PostgreSQL แต่เป็นบั๊กของเราเองที่มีมานาน ฐานข้อมูลเดิมที่ไม่สนตัวพิมพ์ได้กลบมันไว้ให้ทุกวันจนไม่มีใครรู้ว่ามีอยู่
และมันไม่ได้พังแค่การค้นหา — รหัสผ่านถูกเข้ารหัสโดยเอารหัสผู้ใช้มาผสมด้วย เมื่อตัวพิมพ์ไม่ตรง ค่าที่ได้ก็ผิดไปด้วย
สิ่งที่เราตรวจต่อ
เราไม่ได้หยุดที่การแก้จุดที่พัง แต่ไล่หาว่ายังมีที่ไหนอีกที่พึ่งพาพฤติกรรมนี้อยู่ และพบอีกจุดคือการกู้รหัสผ่านทางอีเมล ซึ่งค้นหาอีเมลแบบตรงตัว จะใช้ไม่ได้ทันทีถ้าผู้ใช้พิมพ์อีเมลด้วยตัวพิมพ์ต่างจากตอนสมัคร
ทางเลือกที่พิสูจน์แล้วตัดออก
เราทดสอบดูว่าตั้งให้ทั้งฐานข้อมูลไม่สนตัวพิมพ์แบบเดิมได้ไหม คำตอบคือไม่ได้ — PostgreSQL สร้างฐานข้อมูลแบบเทียบตรงตัวเสมอ ทำเป็นค่าเริ่มต้นทั้งฐานข้อมูลไม่ได้
ทางเลือกที่เหลือมีสองทาง คือกำหนดกฎการเทียบแบบไม่สนตัวพิมพ์เป็นรายคอลัมน์ (เราทดสอบแล้วว่าใช้ได้ครบทั้งการเทียบเท่า การค้นแบบบางส่วน และการใช้ดัชนี) หรือทำให้ตัวพิมพ์ตรงกันตั้งแต่ต้นทาง
เราเลือกทางหลัง เพราะการแก้ที่ต้นทางแก้บั๊กจริง ส่วนการตั้งกฎรายคอลัมน์เป็นการกลบบั๊กต่อไปอีกชั้น — ซึ่งก็คือสิ่งที่ทำให้เราเจอปัญหานี้ตั้งแต่แรก
ซ้อมย้ายข้อมูลจริง 3 รอบ
ระบบใหม่เริ่มบนฐานข้อมูลเปล่า แต่ลูกค้าที่มีข้อมูลเดิมต้องย้ายตาม เราจึงเขียนเครื่องมือย้ายข้อมูลแล้วซ้อมกับข้อมูลจริงบนบริการฐานข้อมูลแบบเช่าของจริง ไม่ใช่บนเครื่องพัฒนา
| ชุดข้อมูล | ตาราง | จำนวนแถว | ผล |
|---|---|---|---|
| ฐานข้อมูลผู้ใช้ | 20 | 11,020 | ผ่าน · ล็อกอินได้จริงหลังย้าย |
| ฐานข้อมูลกิจการ | 94 | 2,702,983 | ผ่าน · 0 ล้มเหลว |
| นำเข้าจากระบบเดิม | 58 | 1,549,711 | ผ่าน · ยอดบัญชีต่าง 0.003% |
การตรวจไม่ได้ดูแค่จำนวนแถวตรงกัน แต่ตรวจว่าข้อความภาษาไทยไม่เพี้ยน คอลัมน์ที่คำนวณอัตโนมัติทำงานถูก และเปิดบิลจริงในระบบได้ โดยระบุเลขที่เอกสารที่ใช้ทดสอบไว้ในบันทึกด้วย
ปัญหาที่เจอเฉพาะตอนซ้อม
1. ลำดับเลขไม่ขยับตามหลังโหลดข้อมูล
ตอนย้ายข้อมูล เราใส่ค่าเลขประจำแถวเข้าไปตรง ๆ เพื่อรักษาความเชื่อมโยงระหว่างตาราง แต่ PostgreSQL ไม่ขยับตัวนับลำดับตาม
ผลคือรายการแรกที่ระบบสร้างหลังย้ายจะได้เลขที่ถูกใช้ไปแล้ว แล้วชนกันทันที
อาการที่เห็นคือ ล็อกอินไม่ได้ ขึ้นข้อผิดพลาดของเซิร์ฟเวอร์ ซึ่งดูไม่เกี่ยวกับการย้ายข้อมูลเลย สาเหตุจริงคือระบบบันทึกเหตุการณ์การเข้าสู่ระบบลงตาราง แล้วเลขชนกันตรงนั้น
แก้โดยให้เครื่องมือย้ายข้อมูลปรับตัวนับลำดับให้ตรงกับค่าสูงสุดที่มีอยู่จริงเป็นขั้นตอนสุดท้ายอัตโนมัติ ทุกคอลัมน์ที่ออกเลขเอง และทำซ้ำได้โดยไม่เสียหาย
2. สิทธิ์ไม่พอบนบริการแบบเช่า
บัญชีที่ใช้บนบริการแบบเช่าไม่ใช่บัญชีผู้ดูแลสูงสุด ทำให้คำสั่งเปลี่ยนเจ้าของฐานข้อมูลล้มเหลว ผลคือฐานข้อมูลถูกสร้างสำเร็จแต่เจ้าของผิดคน ซึ่งทำลายการแยกข้อมูลระหว่างลูกค้าที่เราออกแบบไว้
บั๊กนี้ซ่อนอยู่ตอนพัฒนาเพราะบนเครื่องพัฒนาเราใช้บัญชีผู้ดูแลสูงสุดซึ่งทำได้ทุกอย่าง แก้โดยขอสิทธิ์ที่จำเป็นให้ตัวเองก่อนสั่งเปลี่ยนเจ้าของ
3. ค่าตั้งภาษาที่มีบนเครื่องพัฒนาแต่ไม่มีบนเครื่องจริง
เราตั้งค่าการเรียงลำดับภาษาไทยไว้ (ไม่งั้นรายชื่อลูกค้าจะเรียงไม่เป็น ก-ฮ ซึ่งผู้ใช้เห็นทันทีวันแรก) แต่ค่าที่ระบุไว้ไม่มีบนเครื่องของผู้ให้บริการ เพราะเขาใช้ระบบปฏิบัติการที่ตัดส่วนไม่จำเป็นออก ทำให้สร้างฐานข้อมูลไม่ได้เลย
แก้โดยเปลี่ยนไปใช้ค่าพื้นฐานที่มีอยู่ทุกที่แน่นอน
อีกการตัดสินใจที่เกี่ยวกันคือ เราเลือกวิธีจัดการภาษาแบบที่มากับตัวฐานข้อมูลเอง แทนแบบที่ต้องติดตั้งชุดภาษาที่ระดับระบบปฏิบัติการ เพราะแบบหลังทำไม่ได้บนบริการแบบเช่า
4. ความกว้างของคอลัมน์ไม่ตรงกับข้อมูลจริง
ระหว่างซ้อมพบว่าข้อมูลจริงบางแถวมีรหัสผู้ใช้ยาวเกินความกว้างที่คอลัมน์ปลายทางรองรับ ทำให้บันทึกบิลไม่ได้ — เจอเพราะข้อมูลจริงเท่านั้น ไม่มีทางเห็นจากการอ่านโครงสร้าง
5. เวลาต้องแปลงตอนย้าย
คอลัมน์ที่เก็บ “เวลาที่เกิดเหตุการณ์” ต้องแปลงจากเวลาท้องถิ่นเป็นเวลามาตรฐานสากลระหว่างย้าย ส่วนคอลัมน์ที่เก็บวันที่เอกสารต้องย้ายไปตรง ๆ ห้ามแปลง
เราตรวจด้วยการไล่ค่าเดียวตลอดเส้นทาง — ค่าเดิม 15:39:53 ต้องกลายเป็น 08:39:53 ในฐานข้อมูลใหม่ และต้องแสดงผลกลับมาเป็น 15:39:53 เท่าเดิมบนหน้าจอ
6. โปรแกรมที่ติดตั้งอยู่เก่ากว่าโครงสร้างล่าสุด
ข้อนี้เจอตอนซ้อมรอบสุดท้ายและอันตรายที่สุด — โปรแกรมที่ติดตั้งบนเครื่องอาจเก่ากว่าการแก้โครงสร้างล่าสุด ทำให้การแก้โครงสร้างบางอย่างไม่ถูกนำไปใช้ และข้อมูลที่ถูกต้องบางส่วนถูกตัดทิ้งระหว่างย้าย
เราบันทึกไว้ในคู่มือเป็นข้อบังคับว่าต้องอัปเดตโปรแกรมก่อนย้ายจริงทุกครั้ง
เรื่องที่เจอระหว่างทางแต่ไม่เกี่ยวกับการย้าย
การไล่ตรวจข้อมูลอย่างละเอียดทำให้เจอปัญหาที่มีมานานและร้ายแรงกว่าเรื่องการย้ายเสียอีก — ข้อมูลเงินเดือนและภาษีหัก ณ ที่จ่ายถูกเก็บเป็นทศนิยมลอย
ทศนิยมลอยไม่เหมาะกับการเก็บจำนวนเงิน เพราะบวกกันแล้วได้ค่าที่คลาดเคลื่อนเล็กน้อย ซึ่งสะสมและทำให้ยอดสุทธิกับยอดภาษีเพี้ยนได้
เราเปลี่ยนคอลัมน์กลุ่มนี้ทั้งหมดเป็นชนิดข้อมูลที่เก็บทศนิยมได้แม่นยำ พร้อมไล่แก้โค้ดที่เกี่ยวข้อง
เราเล่าเรื่องนี้เพราะมันแสดงสิ่งหนึ่ง — งานย้ายฐานข้อมูลบังคับให้ต้องมองข้อมูลทุกคอลัมน์อย่างตั้งใจ ซึ่งเป็นโอกาสที่ไม่ค่อยเกิดขึ้นในการทำงานปกติ และสิ่งที่เจอระหว่างนั้นมีค่ามาก
บทเรียนเดียวที่อยู่เบื้องหลังเกือบทุกปัญหา
ถ้าไล่ดูปัญหาทั้งหมดข้างบน จะเห็นว่าเกือบทั้งหมดมีรากเดียวกัน — เครื่องพัฒนาไม่เหมือนเครื่องจริง
- บัญชีบนเครื่องพัฒนาเป็นผู้ดูแลสูงสุด บนเครื่องจริงไม่ใช่
- ชุดภาษาบนเครื่องพัฒนาครบ บนเครื่องจริงไม่ครบ
- ข้อมูลบนเครื่องพัฒนาเป็นข้อมูลทดสอบที่สะอาด ข้อมูลจริงไม่ใช่
เราจึงแก้ที่ราก โดยเปลี่ยนฐานข้อมูลบนเครื่องพัฒนาให้ตรงกับเครื่องจริงในสามเรื่อง — รุ่นเดียวกัน · บัญชีที่ใช้ทำงานเป็นบัญชีสิทธิ์จำกัดเหมือนของจริง (แยกบัญชีผู้ดูแลสูงสุดไว้ต่างหาก) · สร้างฐานข้อมูลด้วยคำสั่งชุดเดียวกันตั้งแต่ตอนเริ่มเครื่อง
ผลคือบั๊กประเภทนี้โผล่ตั้งแต่ตอนเขียนโค้ดบนเครื่องตัวเอง ไม่ใช่ตอนไปติดตั้งให้ลูกค้า
เรารู้ได้อย่างไรว่าย้ายสำเร็จ
เราไม่นับว่าเสร็จเมื่อคอมไพล์ผ่าน แต่นับเมื่อผ่านการตรวจสี่ชั้น
- สร้างลูกค้าใหม่ได้ครบวงจร ตั้งแต่สร้างฐานข้อมูลไปจนถึงลงโครงสร้างตาราง
- ใช้งานจริงบนฐานข้อมูลลูกค้าจริง — อ่าน เพิ่ม แก้แล้วอ่านกลับ โดยเฉพาะข้อความภาษาไทยที่ต้องตรงเป๊ะ
- ซ้อมย้ายข้อมูลจริงบนบริการแบบเช่าของจริง ครบทั้งสามชุดข้อมูล
- ชุดทดสอบอัตโนมัติ 1,535 เคสผ่านทั้งหมด
ข้อที่สามสำคัญที่สุด และเป็นข้อที่ทีมส่วนใหญ่ข้าม เพราะมันช้า ต้องเตรียมของเยอะ และ “น่าจะผ่าน” อยู่แล้ว
แต่ปัญหา 6 ข้อในหัวข้อก่อนหน้า ไม่มีข้อไหนเลยที่เจอได้โดยไม่ซ้อม
ถ้าคุณกำลังจะทำแบบเดียวกัน
- จัดเครื่องพัฒนาให้เหมือนเครื่องจริงก่อนเขียนโค้ดบรรทัดแรก โดยเฉพาะเรื่องสิทธิ์ของบัญชี เพราะปัญหาสิทธิ์เป็นสิ่งที่คอมไพเลอร์และชุดทดสอบจับไม่ได้เลย
- คาดไว้ล่วงหน้าว่าจะเจอบั๊กเก่าที่ฐานข้อมูลเดิมกลบไว้ — ฐานข้อมูลที่ผ่อนปรนกว่าย่อมกลบความผิดพลาดไว้ให้ พอย้ายไปฝั่งที่เข้มงวดกว่า สิ่งเหล่านั้นจะโผล่พร้อมกัน และมันคือบั๊กของเราเอง ไม่ใช่ของฐานข้อมูลใหม่
- ซ้อมกับข้อมูลจริงบนสภาพแวดล้อมจริง — ปัญหาส่วนใหญ่ที่เราเจอไม่มีทางเห็นจากการอ่านโค้ด
- เปลี่ยนทีละเรื่อง อย่าถือโอกาสปรับปรุงอย่างอื่นไปพร้อมกัน เพราะเมื่อเกิดปัญหาจะแยกไม่ออกว่ามาจากไหน
สุดท้าย — เราเขียนบันทึกทุกอย่างที่เจอลงคู่มือการย้ายพร้อมตัวเลขและวิธีตรวจ ไม่ใช่เพื่อความเรียบร้อย แต่เพราะการย้ายลูกค้ารายถัดไปจะต้องใช้ และคนที่ทำอาจไม่ใช่คนเดิม
บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว