iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Build on AWS

一步步帶你認識 Cloud Native —— 用AWS免費服務打造雲原生專案系列 第 23

Day23 AWS DynamoDB 單表設計 ( Single Table Design )實戰 | 從業務邏輯開始設計高可擴展性的資料模型

  • 分享至 

  • xImage
  •  

前言

上一篇我們介紹了 DynamoDB 的查詢工具(PK、SK、GSI、LSI),今天我們要嘗試把實際的業務邏輯翻譯成單表設計,看看為什麼 GSI 能在這裡發揮強大作用。


白話的業務邏輯

我們的協作平台大致長這樣:

https://ithelp.ithome.com.tw/upload/images/20250902/201781033nK04Vhxct.png

https://ithelp.ithome.com.tw/upload/images/20250902/20178103fQNg1lpwe9.png

  • 使用者可以創建 專案 (Project)
  • 每個專案中有多位 使用者 (User) 共同參與
  • 在專案中大家共享一份 日曆 (Calendar),上面可以放很多 活動 (Event)
  • 一個使用者可以參加多個專案
  • 一個活動一定屬於某一個專案

其實仔細觀察後會發現:
「專案」和「日曆」幾乎是 一對一綁定 的,沒有必要分成兩個實體。
因此我們可以 省略日曆這一層,直接把所有活動掛在專案底下。


傳統 SQL 的做法

如果用 SQL,我們通常會這樣拆:

  • Users
  • Projects
  • Events
  • ProjectMembers (專案與使用者的多對多關聯表)

查詢範例:

  • JOIN Projects 與 ProjectMembers,找出某使用者參加的專案
  • JOIN Events 與 Projects,找出某專案底下的活動
  • 再加條件過濾,找出「某使用者的所有活動」

可以做到,但查詢會牽涉到多次 JOIN。


單表設計思路

在 DynamoDB 裡,最重要的就是 PK (Partition Key)SK (Sort Key)
單表設計的精髓在於:不同的資料型別(Project / User / Event)都放進同一張表,靠 PK、SK 來組織與查詢。

這裡我們的規劃是:

  • PK = ProjectId (查詢效率最高 API Calls 不會浪費在重複查詢)

  • SK = 有前綴的字串 (透過前綴來區分不同實體)

ProjectId 會成為我們的主要 PartitionKey,同一專案底下的資訊會集中在一個分區。

PK (PartitionKey) SK (SortKey) EntityType Attributes
PROJECT# METADATA Project title, description, owner
PROJECT# USER# Member role, joinDate
PROJECT# EVENT# Event title, startTime, endTime, notes

這樣:

  • 查專案細節 → SK = METADATA
  • 查專案成員 → begins_with(SK, USER#)
  • 查專案活動 → begins_with(SK, EVENT#)

不用 JOIN,全都在同一個 Partition。


4. 使用者導向的需求 → GSI 的威力

問題來了:我們視情況還需要以使用者為 Key 的查詢,像是以下這些情境:

  • 找出某使用者參與的所有專案
  • 找出某使用者參加的所有活動

在 DynamoDB 裡,雖然我們已經用完了 PK 和 SK,但我們可以靠 GSI 來幫我們完成這個查詢邏輯。

DynamoDB 的 GSI 在單表設計中的角色

我們先看一張對照表:

使用場景 PK (PartitionKey) SK (SortKey) 查詢方式
查專案細節 PROJECT#<ProjId> METADATA PK=PROJECT#123, SK=METADATA
查專案的成員清單 PROJECT#<ProjId> USER#<UserId> PK=PROJECT#123, begins_with(SK,USER)
查專案的事件清單 PROJECT#<ProjId> EVENT#<EventId> PK=PROJECT#123, begins_with(SK,EVENT)
查某位使用者參與哪些專案 USER#<UserId> (投影到 GSI1PK) PROJECT#<ProjId> (GSI1SK) 透過 GSI,PK=USER#456

這裡的最後一行就是關鍵:
即使在原始表裡,User 資料是掛在 Project 底下,但我們可以建立一個 GSI,讓查詢「從使用者反查專案」變得容易。


CDK 範例:建立一個帶有 GSI 的 Table

python
from aws_cdk import (
    aws_dynamodb as dynamodb,
    core
)

class DynamoDemoStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        table = dynamodb.Table(
            self, "ProjectTable",
            partition_key=dynamodb.Attribute(
                name="PK",
                type=dynamodb.AttributeType.STRING
            ),
            sort_key=dynamodb.Attribute(
                name="SK",
                type=dynamodb.AttributeType.STRING
            ),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST
        )

        # 建立一個 GSI 來支援「使用者反查專案」
        table.add_global_secondary_index(
            index_name="GSI1",
            partition_key=dynamodb.Attribute(
                name="GSI1PK",
                type=dynamodb.AttributeType.STRING
            ),
            sort_key=dynamodb.Attribute(
                name="GSI1SK",
                type=dynamodb.AttributeType.STRING
            ),
            projection_type=dynamodb.ProjectionType.ALL
        )
        

如此一來我們就可以透過PK Project 來查詢 Event,同時也可以透過原本不是PK的User來去找出 Project 進而實現儀表板以及日曆行程管理的邏輯

https://ithelp.ithome.com.tw/upload/images/20250902/20178103GfZJkvd0R2.png

https://ithelp.ithome.com.tw/upload/images/20250902/20178103YtkCX95V8s.png

可以把 GSI 想像成「分身表格」只不過同樣的 item 被換了Primary Key(Partition Key + Sort Key) 於是可以用不同的方式來查資料


LSI 的補充應用

如果需要在同一專案內,根據不同條件排序活動(例如依照狀態或起始時間),我們可以利用 LSI

  • 在專案分區下,新增一個 LSI 以 status 作為 SortKey
  • 另一個 LSI 以 startTime 作為 SortKey

這樣:

  • 查「專案 A 的所有待辦事件」
  • 查「專案 B 活動依開始時間排序」
    都能用一個 Query 解決。

單表設計的高可擴展性優勢

今天我們的實作重點放在「把專案、成員、事件」放在同一個 Table 裡,並透過 GSI 讓查詢能夠靈活切換。
這種做法在 DynamoDB 的世界裡有一個很大的優勢:天然的可擴展性

  1. 查詢分區化 (Partitioning)
    所有跟同一個專案相關的資料,都會被放到相同的 Partition 裡。這樣查詢專案成員或事件時,就能快速掃描,而不用像關聯式資料庫那樣做多表 JOIN。

  2. 高併發讀寫
    DynamoDB 的底層是分散式設計,每個 Partition 可以獨立擴展。
    單表設計意味著我們的資料模型是「查詢導向」,不會被過多的隨機 Query 拖垮。

  3. GSI 提供彈性的查詢路徑
    我們可以根據業務需求隨時新增 GSI,讓原本「需要跨表 JOIN」才能完成的邏輯,在單表中以投影欄位的方式完成。

  4. 簡化 Schema 演進
    新的 Entity 只要定義好 PK / SK 前綴,就能直接加入同一個 Table。
    不需要動資料庫結構,也不會因為 Schema Migration 而造成系統中斷。

DynamoDB 單表設計讓「查詢快速」和「系統擴展」兩者兼得,很適合做為大型高併發系統(像是遊戲後端、IoT 平台)的資料庫解決方案,但同時因為按需計費 (Pay per request) 也很適合我們的小小專案。


結語

今天我們完成了業務邏輯到資料模型設計的部分,成功定義了協作平台的資料庫設計
藉此經驗我們也能分割出來 RDB 經典的 SQL 模式與 DynamoDB 的資料設計思考角度的不同

  1. SQL 模式:多張表 + 多對多關聯 + JOIN
  2. DynamoDB 模式
    • 專案作為主要分區,成員與活動掛在底下
    • GSI 讓我們能從「使用者視角」查詢跨專案的資料
    • LSI 幫助我們在專案內排序或過濾

希望在今天的實作過程中有讓大家滿意,往後我們將會繼續向專案中的其他部件進行實作,敬請期待


上一篇
Day22 DynamoDB 單表設計 (Single Table Design) | PK&SK、GSI、LSI 實現查詢邏輯
系列文
一步步帶你認識 Cloud Native —— 用AWS免費服務打造雲原生專案23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言