← Blog

ย้ายจาก 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 ข้อในหัวข้อก่อนหน้า ไม่มีข้อไหนเลยที่เจอได้โดยไม่ซ้อม

ถ้าคุณกำลังจะทำแบบเดียวกัน

  1. จัดเครื่องพัฒนาให้เหมือนเครื่องจริงก่อนเขียนโค้ดบรรทัดแรก โดยเฉพาะเรื่องสิทธิ์ของบัญชี เพราะปัญหาสิทธิ์เป็นสิ่งที่คอมไพเลอร์และชุดทดสอบจับไม่ได้เลย
  2. คาดไว้ล่วงหน้าว่าจะเจอบั๊กเก่าที่ฐานข้อมูลเดิมกลบไว้ — ฐานข้อมูลที่ผ่อนปรนกว่าย่อมกลบความผิดพลาดไว้ให้ พอย้ายไปฝั่งที่เข้มงวดกว่า สิ่งเหล่านั้นจะโผล่พร้อมกัน และมันคือบั๊กของเราเอง ไม่ใช่ของฐานข้อมูลใหม่
  3. ซ้อมกับข้อมูลจริงบนสภาพแวดล้อมจริง — ปัญหาส่วนใหญ่ที่เราเจอไม่มีทางเห็นจากการอ่านโค้ด
  4. เปลี่ยนทีละเรื่อง อย่าถือโอกาสปรับปรุงอย่างอื่นไปพร้อมกัน เพราะเมื่อเกิดปัญหาจะแยกไม่ออกว่ามาจากไหน

สุดท้าย — เราเขียนบันทึกทุกอย่างที่เจอลงคู่มือการย้ายพร้อมตัวเลขและวิธีตรวจ ไม่ใช่เพื่อความเรียบร้อย แต่เพราะการย้ายลูกค้ารายถัดไปจะต้องใช้ และคนที่ทำอาจไม่ใช่คนเดิม

Freeable

บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว

ดูรายละเอียด →

อ่านต่อ

Freeable

รหัสผ่าน 2026 — อักษรพิเศษ มาตรฐานสากล กฎหมายไทย และ 2FA/3FA ที่ควรรู้

กฎรหัสผ่านที่เราคุ้นเคย — ต้องมีตัวใหญ่ ตัวเล็ก ตัวเลข อักษรพิเศษ และเปลี่ยนทุก 90 วัน — ถูกมาตรฐานสากลถอดออกไปแล้ว บทความนี้รวบรวมว่าปัจจุบันมาตรฐานว่าอย่างไร กฎหมายไทยกำหนดอะไร และ 2FA ช่วยตรงไหนที่รหัสผ่านช่วยไม่ได้

Freeable

ค่าหนึ่งค่ามี 4 หน้าที่ — มาตรฐานการแทนค่า เก็บ · ส่ง · คำนวณ · แสดง

ปัญหาเรื่องวันที่ไม่ใช่ปัญหาของวันที่ แต่เป็นหนึ่งตัวอย่างของปัญหาตระกูลเดียวกัน — ค่าหนึ่งค่าถูกใช้ทำ 4 หน้าที่ที่ต่างกัน พอรูปแบบของหน้าที่หนึ่งรั่วไปอีกหน้าที่ ตัวเลขก็เพี้ยนเงียบ ๆ

Freeable

เวลาในระบบมีหลายชั้น และมันไม่ตรงกัน — บทเรียนจากบั๊กที่ไล่ 8 วันแล้วสรุปผิดหลายรอบ

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