Multi-Tenancy คืออะไร ทำไมต้องเข้าใจก่อนเริ่มเขียนโปรแกรม
เวลาเราสร้างซอฟต์แวร์ให้คนใช้ หลายคนมักมองแค่ว่าทำยังไงให้โปรแกรมทำงานได้ แต่ในโลกของ SaaS (ซอฟต์แวร์ที่ใช้งานผ่านอินเทอร์เน็ตโดยไม่ต้องติดตั้งลงเครื่อง) คำว่า Multi-Tenancy (การที่ซอฟต์แวร์หนึ่งชุดรองรับลูกค้าหลายรายโดยใช้ฐานข้อมูลร่วมกัน) คือหัวใจสำคัญของการออกแบบระบบที่เติบโตได้ไกล
ลองจินตนาการถึงอพาร์ตเมนต์ที่มีผู้เช่าหลายห้อง ทุกคนใช้น้ำและไฟจากมิเตอร์หลักตัวเดียวกัน แต่ละห้องมีกุญแจเข้าได้เฉพาะห้องตัวเอง นี่แหละคือแนวคิดของมัน หากเราออกแบบไม่ดี คนในห้อง A อาจจะเปิดประตูเข้าไปดูของในห้อง B ได้ ซึ่งเป็นหายนะของความปลอดภัยข้อมูล
มือใหม่หลายคนมักคิดว่าเรื่องนี้เป็นเรื่องของ Deployment (การนำโปรแกรมขึ้นระบบจริงเพื่อให้คนใช้งาน) หรือแค่การตั้งค่าเซิร์ฟเวอร์ แต่ความจริงแล้วมันคือ Data Model (โครงสร้างการจัดเก็บข้อมูล) ที่เราต้องคิดตั้งแต่บรรทัดแรกที่เริ่มเขียนโค้ด ถ้าวางโครงสร้างผิดตั้งแต่ต้น การแก้ไขในภายหลังจะเหมือนการรื้อตึกทั้งหลังเพื่อเปลี่ยนท่อประปาใหม่เลยทีเดียว
แยกแยะประเภทของ Tenant ให้ชัดก่อนเริ่มออกแบบ
คำว่า Tenant (ผู้เช่าหรือลูกค้าที่ใช้ระบบร่วมกัน) ในมุมมองของโปรแกรมเมอร์ไม่ได้มีความหมายเดียว แต่มันซ่อนความต้องการที่ต่างกันถึง 3 รูปแบบ ถ้าเราเข้าใจไม่ชัด เราจะหลงทางในการเขียนโค้ดแน่นอน
แบบแรกคือ Deployment Tenant (การแยกติดตั้งระบบ) คือการแยกโปรแกรมใครโปรแกรมมันไปเลย วิธีนี้ปลอดภัยที่สุดแต่ดูแลยากที่สุด แบบที่สองคือ Data Tenant (การแยกข้อมูลในฐานข้อมูล) คือใช้โปรแกรมชุดเดียวกันแต่มีการแบ่งพาร์ทิชัน (การแบ่งส่วนข้อมูล) ไว้ชัดเจน แบบสุดท้ายคือ Configuration Tenant (การแยกการตั้งค่า) คือใช้ระบบเดียวกันแต่ปรับแต่งหน้าตาหรือขั้นตอนการทำงานให้แต่ละคนต่างกันได้
ความผิดพลาดของมือใหม่คือการพยายามทำทุกอย่างพร้อมกันโดยไม่วางแผนให้ดี หากเราออกแบบให้รองรับแค่การแยกข้อมูล แต่ลืมเผื่อเรื่องการตั้งค่าที่ต่างกันไปในแต่ละบริษัท เราจะพบว่าโค้ดของเรากลายเป็น Spaghetti Code (โค้ดที่พันกันยุ่งเหยิงจนแก้ไม่ได้) ในเวลาไม่นาน การรู้ว่าเรากำลังแก้ปัญหาประเภทไหนจะช่วยให้เราเลือกใช้เครื่องมือได้ถูกต้อง
Metadata คือข้อมูลที่ต้องจัดการให้เป็นระบบ
ในแอปพลิเคชันทั่วไป เรามักจะเน้นที่การจัดการข้อมูลธุรกิจ เช่น ชื่อลูกค้า หรือยอดสั่งซื้อ แต่ในระบบที่ซับซ้อนขึ้น เราต้องมองว่า Metadata (ข้อมูลที่ใช้อธิบายโครงสร้างของแอป เช่น ชื่อฟิลด์หรือสิทธิ์การเข้าถึง) ก็คือข้อมูลประเภทหนึ่งที่ต้องถูกจัดเก็บแบบแบ่งแยก Tenant ด้วย
หากเราออกแบบให้ฟิลด์ (ช่องกรอกข้อมูล) หรือขั้นตอนการทำงานแชร์กันหมด วันหนึ่งถ้าลูกค้า A อยากเพิ่มฟิลด์พิเศษ แต่ลูกค้า B ไม่ต้องการ เราจะเจอปัญหาหนักทันที เพราะถ้าเราแก้ที่โครงสร้างฐานข้อมูลหลัก มันจะกระทบทุกคนในระบบโดยไม่ตั้งใจ
วิธีจัดการที่ดีคือการออกแบบตารางในฐานข้อมูลให้รองรับการเก็บ Metadata เหล่านี้โดยผูกติดกับ tenant_id (รหัสระบุตัวตนของลูกค้า) เสมอ เพื่อให้ระบบรู้ว่าใครเป็นเจ้าของข้อมูลชุดไหน และใครมีสิทธิ์ปรับแต่งอะไรได้บ้าง นี่คือตัวอย่างพื้นฐานของการเก็บข้อมูลแบบแยกส่วน
// ตัวอย่างการออกแบบตารางที่เก็บข้อมูลโดยระบุเจ้าของ
const record = {
tenant_id: 101, // รหัสของบริษัท A
field_name: 'special_discount',
value: '10%'
};
// การ Query (คำสั่งค้นหาข้อมูล) ต้องระบุ tenant_id เสมอเพื่อความปลอดภัย
// SELECT * FROM records WHERE tenant_id = 101;
อธิบายโค้ด: บรรทัดแรกคือการสร้างออบเจกต์ข้อมูลที่มี tenant_id กำกับไว้ชัดเจน ส่วนคำสั่ง SQL ด้านล่างคือการดึงข้อมูลโดยกรองเฉพาะของบริษัทนั้นๆ เท่านั้น เพื่อป้องกันไม่ให้ข้อมูลรั่วไหลระหว่างกัน
ผลลัพธ์: ระบบจะแสดงผลเฉพาะข้อมูลของบริษัทที่มี tenant_id เป็น 101 เท่านั้น แม้จะมีข้อมูลของบริษัทอื่นอยู่ในตารางเดียวกันก็ตาม
Customization คือจุดที่ความปลอดภัยรั่วไหล
เหตุผลที่คนยอมจ่ายเงินซื้อแพลตฟอร์มของคุณคือ Customization (การปรับแต่งให้เข้ากับความต้องการเฉพาะ) แต่ก็นี่แหละคือจุดที่อันตรายที่สุด เพราะทุกครั้งที่เราเปิดช่องให้ผู้ใช้ปรับแต่งเอง เรากำลังสร้างช่องโหว่ (จุดที่แฮกเกอร์อาจเจาะเข้ามาได้) ให้ระบบโดยไม่รู้ตัว
ถ้าเราอนุญาตให้ลูกค้าเขียนสคริปต์ (ชุดคำสั่งให้คอมพิวเตอร์ทำงาน) ของตัวเองได้ สิ่งที่ต้องระวังคือสคริปต์นั้นต้องถูกรันใน Sandbox (สภาพแวดล้อมจำลองที่ปลอดภัยและถูกจำกัดสิทธิ์) เสมอ เพื่อไม่ให้สคริปต์นั้นแอบไปอ่านข้อมูลของลูกค้าคนอื่นในระบบเดียวกัน
ในฐานะโปรแกรมเมอร์ คุณต้องยึดหลักการที่ว่า การบังคับใช้กฎต้องอยู่ที่ระบบ Runtime (ช่วงเวลาที่โปรแกรมกำลังทำงาน) ไม่ใช่อยู่ที่ตัวผู้สร้างแอปพลิเคชัน เพราะผู้ใช้งานเองนั่นแหละที่จะเป็นคนทำให้กฎความปลอดภัยพังทลายลง หากคุณไม่ล็อกสิทธิ์ไว้ให้แน่นหนาตั้งแต่ระดับฐานข้อมูลหรือตัวประมวลผลคำสั่ง
ระวังปัญหา Fairness หรือความยุติธรรมในการใช้งาน
ปัญหาที่คนขายซอฟต์แวร์มักไม่บอกคือ "เพื่อนบ้านแย่" (Bad Neighbor) เมื่อลูกค้าทุกคนใช้ทรัพยากรร่วมกัน เช่น ฐานข้อมูลหรือหน่วยความจำ ถ้าลูกค้ารายหนึ่งอัปโหลดไฟล์ขนาดใหญ่มากจนระบบค้าง ลูกค้าคนอื่นทั้งหมดจะซวยไปด้วยทันที
การแก้ปัญหานี้ไม่ใช่แค่เรื่องความเร็ว แต่มันคือเรื่องของ Fairness (ความเท่าเทียมในการเข้าถึงทรัพยากร) คุณอาจต้องเขียนโค้ดเพื่อจำกัด Rate Limit (จำนวนครั้งที่เข้าถึงระบบได้ในเวลาที่กำหนด) หรือสร้างคิวงานแยกสำหรับแต่ละ Tenant เพื่อไม่ให้ใครคนใดคนหนึ่งผูกขาดการทำงานของระบบ
ถ้าคุณกำลังฝึกเขียนแอปพลิเคชัน ลองจำลองสถานการณ์ที่มีผู้ใช้งานหลายคนพร้อมกันดูครับ แล้วลองตั้งค่าให้ระบบจำกัดสิทธิ์หรือคิวงานตาม ID ของผู้ใช้ นี่เป็นแบบฝึกหัดที่ยอดเยี่ยมในการเตรียมตัวทำงานจริงในบริษัทที่ทำระบบขนาดใหญ่ เพราะคุณจะได้เรียนรู้วิธีการจัดการทรัพยากรอย่างมีประสิทธิภาพ
บทสรุป: ความท้าทายที่โปรแกรมเมอร์ต้องเผชิญ
สุดท้ายแล้ว Multi-Tenancy ไม่ใช่ฟีเจอร์ที่คุณสามารถเพิ่มเข้าไปทีหลังได้ แต่มันคือปรัชญาการออกแบบที่ต้องฝังอยู่ในทุกบรรทัดของโค้ด หากคุณสามารถทำให้ลูกค้าแต่ละรายรู้สึกว่าพวกเขาคือเจ้าของระบบเพียงคนเดียวได้ นั่นคือความสำเร็จสูงสุดของคุณ
อย่าพยายามหาทางลัดด้วยการเขียนเงื่อนไข if-else ดักหน้าดักหลังไปเรื่อยๆ เพราะวันหนึ่งมันจะหลุดรอดไปได้แน่นอน การออกแบบที่ถูกต้องคือการทำให้ทุก Query และทุกการกระทำในระบบต้องผ่านการตรวจสอบ tenant_id เสมอโดยอัตโนมัติ ซึ่งจะทำให้คุณนอนหลับได้อย่างสบายใจแม้จะมีลูกค้าเพิ่มขึ้นอีกร้อยรายก็ตาม
เริ่มฝึกจากโปรเจกต์เล็กๆ ของคุณเอง เช่น เว็บไซต์จัดการ To-do list ที่มีหลายผู้ใช้ โดยลองบังคับให้ข้อมูลของแต่ละคนถูกแยกออกจากกันอย่างเด็ดขาดด้วย tenant_id ตั้งแต่วันนี้ แล้วคุณจะเข้าใจว่าทำไมเรื่องนี้ถึงไม่ใช่แค่เรื่องของการ Deploy แต่เป็นเรื่องของ Data Model ที่แท้จริง
ที่มา: Multi-Tenancy Is Not a Deployment Model. It Is a Data Model Decision. — DEV Community