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 實現查詢邏輯
下一篇
Day 24 AWS Cloudfront OAC + S3 | 透過 CDN 分發並保護 S3 資源
系列文
一步步帶你認識 Cloud Native —— 用AWS免費服務打造雲原生專案 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言