← Blog

หลายลูกค้าบนเครื่องเดียว — การแยกที่ต้องแยกจริง ไม่ใช่แยกในโค้ด

Freeable รับลูกค้าหลายรายอยู่บนเครื่องเดียวกัน ซึ่งเป็นวิธีที่ทำให้ต้นทุนต่อลูกค้าหนึ่งรายต่ำพอจะขายได้จริง

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

คำตอบที่เรายอมรับได้มีอย่างเดียวคือ “ไม่ได้ แม้โค้ดจะพลาด”

ทำไมการแยกในโค้ดอย่างเดียวไม่พอ

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

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

ในระบบที่มีหน้าจอหลายร้อยหน้าและมีคนเขียนหลายคนตลอดหลายปี การเดิมพันกับ “ไม่มีใครลืม” ไม่ใช่การออกแบบ แต่เป็นการหวัง

เราจึงวางการแยกไว้หลายชั้น โดยให้ชั้นที่อยู่ล่างสุดเป็นชั้นที่โค้ดของเราแหกไม่ได้

ชั้นที่ 1 — ฐานข้อมูลแยกกันคนละชุด

ลูกค้าแต่ละรายมีฐานข้อมูลของตัวเอง ไม่ได้แชร์ตารางกัน

ผลที่ตามมาคือคำสั่งที่ลืมใส่เงื่อนไขกรองจะไม่ได้ข้อมูลของลูกค้ารายอื่น เพราะข้อมูลนั้นไม่ได้อยู่ในฐานข้อมูลที่กำลังเชื่อมต่ออยู่ตั้งแต่แรก

แต่ละฐานข้อมูลมีบัญชีผู้ใช้ของตัวเองและมีเจ้าของเป็นบัญชีของลูกค้ารายนั้น ไม่ใช่บัญชีกลางที่ใช้สร้าง

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

ชั้นที่ 2 — บัญชีระบบปฏิบัติการแยกต่อโดเมน

ลูกค้าแต่ละรายรันด้วยบัญชีของระบบปฏิบัติการของตัวเอง ไม่ใช่บัญชีเดียวกันทั้งเครื่อง

นี่คือชั้นที่ทำให้การแยกเป็นจริงในระดับที่โค้ดของเราเปลี่ยนไม่ได้ — ไฟล์ของลูกค้ารายหนึ่งตั้งสิทธิ์ไว้ให้เฉพาะบัญชีของรายนั้นอ่านได้ ต่อให้เขียนโค้ดผิดจนพยายามเปิดไฟล์ของรายอื่น ระบบปฏิบัติการก็ปฏิเสธ

ครอบคลุมทั้งไฟล์ข้อมูล พื้นที่ทำงานของบริการ และกุญแจเข้ารหัส

ช่องโหว่ที่เจอระหว่างทำชั้นนี้

ระหว่างย้ายระบบมารันบน Linux เราพบว่าการตั้งค่าเว็บเซิร์ฟเวอร์เดิมเสิร์ฟไฟล์จากโฟลเดอร์ข้อมูลออกไปตรง ๆ

แปลว่าถ้ารู้เส้นทาง ก็เปิดไฟล์ของลูกค้ารายอื่นผ่านเว็บได้ โดยไม่ต้องผ่านโค้ดของเราเลยสักบรรทัด — การกรองข้อมูลในแอปพลิเคชันไม่มีความหมายเลยกับช่องทางนี้

ปิดช่องนี้พร้อมกับไล่ตั้งสิทธิ์ไฟล์ให้กันการอ่านข้ามกันในทุกระดับ

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

ชั้นที่ 3 — กุญแจเข้ารหัสแยกต่อโดเมน

ระบบเว็บต้องมีกุญแจสำหรับเข้ารหัสข้อมูลของผู้ใช้ที่ฝากไว้กับเบราว์เซอร์ เช่นคุกกี้ยืนยันตัวตนและโทเคนกันการปลอมคำขอ

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

เราจึงแยกที่เก็บกุญแจเป็นโฟลเดอร์ต่อโดเมน โดยแต่ละโฟลเดอร์เป็นของบัญชีระบบของโดเมนนั้นและอ่านได้เฉพาะเจ้าของ

เหตุการณ์: ทุกโดเมนล่มพร้อมกันโดยไม่มีใครแตะอะไร

วันหนึ่งทุกโดเมนบนเครื่องขึ้นข้อผิดพลาดของเซิร์ฟเวอร์พร้อมกัน โดยไม่มีการอัปเดตหรือแก้ไขใด ๆ

สาเหตุคือสิทธิ์ของโฟลเดอร์แม่ที่เก็บกุญแจถูกตั้งไว้ผิดไปหนึ่งหลัก

ทำไมต้อง 711 ไม่ใช่ 700

โครงสร้างคือ โฟลเดอร์แม่หนึ่งอัน ข้างในมีโฟลเดอร์ย่อยของแต่ละโดเมน

  • โฟลเดอร์ย่อยตั้งเป็น 700 — เจ้าของเท่านั้นที่เข้าถึงได้ ความลับอยู่ตรงนี้
  • โฟลเดอร์แม่ต้องเป็น 711 — ให้ทุกบัญชี “เดินผ่าน” เข้าไปยังโฟลเดอร์ของตัวเองได้ แต่ไล่ดูรายชื่อไม่ได้ จึงไม่รู้ด้วยซ้ำว่ามีโดเมนอื่นอยู่บนเครื่องนี้

ถ้าตั้งโฟลเดอร์แม่เป็น 700 ผลคือทุกโดเมนเข้าโฟลเดอร์ของตัวเองไม่ได้ เพราะเดินผ่านแม่ไม่ได้ ทุกอย่างล่มพร้อมกัน

ทำไมถึงหาสาเหตุยาก

ประเด็นที่น่าสนใจคือการล่มพร้อมกันทั้งเครื่องกลับหาสาเหตุยากกว่าการล่มทีละราย

เมื่อลูกค้ารายเดียวมีปัญหา เราจะไปดูว่ารายนั้นต่างจากรายอื่นตรงไหน แต่เมื่อทุกรายพังพร้อมกัน สัญชาตญาณแรกคือไปมองหาสิ่งที่เพิ่งเปลี่ยน ทั้งที่ในกรณีนี้ไม่มีใครเปลี่ยนอะไรเลย

รากของปัญหา — สเปคเดียวกันเขียนไว้สองที่

สิ่งที่ทำให้เรื่องนี้เกิดขึ้นได้คือโค้ดติดตั้งทำถูกอยู่แล้ว — มันตั้งโฟลเดอร์แม่เป็น 711 และโฟลเดอร์ย่อยเป็น 700 ตรงตามสเปคที่เขียนไว้

แต่สคริปต์ตั้งเครื่องอีกตัวหนึ่งตั้งโฟลเดอร์แม่เป็น 700

ผลคือใครรันทีหลังชนะ และสคริปต์ตั้งเครื่องรันทุกครั้งที่เครื่องบูต จึงชนะเสมอ

เราแก้ที่สามจุด และจุดที่อันตรายที่สุดไม่ใช่จุดที่ทำให้เกิดเหตุวันนั้น

  1. สคริปต์เมาต์ดิสก์ข้อมูล — อันตรายที่สุด เพราะรันทุกครั้งที่รีสตาร์ตเครื่อง ย้ายเครื่อง หรือทำตามคู่มือย้ายระบบ แปลว่ามันยิงเครื่องจริงได้ทั้งเครื่อง
  2. สคริปต์ของคอนเทนเนอร์สำหรับพัฒนา — ตัวที่ทำให้เกิดเหตุในวันนั้น
  3. เอกสารคู่มือ — ที่สอนให้ตั้งเป็น 700 ซึ่งแปลว่าใครทำตามก็ยิงตัวเอง

ข้อสุดท้ายสำคัญไม่แพ้สองข้อแรก เพราะเอกสารที่ผิดจะสร้างปัญหาเดิมซ้ำได้เรื่อย ๆ ผ่านคนที่ตั้งใจทำถูก

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

เครื่องมือที่ต้องมีเมื่อดูแลหลายลูกค้า

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

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

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

สิ่งที่เรื่องนี้บอกเกี่ยวกับการเลือกผู้ให้บริการ

เวลาประเมินระบบที่รับลูกค้าหลายรายบนโครงสร้างเดียวกัน คำถามที่ควรถามไม่ใช่ “ปลอดภัยไหม” เพราะทุกคนตอบว่าปลอดภัย

คำถามที่ได้คำตอบจริงคือ “ถ้าโค้ดพลาดไปหนึ่งจุด อะไรจะกันไว้เป็นชั้นถัดไป”

ถ้าคำตอบคือ “เรากรองข้อมูลทุกจุด” แปลว่าไม่มีชั้นถัดไป — ความปลอดภัยทั้งหมดวางอยู่บนความไม่ผิดพลาดของคน

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

Freeable

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

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

อ่านต่อ

Freeable

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

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

Freeable

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

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

Freeable

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

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