論壇文章
【技術e專欄】SaaS之多租戶資料綱要設計技巧

隨著雲端運算環境的興起,軟體即服務(Software as a Service, SaaS)的營運模式也開始受到軟體開發商的注意,紛紛投入資源,將一些既有的應用軟體以及新開發的軟體採用此種模式推向市場。不同於以往到客戶端安裝(on-premises),供單一用戶使用的模式,SaaS的一個特色是在資料中心安裝,以多租戶(multi-tenants)方式營運:每個租戶的使用者透過網路使用該應用軟體,其運作所需的軟硬體設施由資料中心與應用軟體開發商負責維運,租戶只需按使用量與服務品質等因素依合約付費。

SaaS、ASP (Application Service Provider) 觀念大釐清

乍聽之下,SaaS跟10多年前曾經也風行一時應用軟體服務商(Application Service Provider, ASP)的模式好像是一樣的,難道只是新方式,提供多租戶服務,而不是一租戶一套設備的方式運作,那種模式只能稱得上是網路應用程式寄存服務(Web applications hosting)。在資源共享方面,除了關鍵的虛擬化(virtualization)技術外,如何讓各個租戶之間可以共享資料庫也是一個重要技術指標。

五種多租戶資料綱要的設計技巧

如果說一租戶一資料庫的寄存服務是一個極端狀況(圖一),另一個極端就是所有的租戶都共用一資料庫(圖二)。顯然的,這兩種情況都不理想,第一種完全沒有資源共享,第二種則缺乏擴充性,所有租戶都必須使用同一種資料綱要(data schema),不易提供租戶客製化的服務。兩者之間,應該有一些比較彈性的作法存在。本文的目的就是要介紹幾種介於二者之間的資料綱要設計技巧。

以下我們將用資料庫表格(table)搭配SQL查詢指令的方式,介紹五種可用於多租戶資料綱要的設計技巧。首先,我們以一租戶一資料庫的情境(沒有共享資料表格)當作基礎範例,再逐一介紹有共享且具擴充性的五種作法。

假設我們有三個租戶: Tenant 31, 32, 33,以下(圖三)是他們都要使用的一個資料庫表格:學生資料。三個表格都有AccountID與Name這兩個共同欄位,但也有個別的欄位,以表達客製化的需求。

根據以上的表格設計,如果我們要查詢 Tenant 31 裡AccountID為1的Major欄位資料,可用SQL指令「SELECT Major FROM Tenant31 WHERE AccountID = 1」來完成。接著我們以此範例為基礎,介紹五種多租戶資料綱要的設計技巧。

Extension Table

Extension Table 這個作法是將租戶共有的欄位抽出放置於一個資料庫表格,並以新增的TenantID欄位與Row欄位來區分不同租戶的資料。其它個別租戶專有的資料欄位(user column)則分別寫入不同的表格中。圖四以上述的基礎範例來展示這種作法,其中欄位Row是用來標記表格內資料紀錄(record)的編號。

SQL指令範例:(以查詢Tenant31 表格裡AccountID為1的Major欄位資料)

SELECT Tenant31.Major FROM TenantExt
LEFT JOIN Tenant31
ON TenantExt.TenantID = Tenant31.TenantID
AND TenantExt.Row = Tenant31.Row
WHERE TenantExt.AccountID = 1

這種作法的主要優點是可以透過將共有欄位集中存放,達到較好的資料整合性。但也有一些缺點:(1)共有表格中需額外增加一些詮釋欄位(meta-data)來區分不同的租戶,圖四中紅色框框即代表此表格的詮釋欄位。(2)讀取某些資料的時候,需要執行較費時的join的指令。(3)跟一租戶一資料庫的作法一樣,仍然有表格數量隨租戶數增加而增加的問題。

Universal Table

Universal Table 這個作法也是將所有租戶的資料放置於一個共用的資料庫表格,但為支援租戶之間資料欄位的差異性,它是採稀疏矩陣的方式來存放資料。具體而言,共用表格的資料欄位是所有租戶的聯集,簡單以流水號命名(Col1, Clo2, …),並以新增的TenantID欄位與Table欄位來區分不同租戶的資料。圖五以上述的基礎範例來展示這種作法。

SQL指令範例: (以查詢Tenant31 Table裡AccountID為1的Major欄位資料)

SELECT Col3
FROM TenantUniversal
WHERE Col1 = 1 AND TenantID = 31 AND Table = 0

這種作法的主要優點是減少表格的數量,無論多少個租戶都只需要一個表格。但也有一些缺點:(1)表格中的欄位數量會增多;此外,因為不同租戶間的資料差異,造成部份的欄位會有浪費的情況發生,圖五中綠色框框即為此類欄位。(2)因租戶間同時也共用欄位,所以某些資料欄位的資料會有異質性型別的情況(例如,Col3有字串也有數字),從而可能有輸入錯誤的型別資料到表格的情況發生。(3)缺乏索引(index)的支援,無法利用TenantID欄位與Table欄位來指定任一所需資料。

Pivot Table

Pivot Table 這個作法是將所有租戶的資料依資料的型別來分別存放在不同的表格,即同一型別的資料放置於同一個資料庫表格,並以新增的TenantID欄位、Table欄位、Col欄位以及Row欄位來區分不同租戶的資料。其餘的欄位用來放置租戶的資料。圖六以上述的基礎範例來展示這種作法。

SQL指令範例: (以查詢Tenant31 Table裡AccountID為1的Major欄位資料)

SELECT Str
FROM PivotStr
WHERE TenantID = 31
AND Table = 0
AND Col = 2
AND Row IN
( SELECT Row
FROM PivotInt
WHERE TenantID = 31
AND Table = 0
AND Col = 0
AND Int = 1
)

這種作法的主要優點是加入了資料型別的處理,同時也不會有欄位浪費的情況發生。但共有表格中需額外增加不少的詮釋欄位(meta-data)來區分不同的租戶,圖六中紅色框框即代表此表格的詮釋欄位。

Chunk Table

Chunk Table 這個作法參考Pivot與Universal table的方式而有所改良。首先,也是將所有租戶的資料放置於同一個資料庫表格,但它改良Universal table,加入了資料型別的考量,將租戶資料依其型別存放於不同的欄位內,一欄位一型別,稱之為chunk,而不是像Pivot table,一型別一表格的作法。並以新增的TenantID欄位、Table欄位、ChunkNo欄位以及Row欄位來區分不同租戶的資料。其餘的欄位用來放置該租戶的資料。圖七以上述的基礎範例來展示這種作法,在本例中,有整數與字串兩種資料型別,所以有Int1與Str1兩個Chunks,同一筆資料紀錄的相同型別的資料以ChunkNo來區別。例如,Tenant 31的AccountId與Grade欄位皆為整數型別,但它們的資料可以透過ChunkNo的值來區別。

SQL指令範例: (以查詢Tenant31 Table裡AccountID為1的Major欄位資料)

SELECT Str1
FROM ChunkTable
WHERE TenantID = 31
AND Table = 0
AND ChunkNo = 1
AND Row IN
( SELECT Row
FROM ChunkTable
WHERE TenantID = 31
AND Table = 0
AND ChunkNo = 0
AND Int1 = 1
)

這種作法的相對於Pivot Table的作法有下列優點:(1)減少詮釋欄位與實際資料的比例。(2)由於只使用一個表格,所以方便還原建立各租戶的原始資料。除此之外,還有一些相對於Universal Table的優點:(1)可以透過詮釋欄位來建立索引。(2)減少欄位的數量。(3)加入資料型別的概念。但同時也潛藏一些資料欄位有浪費現象發生的可能,圖七中綠色框框即為部份欄位浪費的情況。

Chunk Folding

Chunk Folding這個作法修改Chunk table的方式,將租戶共有的欄位抽出放置於一個資料庫表格,並以新增的TenantID欄位與Row欄位來區分不同租戶的資料。其它個別租戶專有的資料欄位則分別寫入另一個ChunkRow Table中。圖八以上述的基礎範例來展示這種作法。

SQL指令範例: (以查詢Tenant31 Table裡AccountID為1的Major欄位資料)

SELECT Str1
FROM ChunkRow
WHERE TenantID = 31
AND Table = 0
AND Chunk = 0
AND Row IN
( SELECT Row
FROM TenantCommon
WHERE TenantID = 31
AND AccountID = 1
)

這種作法除了保有Chunk Table的主要優點外,還可以透過將共有欄位集中存放,達到較好的資料整合性。