ตัวเลือกโมเดลข้อมูลสำหรับ SaaS แบบหลายผู้เช่า สำหรับ backend แบบ no-code
ตัวเลือกโมเดลข้อมูลสำหรับ SaaS แบบ multi-tenant กำหนดความปลอดภัย การรายงาน และประสิทธิภาพ เปรียบเทียบ tenant_id, schema แยก และฐานข้อมูลแยก พร้อมข้อแลกเปลี่ยนชัดเจน

ปัญหา: แยกผู้เช่าให้ชัดเจนโดยไม่ทำให้ระบบช้าลง
Multi-tenancy หมายความว่าโปรดักต์ซอฟต์แวร์ตัวเดียวให้บริการลูกค้าหลายราย (ผู้เช่า) และแต่ละผู้เช่าต้องเห็นข้อมูลของตัวเองเท่านั้น จุดยากคือการทำให้เรื่องนี้เกิดขึ้นอย่างสม่ำเสมอ: ไม่ใช่แค่บนหน้าจอเดียว แต่ต้องครอบคลุมทุก API call, แผงผู้ดูแล, การส่งออกข้อมูล และงานเบื้องหลัง
โครงสร้างข้อมูลของคุณมีผลต่อการดำเนินงานประจำวันมากกว่าที่ทีมมักคาดคิด มันกำหนดสิทธิ์ การรายงาน ความเร็วการสืบค้นเมื่อเติบโต และความเสี่ยงของบั๊ก "เล็กๆ" ที่อาจรั่วข้อมูลได้ หากขาดตัวกรองตัวเดียวก็อาจเกิดการรั่วไหล แยกมากเกินไปก็ทำให้การรายงานกลายเป็นภาระ
มีสามวิธีที่พบบ่อยในการจัดโครงสร้างข้อมูลแบบ multi-tenant:
- ฐานข้อมูลเดียวที่แต่ละตารางมี
tenant_id - schema แยกตามผู้เช่าในฐานข้อมูลเดียว
- ฐานข้อมูลแยกตามผู้เช่า
แม้คุณจะสร้างด้วย no-code backend ข้อแลกเปลี่ยนเดียวกันก็ยังใช้ได้ เครื่องมืออย่าง AppMaster สร้างโค้ด backend และโครงสร้างฐานข้อมูลจริงจากการออกแบบของคุณ ดังนั้นการตัดสินใจตอนต้นจะสะท้อนในพฤติกรรมและประสิทธิภาพเมื่อใช้งานจริงอย่างรวดเร็ว
ลองนึกถึงเครื่องมือ helpdesk หากแต่ละแถวของ ticket มี tenant_id จะง่ายต่อการสืบค้น "ตั๋วเปิดทั้งหมด" แต่คุณต้องบังคับการตรวจสอบผู้เช่าทุกจุด หากแต่ละผู้เช่ามี schema หรือฐานข้อมูลของตัวเอง การแยกจะเข้มแข็งขึ้นโดยค่าเริ่มต้น แต่การรายงานข้ามผู้เช่า (เช่น "เวลาปิดเฉลี่ยของลูกค้าทั้งหมด") จะต้องทำงานหนักขึ้น
จุดมุ่งหมายคือการแยกที่คุณวางใจได้โดยไม่เพิ่มแรงเสียดทานต่อการรายงาน ฝ่ายซัพพอร์ต และการเติบโต
วิธีเลือกอย่างรวดเร็ว: 4 คำถามที่ช่วยจำกัดตัวเลือก
อย่าเริ่มจากทฤษฎีฐานข้อมูล เริ่มจากการใช้งานผลิตภัณฑ์และสิ่งที่คุณต้องทำทุกสัปดาห์
สี่คำถามที่มักทำให้คำตอบชัดเจน
-
ข้อมูลมีความอ่อนไหวแค่ไหน และคุณอยู่ภายใต้กฎเข้มงวดหรือไม่? สายสุขภาพ การเงิน หรือสัญญาลูกค้าที่เข้มงวดมักจะชี้ไปทางการแยกที่เข้มขึ้น (schema แยกหรือฐานข้อมูลแยก) เพราะช่วยลดความเสี่ยงและทำให้ง่ายต่อการตรวจสอบ
-
คุณต้องการการรายงานข้ามผู้เช่าบ่อยไหม? ถ้าคุณต้องการเมตริก "ลูกค้าทั้งหมด" บ่อย ๆ (การใช้งาน รายได้ ประสิทธิภาพ) ฐานข้อมูลเดียวกับ
tenant_idมักเป็นทางเลือกที่ง่ายที่สุด ฐานข้อมูลแยกทำให้ยากขึ้นเพราะต้องสืบค้นจากหลายที่แล้วรวมผล -
ผู้เช่าจะแตกต่างกันแค่ไหน? ถ้าผู้เช่าต้องการฟิลด์เฉพาะ workflow เฉพาะ หรือการผสานระบบเฉพาะตัว schema หรือตัวเลือกฐานข้อมูลแยกอาจช่วยลดโอกาสที่การเปลี่ยนแปลงจะกระทบคนอื่น หากผู้เช่าส่วนใหญ่มีโครงสร้างเหมือนกัน
tenant_idจะยังคงสะอาด -
ทีมของคุณสามารถปฏิบัติการอะไรได้จริง? ยิ่งแยกมากขึ้น มักหมายถึงงานมากขึ้น: สำรองข้อมูลมากขึ้น การรัน migrations มากขึ้น และชิ้นส่วนที่ต้องดูแลมากขึ้น ซึ่งเพิ่มจุดที่ความล้มเหลวอาจซ่อนอยู่
แนวทางปฏิบัติคือสร้างต้นแบบจากสองตัวเลือกที่ดีที่สุดของคุณ จากนั้นทดสอบจุดที่เจ็บปวดจริง ๆ: กฎสิทธิ์ การสืบค้นรายงาน และวิธีการปล่อยการเปลี่ยนแปลงเมื่อโมเดลพัฒนา
แนวทางที่ 1: ฐานข้อมูลเดียวโดยมี tenant_id ในทุกแถว
นี่เป็นการตั้งค่าที่พบบ่อยที่สุด: ลูกค้าทุกคนใช้ตารางเดียวกัน และเราจัดเก็บ tenant_id กับทุกเรคคอร์ดที่เป็นของผู้เช่า การปฏิบัติการง่ายเพราะคุณมีฐานข้อมูลเดียวและการรัน migrations ครั้งเดียว
กฎคือเข้มงวด: ถ้าแถวเป็นของผู้เช่า ต้องมี tenant_id และทุกการสืบค้นต้องกรองด้วยมัน ตารางที่เป็นของผู้เช่ามักรวมถึง users, roles, projects, tickets, invoices, messages, metadata ของไฟล์ และตารางเชื่อมที่เชื่อมข้อมูลของผู้เช่า
เพื่อ ลดการรั่วไหล ให้ถือว่า tenant_id เป็นสิ่งที่ต่อรองไม่ได้:
- กำหนด
tenant_idให้เป็นคอลัมน์บังคับ (NOT NULL) ในตารางที่เป็นของผู้เช่า - เพิ่มดัชนีที่ขึ้นต้นด้วย
tenant_id(เช่นtenant_id, created_at) - ทำกฎความเป็นเอกลักษณ์ให้รวม
tenant_id(เช่น อีเมลเฉพาะต่อผู้เช่า) - ส่ง
tenant_idผ่านทุก API และ flow ทางธุรกิจ ไม่ใช่แค่ฟอร์ม UI - บังคับใน backend ไม่ใช่แค่ตัวกรองฝั่งไคลเอนต์
ใน PostgreSQL นโยบาย row-level security สามารถเป็นตาข่ายความปลอดภัยที่เข้มแข็ง โดยเฉพาะเมื่อการสืบค้นถูกสร้างขึ้นแบบไดนามิก
ข้อมูลอ้างอิงมักตกอยู่ในสองกลุ่ม: ตารางที่แชร์ (เช่น countries) ที่ไม่มี tenant_id และ catalog ที่มีขอบเขตของผู้เช่า (เช่น แท็กหรือ pipeline ที่กำหนดเอง) ซึ่งมี tenant_id
ถ้าคุณสร้างด้วย AppMaster นิสัยง่าย ๆ ที่ป้องกันเหตุการณ์ส่วนใหญ่คือ: ตั้งค่า tenant_id จาก tenant ของผู้ใช้ที่ยืนยันตัวตนก่อนการสร้างหรืออ่านใด ๆ ใน Business Process ของคุณ และรักษารูปแบบนั้นให้สม่ำเสมอ
ผลกระทบด้านสิทธิ์: สิ่งที่เปลี่ยนไปตามแต่ละแนวทาง
สิทธิ์คือจุดที่ multi-tenancy จะสำเร็จหรือพัง รูปแบบข้อมูลที่คุณเลือกเปลี่ยนวิธีเก็บผู้ใช้ วิธีการจำกัดขอบเขตการสืบค้น และวิธีป้องกันเหตุ "อุ๊ป" ในหน้าผู้ดูแล
กับฐานข้อมูลเดียวและ tenant_id บ่อยครั้งทีมจะใช้ตาราง Users ร่วมและเชื่อมผู้ใช้แต่ละคนกับ tenant และกับ role หนึ่งหรือมากกว่า กฎใหญ่ยังคงเหมือนเดิม: ทุกการอ่านและเขียนต้องมีขอบเขตผู้เช่า แม้กระทั่งตาราง "เล็ก" อย่าง settings, tags, หรือ logs
กับ schema แยก มักเก็บชั้น identity ร่วม (login, password, MFA) ไว้ที่เดียว ขณะที่ข้อมูลของผู้เช่าอยู่ใน schema ต่อผู้เช่า การจัดการสิทธิ์กลายเป็นปัญหาการกำหนดเส้นทาง: แอปต้องชี้การสืบค้นไปยัง schema ที่ถูกต้องก่อนให้ตรรกะทางธุรกิจทำงาน
กับฐานข้อมูลแยก การแยกจะเข้มแข็งที่สุด แต่ตรรกะสิทธิ์ย้ายไปสู่โครงสร้างพื้นฐาน: เลือกการเชื่อมต่อฐานข้อมูลที่ถูกต้อง จัดการ credential และจัดการบัญชีพนักงานระดับ "global"
ในทั้งสามแนวทาง รูปแบบบางอย่างช่วยลดความเสี่ยงข้ามผู้เช่าได้สม่ำเสมอ:
- ใส่
tenant_idลงใน session หรือ claims ของ auth token และถือว่าเป็นจำเป็น - รวมการตรวจสอบผู้เช่าไว้ในที่เดียว (middleware หรือ Business Process ที่ใช้ร่วมกัน) ไม่กระจัดกระจายตาม endpoints
- ในเครื่องมือ admin แสดงบริบทผู้เช่าอย่างชัดเจนและต้องให้สลับผู้เช่าอย่างชัดเจน
- สำหรับการเข้าถึงของฝ่ายซัพพอร์ต ให้ใช้ impersonation พร้อมบันทึกการตรวจสอบ
ใน AppMaster โดยทั่วไปหมายถึงเก็บบริบทผู้เช่าทันทีหลังการยืนยันตัวตนและนำกลับมาใช้ใน API endpoints และ Business Processes เพื่อให้การสืบค้นทุกครั้งยังคงอยู่ในขอบเขต ผู้ดูแลควรเห็นคำสั่งซื้อหลังจากแอปตั้งบริบทผู้เช่า ไม่ใช่เพราะ UI ดันกรองอย่างถูกต้องบังเอิญ
การรายงานและประสิทธิภาพกับโมเดล tenant_id
กับแนวทางฐานข้อมูลเดียว tenant_id การรายงานมักตรงไปตรงมา แดชบอร์ดระดับโลก (MRR, การลงชื่อสมัคร, การใช้งาน) สามารถรันคำสั่งสืบค้นเดียวครอบคลุมทุกคน และรายงานระดับผู้เช่าก็คือคำสั่งเดียวกันพร้อมตัวกรอง
ข้อแลกเปลี่ยนคือประสิทธิภาพเมื่อเวลาผ่านไป เมื่อตารางเติบโต ผู้เช่ารายเดียวที่มีปริมาณมากอาจเป็น "neighbor ที่มีเสียงดัง" โดยสร้างแถวมากขึ้น เขียนมากขึ้น และทำให้การสืบค้นทั่วไปช้าลงหากฐานข้อมูลต้องสแกนมากเกินไป
การทำดัชนีช่วยให้โมเดลนี้สุขภาพดี ควรให้การอ่านที่มีขอบเขตผู้เช่าส่วนใหญ่ใช้ดัชนีที่ขึ้นต้นด้วย tenant_id เพื่อให้ฐานข้อมูลกระโดดไปยังชุดข้อมูลของผู้เช่า
เกณฑ์พื้นฐานที่ดี:
- เพิ่มดัชนีประกอบโดยให้
tenant_idเป็นคอลัมน์แรก (เช่นtenant_id + created_at,tenant_id + status,tenant_id + user_id) - เก็บดัชนีที่เป็น global จริง ๆ ไว้เมื่อจำเป็นสำหรับการสืบค้นข้ามผู้เช่า
- ระวังการ join และตัวกรองที่ "ลืม"
tenant_idซึ่งทำให้เกิดการสแกนช้า
การเก็บรักษา (retention) และการลบก็ต้องมีแผน เพราะประวัติของผู้เช่าคนเดียวอาจทำให้ตารางอ้วนขึ้นสำหรับทุกคน หากผู้เช่ามีนโยบาย retention ต่างกัน ให้พิจารณา soft deletes ร่วมกับการจัดเก็บถาวรตามตารางเวลาต่อผู้เช่า หรือย้ายแถวเก่าไปยังตาราง archive ที่มีคีย์ tenant_id
แนวทางที่ 2: schema แยกต่อผู้เช่า
กับ schema แยก คุณยังใช้ฐานข้อมูล PostgreSQL เดียว แต่แต่ละผู้เช่าได้ schema ของตัวเอง (เช่น tenant_42) ตารางภายใน schema นั้นเป็นของผู้เช่าเท่านั้น รู้สึกเหมือนให้ลูกค้าแต่ละรายมี "มินิ-ฐานข้อมูล" โดยไม่ต้องรันหลายฐานข้อมูลจริง
การตั้งค่าทั่วไปคือเก็บบริการร่วมใน shared schema และข้อมูลผู้เช่าใน tenant schemas แยก ความแบ่งมักเกี่ยวกับอะไรที่ต้องแชร์ระหว่างลูกค้าทั้งหมดเทียบกับอะไรที่ต้องไม่ปะปนกัน
การแบ่งแบบตัวอย่าง:
- shared schema: ตาราง tenants, แผนบริการ, บันทึกการเรียกเก็บเงิน, การตั้งค่า feature flag
- tenant schema: ตารางธุรกิจเช่น orders, tickets, inventory, projects, ฟิลด์ที่กำหนดเอง
- ทั้งสองด้าน (ขึ้นกับผลิตภัณฑ์): users และ roles โดยเฉพาะถ้าผู้ใช้สามารถเข้าถึงหลายผู้เช่าได้
โมเดลนี้ลดความเสี่ยงของการ join ข้ามผู้เช่าเพราะตารางอยู่คนละ namespace และยังทำให้การสำรองหรือกู้คืนผู้เช่าเดียวทำได้ง่ายกว่า
สิ่งที่ทำให้ทีมประหลาดใจก็คือ migrations เมื่อคุณเพิ่มตารางหรือคอลัมน์ คุณต้องใช้การเปลี่ยนแปลงกับทุก tenant schema หากมี 10 ผู้เช่าจะจัดการง่าย แต่กับ 1,000 ผู้เช่าคุณต้องมีกระบวนการ: ติดตามเวอร์ชันสกีม่า รัน migrations เป็นแบตช์ และล้มเหลวอย่างปลอดภัยเพื่อไม่ให้ tenant หนึ่งล้มเหลวขวางคนอื่น
บริการร่วมเช่น auth และ billing มักอยู่ภายนอก tenant schemas รูปแบบปฏิบัติคือ auth ร่วม (ตาราง user หนึ่งตารางกับตารางสมาชิก tenant) และ billing ร่วม (Stripe customer IDs, ใบแจ้งหนี้) ขณะที่ tenant schemas เก็บข้อมูลธุรกิจของผู้เช่า
ถ้าคุณใช้ AppMaster ให้วางแผนตั้งแต่ต้นว่าการออกแบบใน Data Designer จะแมปไปยัง shared vs tenant schemas อย่างไร และรักษาบริการร่วมให้เสถียรเพื่อให้ tenant schemas พัฒนาโดยไม่ทำให้การล็อกอินหรือการชำระเงินพัง
การรายงานและประสิทธิภาพกับ schema แยก
Schema แยกให้การแยกที่เข้มแข็งขึ้นโดยค่าเริ่มต้นมากกว่าโมเดล tenant_id เพราะตารางแยกกันจริงและสิทธิ์สามารถตั้งระดับ schema ได้
การรายงานยอดเยี่ยมเมื่อรายงานส่วนใหญ่เป็นต่อผู้เช่า คำสั่งสืบค้นจะง่ายเพราะคุณอ่านจากตารางของผู้เช่าเดียวโดยไม่ต้องกรองตารางร่วม โมเดลนี้ยังรองรับ "ผู้เช่าพิเศษ" ที่ต้องการตารางเพิ่มหรือคอลัมน์เฉพาะโดยไม่บังคับให้ทุกคนต้องถือโครงสร้างนั้น
การรายงานเชิงรวมสำหรับผู้เช่าทั้งหมดคือจุดที่ schema เริ่มมีปัญหา คุณต้องมี layer รายงานที่สามารถสืบค้นหลาย schema ได้ หรือต้องเก็บตารางสรุปใน shared schema แล้วคัดลอกข้อมูลมาวันละครั้ง
รูปแบบที่พบบ่อย:
- แดชบอร์ดต่อผู้เช่าที่สืบค้นเฉพาะ schema ของผู้เช่า
- analytics schema ศูนย์กลางที่มี nightly rollups จากแต่ละผู้เช่า
- งาน export ที่คัดลอกข้อมูลผู้เช่าไปยังรูปแบบที่เหมาะกับ data warehouse
ประสิทธิภาพมักดีสำหรับงานระดับผู้เช่า ดัชนีมีขนาดเล็กต่อผู้เช่า และการเขียนหนักใน schema หนึ่งไม่ค่อยส่งผลกระทบต่ออีก schema หนึ่ง ข้อเสียคือต้นทุนการดำเนินงาน: การ provision ผู้เช่าใหม่หมายถึงการสร้าง schema, รัน migrations และรักษาความสอดคล้องของทุก schema เมื่อโมเดลเปลี่ยน
Schema เหมาะเมื่อคุณต้องการการแยกที่เข้มขึ้นโดยไม่ต้องรันหลายฐานข้อมูล หรือเมื่อคุณคาดว่าจะมีการปรับแต่งต่อผู้เช่า
แนวทางที่ 3: ฐานข้อมูลแยกต่อผู้เช่า
กับฐานข้อมูลแยก ผู้ใช้แต่ละรายจะได้ฐานข้อมูลของตัวเอง (หรือฐานข้อมูลของตัวเองบนเซิร์ฟเวอร์เดียว) นี่เป็นตัวเลือกที่แยกมากที่สุด: ถ้าข้อมูลของผู้เช่าหนึ่งเสียหายหรือโหลดสูง มันไม่น่าจะกระจายไปยังคนอื่น
เหมาะอย่างยิ่งกับสภาพแวดล้อมที่มีการควบคุม (สุขภาพ การเงิน รัฐบาล) หรือลูกค้าระดับองค์กรที่คาดหวังการแยกเข้มงวด กฎการเก็บรักษาข้อมูล หรือประสิทธิภาพเฉพาะ
การ onboard กลายเป็น workflow การ provision เมื่อมีผู้เช่าใหม่ ระบบของคุณต้องสร้างหรือโคลนฐานข้อมูล รันสกีมาพื้นฐาน (ตาราง ดัชนี ข้อจำกัด) สร้างและเก็บ credential อย่างปลอดภัย และ route คำขอ API ไปยังฐานข้อมูลที่ถูกต้อง
ถ้าคุณสร้างด้วย AppMaster การตัดสินใจเชิงออกแบบสำคัญคือตำแหน่งเก็บไดเรกทอรีผู้เช่า (แผนที่ศูนย์กลางจากผู้เช่าไปยังการเชื่อมต่อฐานข้อมูล) และวิธีการรับประกันว่าทุกคำขอใช้การเชื่อมต่อที่ถูกต้อง
การอัปเกรดและการรัน migrations เป็นข้อแลกเปลี่ยนหลัก การเปลี่ยนแปลงสกีม่าไม่ใช่แค่ "รันครั้งเดียว" แต่เป็น "รันสำหรับทุกผู้เช่า" ซึ่งเพิ่มงานปฏิบัติการและความเสี่ยง ทีมมักจะ version สกีม่าและรัน migrations เป็นงานควบคุมที่ติดตามความคืบหน้าต่อผู้เช่า
ข้อดีคือการควบคุม คุณสามารถย้ายผู้เช่าขนาดใหญ่ก่อน ดูผลกระทบ แล้วค่อย ๆ ปล่อยการเปลี่ยนแปลง
การรายงานและประสิทธิภาพกับฐานข้อมูลแยก
ฐานข้อมูลแยกทำให้คิดง่ายขึ้น ข้อผิดพลาดการอ่านข้ามผู้เช่ามักเกิดได้น้อยลง และความผิดพลาดด้านสิทธิ์มักกระทบเพียงผู้เช่ารายเดียว
ประสิทธิภาพเป็นจุดแข็ง การสืบค้นหนัก การนำเข้าข้อมูลขนาดใหญ่ หรือรายงาน runaway ใน Tenant A จะไม่ช้าลง Tenant B นี่เป็นการป้องกันที่ดีต่อ noisy neighbors และช่วยให้คุณปรับแต่งทรัพยากรต่อผู้เช่าได้
ข้อแลกเปลี่ยนคือการรายงานระดับโลกจะยากที่สุดเพราะข้อมูลถูกแยกทางกายภาพ รูปแบบที่ใช้ได้จริงรวมถึงคัดลอกเหตุการณ์หรือตารางสำคัญไปยังฐานข้อมูลรายงานศูนย์กลาง ส่งเหตุการณ์ไปยัง warehouse, รันรายงานต่อผู้เช่าแล้วรวบรวมผล (เมื่อจำนวนผู้เช่าไม่มาก), และแยกเมตริกผลิตภัณฑ์ออกจากข้อมูลลูกค้า
ต้นทุนการดำเนินงานเป็นปัจจัยใหญ่ การมีหลายฐานข้อมูลหมายถึงการสำรอง ขึ้นเวอร์ชัน มอนิเตอร์ และตอบสนองต่อเหตุการณ์มากขึ้น คุณยังอาจเจอขีดจำกัดการเชื่อมต่อได้เร็วกว่าสำหรับแต่ละผู้เช่าที่ต้องการ connection pool ของตัวเอง
ข้อผิดพลาดทั่วไปที่ทำให้เกิดการรั่วไหลของข้อมูลหรือความลำบากในภายหลัง
ปัญหา multi-tenant ส่วนใหญ่ไม่ใช่ความล้มเหลวการออกแบบครั้งใหญ่ แต่เป็นการละเลยเล็กน้อยที่เติบโตเป็นบั๊กด้านความปลอดภัย การรายงานยุ่ง และการแก้ไขที่แพง Multi-tenancy ทำงานได้เมื่อการแยกผู้เช่าเป็นนิสัย ไม่ใช่ฟีเจอร์ที่ต่อเติมทีหลัง
การรั่วไหลที่พบบ่อยคือการลืมฟิลด์ผู้เช่าในตารางหนึ่งโดยเฉพาะตารางเชื่อมเช่น user_roles, invoice_items, หรือ tags ทุกอย่างดูปกติจนกระทั่งรายงานหรือการค้นหาที่ join ผ่านตารางนั้นดึงแถวจากผู้เช่าอื่น
ปัญหาบ่อยอีกอย่างคือแดชบอร์ด admin ที่ bypass การกรองผู้เช่า มักเริ่มจาก "แค่อันเดียวสำหรับซัพพอร์ต" แล้วถูกนำกลับมาใช้ซ้ำ เครื่องมือ no-code ไม่ได้ลดความเสี่ยงนี้: ทุกการสืบค้น, Business Process, และ endpoint ที่อ่านข้อมูลผู้เช่าต้องมีขอบเขตผู้เช่าเหมือนกัน
ID ก็อาจทำให้เกิดปัญหาได้ หากคุณแชร์ ID ที่อ่านง่ายข้ามผู้เช่า (เช่น order_number = 1001) แล้วถือว่าเป็นเอกลักษณ์ระดับโลก เครื่องมือ support และการผสานระบบจะผสมข้อมูลกัน เก็บตัวระบุที่มีขอบเขตผู้เช่าแยกจาก primary key ภายใน และรวมบริบทผู้เช่าในการค้นหา
สุดท้าย ทีมมักประเมินค่าต่ำไปสำหรับ migrations และ backups ขณะที่ระบบเติบโต สิ่งที่ง่ายกับ 10 ผู้เช่า อาจช้าและเสี่ยงกับ 1,000 ผู้เช่า
เช็คอย่างรวดเร็วที่ป้องกันความเจ็บปวดส่วนใหญ่:
- ทำให้ความเป็นเจ้าของผู้เช่าเป็นชัดเจนในทุกตาราง รวมถึงตารางเชื่อม
- ใช้รูปแบบการ scoping ผู้เช่าเดียวและนำกลับมาใช้ซ้ำทุกที่
- ตรวจสอบให้แน่ใจว่ารายงานและการส่งออกไม่สามารถรันโดยไม่มีขอบเขตผู้เช่า (ยกเว้นเป็น global จริง ๆ)
- หลีกเลี่ยงตัวระบุที่กำกวมต่อผู้เช่าใน API และเครื่องมือ support
- ฝึกขั้นตอนการกู้คืนและการรัน migration ตั้งแต่เนิ่น ๆ ไม่ใช่หลังการเติบโต
ตัวอย่าง: เจ้าหน้าที่ support ค้นหา "invoice 1001" แล้วดึงข้อมูลผิดผู้เช่าเพราะการค้นหาไม่ใส่ขอบเขตผู้เช่า นี่คือบั๊กเล็ก ๆ ที่มีผลกระทบใหญ่
เช็คลิสต์สั้น ๆ ก่อนตัดสินใจ
ก่อนล็อกโมเดล multi-tenant ให้รันการทดสอบบางอย่าง เป้าหมายคือจับการรั่วไหลข้อมูลตั้งแต่ต้นและยืนยันว่าตัวเลือกของคุณยังใช้ได้เมื่อขนาดตารางใหญ่ขึ้น
การตรวจสอบเร็วที่ทำได้ภายในวันเดียว
- พิสูจน์การแยกข้อมูล: สร้างสองผู้เช่า (A และ B), เพิ่มเรคคอร์ดที่คล้ายกัน แล้วยืนยันว่าการอ่านและการอัปเดตทุกอย่างถูกจำกัดให้เป็นผู้เช่าที่ active อย่าไว้วางใจตัวกรอง UI เพียงอย่างเดียว
- ทดสอบการข้ามสิทธิ์: เข้าเป็นผู้ใช้ของ Tenant A แล้วพยายามเปิด แก้ไข หรือลบเรคคอร์ดของ Tenant B โดยเปลี่ยนแค่ ID ของระเบียน หากสำเร็จ ให้ถือว่าเป็นบล็อกการปล่อย
- ความปลอดภัยเส้นทางเขียน: ยืนยันว่าเรคคอร์ดใหม่ได้รับค่า
tenant_idถูกต้อง (หรือไปอยู่ใน schema/database ถูกต้อง) แม้ถูกสร้างผ่าน background jobs, imports หรือ automations - ทดลองรายงาน: ยืนยันว่าคุณสามารถทำรายงานเฉพาะผู้เช่าและรายงาน "ทุกผู้เช่า" (สำหรับพนักงานภายใน) โดยมีกฎชัดเจนว่าใครบ้างที่เห็นมุมมองระดับโลก
- ตรวจสอบประสิทธิภาพ: วางกลยุทธ์ดัชนีตั้งแต่ตอนนี้ (โดยเฉพาะ
(tenant_id, created_at)และตัวกรองทั่วไปอื่น ๆ) และวัดคำสั่งสืบค้นอย่างน้อยหนึ่งคำสั่งที่ช้าโดยตั้งใจเพื่อรู้ว่ารูปแบบ "แย่" เป็นอย่างไร
เพื่อทำให้การทดสอบรายงานเป็นรูปธรรม ให้เลือกสองคำถามที่คุณรู้ว่าจะต้องใช้ (หนึ่งแบบระดับผู้เช่า หนึ่งแบบระดับโลก) แล้วรันกับข้อมูลตัวอย่าง
-- Tenant-only: last 30 days, one tenant
SELECT count(*)
FROM tickets
WHERE tenant_id = :tenant_id
AND created_at >= now() - interval '30 days';
-- Global (admin): compare tenants
SELECT tenant_id, count(*)
FROM tickets
WHERE created_at >= now() - interval '30 days'
GROUP BY tenant_id;
ถ้าคุณกำลังสร้างต้นแบบใน AppMaster ให้สร้างการตรวจสอบเหล่านี้เข้าไปใน Business Process flows ของคุณ (read, write, delete) และ seed สองผู้เช่าใน Data Designer เมื่อการทดสอบเหล่านี้ผ่านกับปริมาณข้อมูลที่สมจริง คุณจะตัดสินใจได้อย่างมั่นใจ
กรณีตัวอย่าง: จากลูกค้าแรกสู่การขยาย
บริษัทขนาด 20 คนกำลังเปิดพอร์ทัลลูกค้า: ใบแจ้งหนี้ ตั๋ว และแดชบอร์ดเรียบง่าย คาดว่าจะมี 10 ผู้เช่าในเดือนแรก และเติบโตเป็น 1,000 ภายในปี
ในช่วงต้น โมเดลง่ายที่สุดมักเป็นฐานข้อมูลเดียวที่ตารางลูกค้ามี tenant_id เร็วในการสร้าง ง่ายต่อการรายงาน และหลีกเลี่ยงการซ้ำซ้อนการตั้งค่า
กับ 10 ผู้เช่า ความเสี่ยงใหญ่ที่สุดไม่ใช่ประสิทธิภาพ แต่เป็นสิทธิ์ การลืมตัวกรองหนึ่งตัว (เช่น คำสั่ง "list invoices" ที่ลืม tenant_id) อาจรั่วข้อมูล ทีมควรบังคับการตรวจสอบผู้เช่าในจุดเดียวที่สอดคล้องกัน (Business Logic ร่วม หรือตัวแบบ API ที่นำกลับมาใช้ซ้ำ) และถือว่าการกำหนดขอบเขตผู้เช่าเป็นสิ่งที่ไม่ต่อรอง
เมื่อเติบโตจาก 10 เป็น 1,000 ผู้เช่าความต้องการเปลี่ยน การรายงานหนักขึ้น ฝ่ายซัพพอร์ตร้องขอ "ส่งออกรายการทั้งหมดสำหรับผู้เช่านี้" และผู้เช่าบางรายเริ่มครองทราฟฟิคและทำให้ตารางแชร์ช้าลง
เส้นทางอัปเกรดที่เป็นปฏิบัติได้มักเป็นดังนี้:
- เก็บตรรกะแอปและกฎสิทธิ์ไว้เหมือนเดิม แต่ย้ายผู้เช่าที่มีปริมาณสูงไปยัง schema แยก
- สำหรับผู้เช่าที่ใหญ่ที่สุด (หรือลูกค้าที่ต้องปฏิบัติตามกฎเข้มงวด) ย้ายไปยังฐานข้อมูลแยก
- เก็บชั้นรายงานร่วมที่อ่านจากทุกผู้เช่า และรันรายงานหนักนอกเวลาใช้งาน
เลือกโมเดลที่ง่ายที่สุดซึ่งแยกข้อมูลอย่างปลอดภัยวันนี้ แล้ววางแผนเส้นทางการย้ายสำหรับปัญหา "ผู้เช่าจำนวนหนึ่งที่ใหญ่โต" แทนการปรับแต่งสำหรับปัญหานั้นในวันแรก
ขั้นตอนต่อไป: เลือกโมเดลและสร้างต้นแบบใน no-code backend
เลือกตามสิ่งที่คุณต้องปกป้องก่อน: การแยกข้อมูล ความเรียบง่ายในการปฏิบัติการ หรือการปรับขนาดเฉพาะผู้เช่า ความมั่นใจมาจากการสร้างต้นแบบเล็ก ๆ และพยายามทำลายมันด้วยกรณีสิทธิ์และการรายงานจริง
คู่มือเริ่มต้นง่าย ๆ:
- หากผู้เช่าส่วนใหญ่เล็กและคุณต้องการการรายงานข้ามผู้เช่าแบบง่าย ให้เริ่มด้วยฐานข้อมูลเดียวและ
tenant_idในทุกแถว - หากคุณต้องการการแยกที่เข้มขึ้นแต่ยังต้องการจัดการฐานข้อมูลตัวเดียว ให้พิจารณา schema แยกต่อผู้เช่า
- หากผู้เช่าต้องการการแยกอย่างเข้มงวด (ความสอดคล้องกฎ, สำรองข้อมูลเฉพาะ, ความเสี่ยง noisy-neighbor) ให้พิจารณาฐานข้อมูลแยกต่อผู้เช่า
ก่อนสร้าง ให้เขียนขอบเขตของผู้เช่าเป็นภาษาธรรมดา กำหนดบทบาท (owner, admin, agent, viewer), สิ่งที่แต่ละบททำได้ และ "ข้อมูลระดับโลก" คืออะไร (แผน, เท็มเพลต, audit logs) ตัดสินใจด้วยว่าการรายงานควรเป็นอย่างไร: เฉพาะผู้เช่า หรือ "ทุกผู้เช่า" สำหรับพนักงานภายใน
ถ้าคุณใช้ AppMaster คุณสามารถสร้างต้นแบบรูปแบบเหล่านี้ได้อย่างรวดเร็ว: ออกแบบตารางใน Data Designer (รวม tenant_id, ข้อจำกัด unique, และดัชนีที่คำสั่งสืบค้นจะพึ่งพา) แล้วบังคับกฎใน Business Process Editor เพื่อให้การอ่านและเขียนทุกรายการยังคงอยู่ในขอบเขตของผู้เช่า หากต้องการจุดอ้างอิงสำหรับแพลตฟอร์ม AppMaster มีให้ที่ appmaster.io
การทดสอบสุดท้ายที่ใช้งานได้: สร้างสองผู้เช่า (A และ B), เพิ่มผู้ใช้และคำสั่งซื้อที่คล้ายกัน แล้วรัน flow เดียวกันสำหรับทั้งคู่ ลองส่งออกรายงานสำหรับผู้เช่า A แล้วตั้งใจส่ง ID ของผู้เช่า B ไปยัง endpoints เดียวกัน ต้นแบบของคุณจะปลอดภัยพอเมื่อความพยายามเหล่านี้ล้มเหลวทุกครั้งและรายงานสำคัญยังทำงานได้เร็วเมื่อมีขนาดข้อมูลสมจริง
คำถามที่พบบ่อย
เริ่มด้วยฐานข้อมูลเดียวที่มี tenant_id ในทุกตารางที่เป็นของผู้เช่า หากคุณต้องการการดำเนินงานที่เรียบง่ายและการวิเคราะห์ข้ามผู้เช่าบ่อย ๆ ย้ายไปใช้ schema แยกเมื่อคุณต้องการการแยกที่เข้มขึ้นหรือการปรับแต่งต่อผู้เช่าโดยไม่ต้องรันหลายฐานข้อมูล แยกฐานข้อมูลคุ้มค่าต่อเมื่อข้อกำหนดด้านความเป็นปฏิบัติตามกฎหรือความต้องการขององค์กรต้องการการแยกอย่างจริงจังและการควบคุมประสิทธิภาพต่อผู้เช่า
ถือว่าการกำหนดขอบเขตผู้เช่าเป็นสิ่งจำเป็นในฝั่ง backend ไม่ใช่แค่ตัวกรองใน UI. ทำให้ tenant_id เป็นคอลัมน์บังคับในตารางที่เป็นของผู้เช่า และอ่านค่าจาก context ของผู้ใช้ที่ผ่านการยืนยันตัวตน แทนที่จะเชื่อข้อมูลจาก client เพิ่มเครือข่ายความปลอดภัยเช่นนโยบาย row-level security ของ PostgreSQL หากเหมาะสม และเขียนการทดสอบที่พยายามเข้าถึงข้อมูลของผู้เช่าอื่นโดยเปลี่ยนเพียง ID เท่านั้น
ให้ tenant_id มาเป็นคอลัมน์แรกในดัชนีที่ตรงกับตัวกรองที่ใช้บ่อย เพื่อให้ฐานข้อมูลกระโดดไปยังชุดข้อมูลของผู้เช่าแต่ละรายได้ ตัวอย่าง baseline ที่ดีคือดัชนี (tenant_id, created_at) สำหรับมุมมองตามเวลา และเพิ่ม (tenant_id, status) หรือ (tenant_id, user_id) สำหรับตัวกรองแดชบอร์ดบ่อย ๆ นอกจากนี้ทำให้ข้อจำกัดความเป็นเอกลักษณ์เป็นระดับผู้เช่า เช่น อีเมลต้อง unique ต่อผู้เช่า เพื่อหลีกเลี่ยงการชนกัน
Schema แยกลดโอกาสการ join ข้ามผู้เช่าโดยตารางอยู่ใน namespace คนละอัน และคุณสามารถตั้งสิทธิ์ระดับ schema ได้ ข้อเสียหลักคือการอัปเดตสกีม่า: ทุก schema ต้องได้รับการเปลี่ยนแปลงเมื่อเพิ่มตารางหรือคอลัมน์ และเมื่อผู้เช่ามากขึ้นการรัน migration ต้องมีขั้นตอนและการจัดการให้ปลอดภัย มันเป็นจุดกึ่งกลางที่ดีเมื่อคุณต้องการการแยกมากกว่า tenant_id แต่ยังต้องการจัดการฐานข้อมูลแค่ตัวเดียว
ฐานข้อมูลแยกกันลดผลกระทบวงกว้าง: ปัญหาการโหลดสูง การกำหนดค่าผิด หรือการเสียหายมักจะอยู่กับผู้เช่าเดียว ค่าใช้จ่ายคือภาระการดำเนินงานที่เพิ่มขึ้น เพราะการจัดเตรียม, สำรองข้อมูล, การมอนิเตอร์ และการรัน migration เพิ่มขึ้นตามจำนวนผู้เช่า คุณยังต้องมีไดเรกทอรีผู้เช่าที่เชื่อถือได้และการ routing ที่มั่นคงเพื่อให้ทุกคำขอใช้การเชื่อมต่อฐานข้อมูลที่ถูกต้อง
การรายงานข้ามผู้เช่าง่ายที่สุดกับฐานข้อมูลเดียวและ tenant_id เพราะแดชบอร์ดระดับโลกเป็นเพียงการสั่งสอบโดยไม่ใส่ตัวกรองผู้เช่า ในกรณี schema หรือ database แยก วิธีปฏิบัติที่ใช้ได้จริงคือคัดลอกเหตุการณ์สำคัญหรือตารางสรุปไปยังที่เก็บข้อมูลรายงานร่วม (reporting store) ตามตารางเวลา ให้ส่งอีเวนต์ไปยัง data warehouse หรือรันรายงานต่อผู้เช่าแล้วรวบรวมผล (เมื่อจำนวนผู้เช่าไม่มาก) และแยกเมตริกของผลิตภัณฑ์ออกจากข้อมูลลูกค้า
แสดงบริบทของผู้เช่าอย่างชัดเจนในเครื่องมือ support และบังคับให้ต้องสลับผู้เช่าโดยเจตนาก่อนดูข้อมูล หากใช้ impersonation ให้บันทึกว่าใครดูอะไรและเมื่อใด และจำกัดเวลาการเข้าถึง หลีกเลี่ยง workflow support ที่ยอมรับเพียง ID ของระเบียนโดยไม่มีบริบทผู้เช่า เพราะนั่นคือวิธีที่บั๊กอย่าง “invoice 1001” ทำให้เกิดการรั่วไหลจริง
ถ้าผู้เช่าต้องการฟิลด์หรือ workflow ต่างกัน ให้พิจารณาใช้ schema แยกหรือฐานข้อมูลแยกเพื่อลดโอกาสที่การเปลี่ยนแปลงของผู้เช่าหนึ่งจะกระทบคนอื่น ถ้าส่วนใหญ่คล้ายกัน ให้เก็บโมเดลร่วมกันหนึ่งชุดพร้อม tenant_id แล้วรองรับความแตกต่างด้วยตัวเลือกที่ปรับค่าตามได้เช่น feature flags หรือฟิลด์ที่เป็นทางเลือก สำคัญคือต้องไม่สร้างตารางที่มีความหมายผสมระหว่างข้อมูลร่วมและข้อมูลเฉพาะผู้เช่าโดยไม่มีความเป็นเจ้าของที่ชัดเจน
ตัดสินใจขอบเขตผู้เช่าแต่เนิ่น ๆ: ระบุว่าบริบทผู้เช่าถูกเก็บที่ไหนหลังการยืนยันตัวตนและตรวจสอบว่าการอ่าน/เขียนทุกครั้งใช้งานมัน ใน AppMaster โดยทั่วไปหมายถึงการตั้งค่า tenant_id จากผู้ใช้ที่ยืนยันตัวตนใน Business Process ก่อนสร้างหรือสืบค้นระเบียนที่เป็นของผู้เช่า เพื่อให้ endpoints ไม่ลืมมัน และถือเป็นรูปแบบที่นำกลับมาใช้ซ้ำได้แทนที่จะเขียนใหม่ตามหน้าจอแต่ละหน้า
สร้างสองผู้เช่าที่มีข้อมูลคล้ายกันและพยายามทำลายการแยกโดยเปลี่ยนเพียง ID ของระเบียนระหว่างการอ่าน อัปเดต และลบ ยืนยันว่า background jobs, imports และ exports ยังกำหนดขอบเขตผู้เช่าอย่างถูกต้อง เพราะทางผ่านเหล่านั้นมักถูกมองข้าม นอกจากนี้รันรายงานระดับผู้เช่าและรายงานผู้ดูแลรวมกับข้อมูลตัวอย่างที่มีขนาดสมจริงเพื่อตรวจสอบประสิทธิภาพและกฎการเข้าถึง


