<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Single-Tenant · Nhân Tài Đức</title>
    <link>https://jared.lynskey.co.nz/vi/tags/single-tenant/</link>
    <description>Những câu chuyện ngắn về kỹ thuật phần mềm và lãnh đạo đội ngũ kỹ sư — performance review, tuyển dụng và mở rộng team, CI/CD, giám sát DevOps và AI agent. Viết từ Seoul.</description>
    <generator>Hugo</generator>
    <language>vi</language>
    <copyright>© 2026 Nhân Tài Đức</copyright>
    <lastBuildDate>Mon, 26 Oct 2026 00:00:00 +1200</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/vi/tags/single-tenant/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SaaS single-tenant hay multi-tenant: khi nào nên chọn cái nào</title>
      <link>https://jared.lynskey.co.nz/vi/posts/2024/2024-04-07-singletenantsingleinstance/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/vi/posts/2024/2024-04-07-singletenantsingleinstance/</guid>
      <pubDate>Sat, 06 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Me</dc:creator>
      <category>SaaS</category>
      <category>architecture</category>
      <category>single-tenant</category>
      <category>multi-tenant</category>
      <description>SaaS single-tenant (multi-instance) và multi-tenant khác nhau thế nào: so sánh về cô lập dữ liệu, chi phí và vận hành, các phương án ở giữa, khi nào nên chọn cái nào, cách tách tenant trong một cơ sở dữ liệu dùng chung, và những công cụ giúp bạn trụ được nếu đi theo hướng mỗi khách hàng một instance.</description>
      <content:encoded><![CDATA[<p>SaaS single-tenant cấp cho mỗi khách hàng một bản ứng dụng và cơ sở dữ liệu riêng. SaaS multi-tenant cho tất cả khách hàng dùng chung một ứng dụng và một cơ sở dữ liệu, phân biệt nhau bằng mã tenant (tenant ID), và mã này phải được kiểm tra trong mọi truy vấn. Single-tenant mang lại sự cô lập, quyền kiểm soát riêng cho từng khách hàng và giúp việc đáp ứng các yêu cầu tuân thủ (compliance) dễ hơn, đổi lại bạn phải vận hành cả một loạt bản sao. Multi-tenant rẻ hơn và vận hành đơn giản hơn nhiều, nhưng việc giữ dữ liệu của các khách hàng tách biệt nhau trở thành trách nhiệm của code. Phần lớn sản phẩm bắt đầu với multi-tenant, rồi chỉ cung cấp instance riêng cho số ít khách hàng thật sự cần.</p>
<p>Sản phẩm SaaS B2B nào sớm muộn cũng phải đối mặt với câu hỏi về tenancy, tức các khách hàng (tenant) dùng chung hay tách riêng hạ tầng, thường là vào cái ngày có một khách hàng lớn tiềm năng hỏi xem dữ liệu của họ có được lưu tách riêng khỏi các khách hàng khác không. Tôi đã chứng kiến cuộc thảo luận này ở hơn một công ty, và lần nào câu trả lời cũng quy về hai lựa chọn: cho mỗi khách hàng một instance riêng (single-tenant, multi-instance), hoặc cho tất cả chạy chung trên một instance (multi-tenant, single-instance). Không có lựa chọn nào là &ldquo;đúng&rdquo;. Mỗi cái chỉ gặp sự cố theo một kiểu khác nhau.</p>

<h2 class="relative group">Thiết kế hạ tầng
    <div id="thiết-kế-hạ-tầng" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thi%e1%ba%bft-k%e1%ba%bf-h%e1%ba%a1-t%e1%ba%a7ng" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<figure class="diagram">
  <div role="img" aria-label="Khác biệt giữa Single-Tenant và Multi-Tenant"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 790" aria-hidden="true" focusable="false">
<defs><marker id="tn-ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="d-head" d="M0,0 L10,5 L0,10 z"/></marker></defs>
<text class="d-title" x="320" y="164" text-anchor="middle" font-size="42">Multi-instance</text>
<text class="d-sub" x="320" y="204" text-anchor="middle" font-size="24">single-tenant: mỗi khách hàng một bản riêng</text>
<rect class="d-panel" x="12" y="234" width="616" height="304" rx="36"/>
<rect class="d-group" x="25" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="33" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="336.92" text-anchor="middle" font-size="22">Cơ sở dữ liệu</text>
<polygon class="d-badge" points="118.0,256.0 143.7,274.7 133.9,304.8 102.1,304.8 92.3,274.7"/>
<g class="d-icon">
<ellipse cx="118" cy="273" rx="10" ry="4"/>
<path d="M108,273 v15 a10,4 0 0 0 20,0 v-15 M108,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="33" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="475.64" text-anchor="middle" font-size="24">Ứng dụng</text>
<polygon class="d-badge" points="118.0,394.0 143.7,412.7 133.9,442.8 102.1,442.8 92.3,412.7"/>
<g class="d-icon">
<rect x="110" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="114.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M114,411 v-4 M114,427 v4 M110,415 h-4 M126,415 h4 M118,411 v-4 M118,427 v4 M110,419 h-4 M126,419 h4 M122,411 v-4 M122,427 v4 M110,423 h-4 M126,423 h4"/>
</g>
<line class="d-line" x1="118" y1="508" x2="118" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="84" cy="710.88" r="14.44"/><path d="M58.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="152" cy="710.88" r="14.44"/><path d="M126.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="118" cy="704.0" r="19.0"/><path d="M84.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="227" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="235" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="336.92" text-anchor="middle" font-size="22">Cơ sở dữ liệu</text>
<polygon class="d-badge" points="320.0,256.0 345.7,274.7 335.9,304.8 304.1,304.8 294.3,274.7"/>
<g class="d-icon">
<ellipse cx="320" cy="273" rx="10" ry="4"/>
<path d="M310,273 v15 a10,4 0 0 0 20,0 v-15 M310,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="235" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="475.64" text-anchor="middle" font-size="24">Ứng dụng</text>
<polygon class="d-badge" points="320.0,394.0 345.7,412.7 335.9,442.8 304.1,442.8 294.3,412.7"/>
<g class="d-icon">
<rect x="312" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="316.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M316,411 v-4 M316,427 v4 M312,415 h-4 M328,415 h4 M320,411 v-4 M320,427 v4 M312,419 h-4 M328,419 h4 M324,411 v-4 M324,427 v4 M312,423 h-4 M328,423 h4"/>
</g>
<line class="d-line" x1="320" y1="508" x2="320" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="286" cy="710.88" r="14.44"/><path d="M260.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="354" cy="710.88" r="14.44"/><path d="M328.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="320" cy="704.0" r="19.0"/><path d="M286.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="429" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="437" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="336.92" text-anchor="middle" font-size="22">Cơ sở dữ liệu</text>
<polygon class="d-badge" points="522.0,256.0 547.7,274.7 537.9,304.8 506.1,304.8 496.3,274.7"/>
<g class="d-icon">
<ellipse cx="522" cy="273" rx="10" ry="4"/>
<path d="M512,273 v15 a10,4 0 0 0 20,0 v-15 M512,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="437" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="475.64" text-anchor="middle" font-size="24">Ứng dụng</text>
<polygon class="d-badge" points="522.0,394.0 547.7,412.7 537.9,442.8 506.1,442.8 496.3,412.7"/>
<g class="d-icon">
<rect x="514" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="518.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M518,411 v-4 M518,427 v4 M514,415 h-4 M530,415 h4 M522,411 v-4 M522,427 v4 M514,419 h-4 M530,419 h4 M526,411 v-4 M526,427 v4 M514,423 h-4 M530,423 h4"/>
</g>
<line class="d-line" x1="522" y1="508" x2="522" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="488" cy="710.88" r="14.44"/><path d="M462.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="556" cy="710.88" r="14.44"/><path d="M530.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="522" cy="704.0" r="19.0"/><path d="M488.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<text class="d-title" x="982" y="50" text-anchor="middle" font-size="42">Multi-tenant</text>
<text class="d-sub" x="982" y="88" text-anchor="middle" font-size="24">mọi khách hàng dùng chung một instance</text>
<rect class="d-panel" x="698" y="104" width="568" height="434" rx="44"/>
<rect class="d-box" x="887" y="126" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="183.64" text-anchor="middle" font-size="24">Cơ sở dữ liệu</text>
<polygon class="d-badge" points="982.0,102.0 1007.7,120.7 997.9,150.8 966.1,150.8 956.3,120.7"/>
<g class="d-icon">
<ellipse cx="982" cy="119" rx="10" ry="4"/>
<path d="M972,119 v15 a10,4 0 0 0 20,0 v-15 M972,126 a10,4 0 0 0 20,0"/>
</g>
<line class="d-line" x1="982" y1="216" x2="982" y2="246" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<rect class="d-group" x="704" y="240" width="556" height="156" rx="30"/>
<rect class="d-box" x="714" y="282" width="536" height="90" rx="16"/>
<polygon class="d-badge" points="982.0,248.0 1007.7,266.7 997.9,296.8 966.1,296.8 956.3,266.7"/>
<g class="d-icon">
<rect x="974" y="265" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="978.5" y="269.5" width="7" height="7"/>
<path stroke-width="1.4" d="M978,265 v-4 M978,281 v4 M974,269 h-4 M990,269 h4 M982,265 v-4 M982,281 v4 M974,273 h-4 M990,273 h4 M986,265 v-4 M986,281 v4 M974,277 h-4 M990,277 h4"/>
</g>
<rect class="d-box" x="722" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="806.0" y="347.92" text-anchor="middle" font-size="22">Ứng dụng</text>
<rect class="d-box" x="898" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="982.0" y="347.92" text-anchor="middle" font-size="22">Ứng dụng</text>
<rect class="d-box" x="1074" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="1158.0" y="347.92" text-anchor="middle" font-size="22">Ứng dụng</text>
<rect class="d-box" x="887" y="440" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="497.28" text-anchor="middle" font-size="23">Load balancer</text>
<polygon class="d-badge" points="982.0,404.0 1007.7,422.7 997.9,452.8 966.1,452.8 956.3,422.7"/>
<g class="d-icon">
<path d="M982,416 l6,9 h-12 z M982,425 v5 M972,433 h20 M972,433 v3 M982,433 v3 M992,433 v3"/>
<rect class="d-icon-fill" x="969" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="979" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="989" y="436" width="6" height="5"/>
</g>
<line class="d-line" x1="806" y1="364" x2="922" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="982" y1="364" x2="982" y2="398" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="1158" y1="364" x2="1042" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="932" y1="528" x2="780" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="716" cy="710.88" r="14.44"/><path d="M690.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="784" cy="710.88" r="14.44"/><path d="M758.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="750" cy="704.0" r="19.0"/><path d="M716.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="982" y1="528" x2="982" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="948" cy="710.88" r="14.44"/><path d="M922.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1016" cy="710.88" r="14.44"/><path d="M990.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="982" cy="704.0" r="19.0"/><path d="M948.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="1032" y1="528" x2="1184" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="1180" cy="710.88" r="14.44"/><path d="M1154.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1248" cy="710.88" r="14.44"/><path d="M1222.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="1214" cy="704.0" r="19.0"/><path d="M1180.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
</svg>
</div>
</figure>


<h3 class="relative group">Single tenant, multi-instance
    <div id="single-tenant-multi-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-tenant-multi-instance" aria-label="Cọc dấu">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Single Tenant, Multi-Instance</strong>: Với mô hình này, hạ tầng được nhân thành nhiều instance, mỗi tenant chạy trên một instance riêng.</span>
</div>

<p>Điểm hấp dẫn nhất ở đây là tính cô lập, và gần như mọi lợi ích khác đều xuất phát từ đó. Dữ liệu của mỗi khách hàng nằm trong một môi trường riêng, nên các đợt đánh giá bảo mật hay những buổi làm việc về tuân thủ quy định (compliance) dễ thở hơn hẳn. Một lần triển khai lỗi hay một tải xử lý (workload) quá nặng chỉ làm sập một khách hàng, chứ không kéo sập tất cả. Bạn có thể mở rộng quy mô, tinh chỉnh và tùy biến từng instance độc lập với nhau, và vì tài nguyên được cấp riêng nên hiệu năng của khách này không phụ thuộc vào việc tenant bên cạnh đang làm gì. Lịch bảo trì cũng có thể xếp theo từng khách hàng, nên thời gian gián đoạn dịch vụ (downtime) vừa ngắn vừa đúng chỗ. Và nếu định giá hợp lý, bạn có thể tính chi phí hạ tầng sát với mức sử dụng thực tế của từng tenant.</p>
<p>Cái giá phải trả là giờ bạn đang vận hành N bản sao của mọi thứ. Chi phí tài nguyên cộng dồn lại, cấu hình giữa các instance dần dần lệch nhau, và riêng việc giữ cho phiên bản phần mềm lẫn dữ liệu đồng bộ trên cả dàn instance đã thành một dự án riêng. Chuyện mở rộng quy mô, cô lập và quản lý chi phí vẫn cần được lên kế hoạch nghiêm túc. Các vấn đề không hề biến mất, chúng chỉ nhân lên.</p>

<h3 class="relative group">Multi tenant, single instance
    <div id="multi-tenant-single-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-tenant-single-instance" aria-label="Cọc dấu">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Multi Tenant, Single Instance</strong>: Chỉ một ứng dụng duy nhất phục vụ tất cả các tenant.</span>
</div>

<p>Đây là lựa chọn mặc định cũng có lý do của nó: một lần triển khai, một cơ sở dữ liệu cần sao lưu, một phiên bản phần mềm duy nhất trên môi trường production. Mô hình này tiết kiệm tài nguyên, vận hành đơn giản, và mọi khách hàng đều nhận bản cập nhật cũng như bản vá bảo mật cùng một lúc.</p>
<p>Những phần khó lại chính là mặt trái của việc dùng chung. Chỉ một tenant dùng nặng cũng có thể kéo hiệu năng của tất cả mọi người đi xuống. Khả năng tùy biến cho từng khách hàng thì bị hạn chế. Việc cô lập dữ liệu giờ nằm trong mã ứng dụng chứ không còn nằm ở tầng hạ tầng, nên chỉ một lỗi nhỏ cũng có thể dẫn tới rò rỉ dữ liệu giữa các tenant. Khi gặp điểm nghẽn (bottleneck) lúc mở rộng quy mô thì mọi khách hàng cùng chịu ảnh hưởng, và việc bảo trì phải cẩn thận hơn, vì không còn kiểu sự cố &ldquo;chỉ ảnh hưởng mỗi khách này&rdquo; nữa.</p>
<p><a href="https://www.scaleway.com/en/blog/saas-multi-tenant-vs-multi-instance-architectures/"  target="_blank" rel="noreferrer">Tìm hiểu thêm về giải pháp SaaS - Kiến trúc Multi-instance và Multi-tenant</a></p>

<h2 class="relative group">Multi-tenant hay multi-instance
    <div id="multi-tenant-hay-multi-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-tenant-hay-multi-instance" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Bạn sẽ thường gặp lựa chọn này dưới cái tên multi-tenant và multi-instance, nhưng thực ra vẫn là cùng một lựa chọn, chỉ nhìn từ phía bên kia. &ldquo;Tenant&rdquo; nói về khách hàng: bao nhiêu khách hàng dùng chung một bản phần mềm. &ldquo;Instance&rdquo; nói về việc triển khai: bạn chạy bao nhiêu bản phần mềm. Một sản phẩm single-tenant chính là một hệ thống multi-instance, mỗi khách hàng một bản, còn sản phẩm multi-tenant thường là một instance duy nhất mà mọi người dùng chung. Vậy nên khi ai đó đòi multi-instance, thực chất họ đang đòi single-tenant.</p>
<p>Trên thực tế, đây giống một dải lựa chọn hơn là hai ô tách biệt, và phần lớn quyết định quan trọng nằm ở cơ sở dữ liệu. Ở đầu dùng chung nhiều nhất, dữ liệu của mọi khách hàng nằm trong cùng các bảng, mỗi dòng có một cột <code>tenant_id</code>, và mọi truy vấn đều phải lọc theo cột đó. Cách này rẻ nhất để vận hành và cũng dễ sai nhất. Tiến thêm một bước, mỗi khách hàng có một schema riêng bên trong một cơ sở dữ liệu dùng chung: tách biệt gọn gàng hơn, sao lưu được theo từng khách hàng, nhưng mỗi lần chuyển đổi cấu trúc dữ liệu (migration) phải chạy lại cho từng schema. Xa hơn nữa, ứng dụng vẫn dùng chung nhưng mỗi khách hàng có cơ sở dữ liệu riêng: đây là điểm cân bằng được nhiều đội chọn, vì dữ liệu được cô lập chắc chắn mà không phải chạy cả một bộ hạ tầng cho từng khách hàng. Ở đầu bên kia là single-tenant hoàn toàn, cả ứng dụng lẫn cơ sở dữ liệu đều dành riêng. Tài liệu hướng dẫn SaaS của AWS gọi hai đầu này là silo và pool, còn kiểu kết hợp cả hai là bridge. Đây là bộ từ vựng khá tiện khi bạn cần giải thích kiến trúc của mình cho đội bảo mật của khách hàng.</p>

<h2 class="relative group">So sánh nhanh single-tenant và multi-tenant
    <div id="so-sánh-nhanh-single-tenant-và-multi-tenant" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#so-s%c3%a1nh-nhanh-single-tenant-v%c3%a0-multi-tenant" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Single-tenant (multi-instance)</th>
					<th>Multi-tenant (single instance)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Cô lập dữ liệu</td>
					<td>Hạ tầng riêng cho từng khách hàng</td>
					<td>Do code hoặc chính sách của cơ sở dữ liệu đảm bảo</td>
			</tr>
			<tr>
					<td>Chi phí cho mỗi khách hàng</td>
					<td>Cao: mỗi khách hàng một bộ hạ tầng đầy đủ</td>
					<td>Thấp: mọi người dùng chung một bộ</td>
			</tr>
			<tr>
					<td>Vận hành</td>
					<td>Triển khai, nâng cấp, bảng giám sát nhân theo số khách hàng</td>
					<td>Mỗi thứ chỉ một</td>
			</tr>
			<tr>
					<td>Hàng xóm ồn ào (noisy neighbour)</td>
					<td>Không có</td>
					<td>Một tenant dùng nặng có thể làm chậm tất cả</td>
			</tr>
			<tr>
					<td>Tùy biến</td>
					<td>Theo từng khách hàng, thậm chí khác phiên bản</td>
					<td>Chủ yếu dừng ở cấu hình và feature flag</td>
			</tr>
			<tr>
					<td>Phát hành</td>
					<td>Lần lượt từng khách hàng</td>
					<td>Tất cả cùng lúc</td>
			</tr>
			<tr>
					<td>Một lần triển khai lỗi ảnh hưởng tới</td>
					<td>Một khách hàng</td>
					<td>Mọi khách hàng</td>
			</tr>
			<tr>
					<td>Đánh giá bảo mật và nơi lưu trữ dữ liệu</td>
					<td>Dễ trả lời</td>
					<td>Phải giải thích nhiều hơn</td>
			</tr>
	</tbody>
</table>

<h2 class="relative group">Khi nào nên chọn cái nào
    <div id="khi-nào-nên-chọn-cái-nào" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#khi-n%c3%a0o-n%c3%aan-ch%e1%bb%8dn-c%c3%a1i-n%c3%a0o" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Hãy chọn multi-tenant, trừ khi có lý do cụ thể khiến bạn phải làm khác. Đây là lựa chọn mặc định đúng khi bạn có nhiều khách hàng, mỗi khách trả không nhiều, khi người dùng tự đăng ký, và khi đội của bạn đủ nhỏ để ba mươi môi trường là quá sức.</p>
<p>Single-tenant chỉ đáng với chi phí vận hành của nó trong vài trường hợp. Khách hàng trong ngành y tế, tài chính và cơ quan nhà nước thường yêu cầu dữ liệu phải được tách biệt về mặt vật lý, hoặc phải lưu ở một quốc gia nhất định, và trong buổi đánh giá bảo mật, một instance riêng là câu trả lời đơn giản và trung thực nhất. Nếu chỉ có vài khách hàng lớn ký hợp đồng doanh nghiệp, họ đủ sức gánh chi phí cho một bộ hạ tầng riêng, và nhiều khi họ còn mặc định là phải có. Còn khi khách hàng cần lịch phát hành riêng, cần giữ cố định một phiên bản, hay cần tùy biến sâu, thì việc tách instance không còn là gánh nặng nữa mà trở thành một phần của thứ bạn đang bán.</p>
<p>Cuối cùng thì phần lớn các đội dùng cả hai: một sản phẩm multi-tenant cho gần như tất cả khách hàng, và instance riêng như một gói doanh nghiệp cho số ít khách hàng cần và sẵn sàng trả tiền. Nếu ngay từ ngày đầu bản multi-tenant đã gắn tenant ID ở mọi nơi, thì sau này tách một khách hàng sang cơ sở dữ liệu riêng chỉ là một lần migration, không phải viết lại từ đầu.</p>

<h2 class="relative group">Làm cho việc cô lập trong multi-tenant khó bị phá vỡ hơn
    <div id="làm-cho-việc-cô-lập-trong-multi-tenant-khó-bị-phá-vỡ-hơn" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#l%c3%a0m-cho-vi%e1%bb%87c-c%c3%b4-l%e1%ba%adp-trong-multi-tenant-kh%c3%b3-b%e1%bb%8b-ph%c3%a1-v%e1%bb%a1-h%c6%a1n" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Lỗi đáng sợ nhất trong multi-tenant là một truy vấn quên lọc theo tenant. Nó không báo lỗi. Nó chỉ lặng lẽ trả về dữ liệu của khách hàng khác, và code review không phải lúc nào cũng bắt được. Nếu bạn dùng Postgres, tính năng bảo mật mức dòng (row-level security, RLS) sẽ chuyển việc kiểm tra này xuống cơ sở dữ liệu, nên kể cả truy vấn quên lọc thì cũng chỉ thấy được các dòng của khách hàng hiện tại:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">invoices</span><span class="w"> </span><span class="n">ENABLE</span><span class="w"> </span><span class="k">ROW</span><span class="w"> </span><span class="k">LEVEL</span><span class="w"> </span><span class="k">SECURITY</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">POLICY</span><span class="w"> </span><span class="n">tenant_isolation</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">invoices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">USING</span><span class="w"> </span><span class="p">(</span><span class="n">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">current_setting</span><span class="p">(</span><span class="s1">&#39;app.tenant_id&#39;</span><span class="p">)::</span><span class="n">uuid</span><span class="p">);</span></span></span></code></pre></div></div>
<p>Sau đó, mỗi request đặt tenant của mình bên trong một transaction bằng <code>SET LOCAL app.tenant_id = '…'</code>. Nhờ vậy, một kết nối trong connection pool sẽ không mang ID của khách hàng trước sang request tiếp theo, và request nào quên đặt tenant sẽ nhận về lỗi thay vì dữ liệu. Có hai điểm cần lưu ý: ứng dụng phải kết nối bằng một role không sở hữu bảng, vì chủ sở hữu bảng sẽ bỏ qua các chính sách này trừ khi bạn thêm <code>FORCE ROW LEVEL SECURITY</code>; và tuyệt đối không kết nối bằng superuser, vì superuser luôn bỏ qua chúng.</p>

<h2 class="relative group">Nếu chọn single-tenant thì bắt đầu từ đâu?
    <div id="nếu-chọn-single-tenant-thì-bắt-đầu-từ-đâu" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#n%e1%ba%bfu-ch%e1%bb%8dn-single-tenant-th%c3%ac-b%e1%ba%aft-%c4%91%e1%ba%a7u-t%e1%bb%ab-%c4%91%c3%a2u" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Nếu bạn quyết định đi theo single-tenant, công cụ sẽ quyết định mọi thứ còn nằm trong tầm kiểm soát, hay trở thành một thảm họa diễn ra từ từ. Không ai quản lý thủ công nổi ba mươi môi trường cả.</p>
<p>Phần cốt lõi là container kết hợp với hạ tầng dưới dạng mã (infrastructure as code, IaC). Docker đóng gói ứng dụng giống hệt nhau cho mọi tenant, Kubernetes lo việc triển khai và co giãn cả đống instance đó, còn Terraform, Pulumi hay AWS CloudFormation cho phép bạn định nghĩa toàn bộ hạ tầng của một tenant bằng mã. Nhờ vậy, dựng thêm khách hàng thứ 31 chỉ tốn một pull request, chứ không phải mất trắng một cuối tuần. Nếu hệ thống của bạn chủ yếu chạy trên máy ảo (VM), các công cụ quản lý cấu hình như Ansible, Chef hay Puppet cũng đóng vai trò tương tự. Ở mô hình này, một quy trình CI/CD vững chắc (Jenkins, GitLab CI/CD) còn quan trọng hơn bình thường, vì mỗi bản phát hành phải được triển khai tới mọi instance mà không cần ai ngồi canh.</p>
<p>Ngoài những thứ đó ra thì tùy vào đặc thù sản phẩm của bạn. Kiến trúc phi máy chủ (serverless) như AWS Lambda hay Azure Functions có thể loại bỏ hẳn tầng máy chủ cho một số tải xử lý. Các dịch vụ cơ sở dữ liệu được quản lý sẵn (managed) như Amazon RDS hay Azure SQL Database giúp bạn khỏi phải tự tay quản trị từng cơ sở dữ liệu cho từng tenant. Giám sát bằng Prometheus, Grafana hay New Relic thì không còn là tùy chọn khi bạn đã có cả một dàn instance, vì bạn muốn biết instance nào đang trục trặc trước khi khách hàng gọi tới báo. Về định danh, một dịch vụ như Auth0 hay Okta sẽ lo phần quản lý người dùng và phân quyền truy cập, để bạn khỏi phải làm lại cho từng tenant. Dù bạn dùng ngôn ngữ nào thì chắc cũng có sẵn framework hay thư viện hướng SaaS giúp bạn bớt được phần nào đống việc &ldquo;đi dây&rdquo; này, và nhà cung cấp đám mây lớn nào (AWS, Azure, Google Cloud Platform) cũng sẵn lòng vận hành hết cho bạn.</p>
<p>Đừng dùng hết cả danh sách này ngay từ ngày đầu. Hãy bắt đầu với container và IaC, bổ sung giám sát và quản lý định danh, truy cập (IAM) khi số tenant tăng dần, rồi để những khó khăn thực tế quyết định phần còn lại.</p>

<h2 class="relative group">Nói ngắn gọn
    <div id="nói-ngắn-gọn" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#n%c3%b3i-ng%e1%ba%afn-g%e1%bb%8dn" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Single-tenant, hay multi-instance, nghĩa là mỗi khách hàng có một bản ứng dụng và cơ sở dữ liệu riêng: cô lập tốt, kiểm soát được theo từng khách hàng, đổi lại bạn phải vận hành cả một loạt instance. Multi-tenant nghĩa là mọi người dùng chung một bản: rẻ và đơn giản, còn việc cô lập do code đảm bảo. Phần lớn sản phẩm nên bắt đầu với multi-tenant, gắn tenant ID ở mọi nơi và để cơ sở dữ liệu thực thi việc lọc, rồi cung cấp instance riêng cho số ít khách hàng cần và sẵn sàng trả tiền. Nếu bạn thật sự chạy single-tenant, thứ giúp mọi việc còn trong tầm kiểm soát là container và hạ tầng dưới dạng code (IaC).</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-07-singletenantsingleinstance/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
