這天大師讓洛基晚上再過來,洛基對於這時間站在大師門外感到些許新鮮。
手還沒碰到門把,而他腦中想著這幾天的學習,每一天都是一個新的挑戰,每一天都在學習「選擇」。
但現在,站在這裡,他突然意識到一件事——
這五天,他一直在學「怎麼做」。如何處理一對一、如何設計一對多、如何保證一致性...技巧學會了,但有個更深的問題他從未真正思考過:
「為什麼是這樣設計?」
洛基帶著疑問深吸一口氣,推開門。
茶室裡的氛圍不同以往。大師沒有站在白板前,而是坐在窗邊,手裡拿著一杯茶,看著窗外的星空。茶室裡只有一盞柔和的燈光。
「坐,」大師說,沒有轉頭,「今天不用白板。」
洛基困惑地坐下,等待大師開口。
過了一會兒,大師轉過頭,看著他:「這五天,你學會了很多技巧。但你學會了 Access Pattern 了嗎?」
洛基當然知道那是什麼,原想回答「當然」,但話到嘴邊卻停住了。他發現自己無法給出一個清晰的答案。
大師微笑:「很好的沉默。這就是今天的起點。」
大師站起身,走到窗邊,指著外面的星空:「你看那些星球,它們都有自己的軌道。為什麼是那樣的軌道?」
洛基想了想:「引力、速度、質量...各種因素的平衡。」
「正確,」大師轉身,「軌道不是設計出來的,而是由這些星球的本質特性『決定』出來的。」
他回到桌邊坐下:「DynamoDB 的設計也是一樣。不是『我想要這樣設計』,而是『查詢需求決定了我必須這樣設計』。」
洛基開始理解了:「所以 Access Pattern 不是一個設計原則,而是...」
「是設計的『決定性因素』,」大師說,「就像引力決定軌道一樣。」
他拿出筆記本,翻到第 18 天的筆記快速瀏覽一下,「當時我學到的是『選擇』,」洛基說,「嵌入或分離,取決於更新頻率。」
「沒錯,」大師說,「但為什麼更新頻率會影響設計?」
洛基思考了一會兒:「因為...嵌入的話,每次更新 profile 就要重寫整個項目。如果 profile 常更新,成本會很高。」
「再深入一點,」大師引導,「為什麼重寫整個項目成本會高?」
「因為 DynamoDB 是按項目大小計算 WCU 的...」洛基停頓,「所以根本原因不是『更新頻率』,而是『WCU 消耗模式』?」
大師點頭:「現在你開始觸及本質了。不是技巧,而是理解『為什麼這個技巧有效』。」
洛基翻到第 19 天的筆記,「這個設計,」洛基說,「當時我學到的是『選擇查詢方向』。」
「是的,」大師說,「但為什麼只能選一個方向?」
洛基想起第 2 天學到的:「因為 PK 決定分區,SK 決定排序。一個表的主鍵只能有一組 PK/SK。」
「正確,」大師說,「這是 DynamoDB 的技術限制。但這個限制帶來什麼影響?」
洛基慢慢理解:「它強迫我們『選擇』——必須決定哪個查詢是主要的、最重要的、最頻繁的。」
「這就是 Access Pattern 的核心,」大師說,「不是『有哪些查詢』,而是『哪些查詢最重要』。」
他在筆記本上寫下:
SQL 思維:
- 設計實體關係(ER 圖)
- 建立表結構
- 所有查詢都能支援(JOIN)
- 查詢是對稱的
DynamoDB 思維:
- 分析查詢需求(Access Pattern)
- 識別核心查詢(最重要、最頻繁)
- 主鍵設計支援核心查詢
- 查詢是不對稱的(有主有次)
洛基盯著這個對比,突然有種頓悟的感覺:「所以...第 6 天的時候,大師說『查詢優先』,不只是一個原則,而是 DynamoDB 架構本身的必然結果?」
「沒錯,」大師說,「分散式架構不能做 JOIN,這個技術限制『決定』了設計必須從查詢出發。」
大師繼續:「第 20 天你學了多對多關係,雙向索引的設計。」
洛基翻到筆記,「這個設計,」洛基說,「我當時理解的是『兩個查詢方向都重要,所以存兩份』。」
「對,」大師說,「但為什麼要存兩份?能不能只存一份,然後用某種方式反向查詢?」
洛基想了想:「不行。如果只存 PK: USER#alice, SK: EVENT#xxx
,要查『某個活動的所有參與者』,就只能 Scan 整個表。」
「為什麼?」
「因為沒有辦法用 PK 來查詢『所有包含 EVENT#science-conf 的項目』...」洛基停頓,「PK 必須精確匹配,不能做『包含』或『反向』查詢。」
「這又是技術限制,」大師說,「而這個限制『決定』了必須存兩份。不是設計偏好,而是技術必然。」
洛基感覺腦中的某些東西連接起來了:「所以...這五天學的每個設計模式,本質上都是『在 DynamoDB 的技術限制下,如何滿足查詢需求』?」
大師笑了:「現在你真正理解 Access Pattern 了。」
他在筆記本上寫下洛基的這句話:
在 DynamoDB 的技術限制下,如何滿足查詢需求
「這就是 DynamoDB 設計的本質,」大師說,「不是學會幾個模式,而是理解『為什麼只能這樣設計』。」
洛基沉浸在這個頓悟中。但大師接著提出一個新問題:
「現在,假設你有一個星際活動系統,」大師說,「主表的設計是:」
{
PK: 'LOCATION#MARS', // 按地點分區
SK: 'EVENT#SY210-03-15-001', // 按時間排序
eventName: '火星科學大會',
hostRank: 'Captain',
topic: 'Physics',
status: 'ACTIVE'
}
「這個設計支援什麼查詢?」大師問。
洛基分析:「可以查詢『某個地點在某個時間範圍的活動』。」
「對,」大師說,「這是你的核心查詢。但現在有個新需求:『查詢所有由 Captain 職級主持的活動』。」
洛基皺眉:「hostRank 不在 PK 或 SK 裡...」
「你會怎麼做?」
洛基想起第 9 天學過的:「只能用 Scan,掃描整個表,然後用 FilterExpression 過濾 hostRank。」
「成本如何?」
洛基回憶第 10 天的學習:「Scan 要消耗所有項目的 RCU,即使最後只回傳 10 筆符合的資料,可能要掃描 10,000 筆...」
他停頓:「非常浪費。」
「對,」大師說,「所以問題來了——主表的 PK/SK 只能支援一種查詢模式。但真實系統常常需要多種查詢。」
洛基想到一個可能:「重新設計主表的 PK?把 hostRank 放進去?」
「那就失去了按地點查詢的能力,」大師說,「而且如果又有第三種查詢需求呢?第四種呢?」
洛基陷入沉思。主鍵只有一組,但查詢需求可能有很多種。這個矛盾一直以來都存在。
大師看著他思考的樣子,慢慢說:「第 17 天,我給你看過一個設計。」
洛基翻到筆記,找到那一頁:
// 大師展示的設計範例
{
GSI1PK: 'RANK#Captain',
GSI1SK: 'EVENT#SY210-03-15-001'
}
「當時我只看懂了語法,」洛基說,「但完全不知道為什麼要這樣設計。」
「現在呢?」大師問。
洛基盯著這個設計,結合剛才的討論:「這是...建立第二條查詢路徑?」
「正確,」大師點頭,「主表用 LOCATION 作為 PK,支援按地點查詢。GSI 用 RANK 作為 PK,支援按職級查詢。兩條獨立的查詢路徑。」
洛基的眼睛亮了:「所以 GSI 就是為了解決『主鍵只能支援一種查詢』的限制?」
「沒錯,」大師說,「這就是 Global Secondary Index 存在的原因。」
大師站起身,這次走到白板前,開始畫圖:
主表設計:
PK: LOCATION#MARS
SK: EVENT#SY210-03-15-001
→ 支援查詢:「某地點的活動」
問題:如何查詢「某職級主持的活動」?
解法 1:Scan + FilterExpression
→ 成本高、效能差
解法 2:重新設計主表
→ 失去原有的查詢能力
解法 3:GSI
→ 建立第二條查詢路徑
洛基看著這個分析,所有的片段開始連接起來:
「第 9 天,大師提過『索引的額外存取路徑』,但當時我不理解。」
「第 17 天,大師展示了 GSI 的語法,但我只看到程式碼,不理解原理。」
「第 19 天,面對次要查詢,我只能接受 Scan,大師說『後面會有其他工具』。」
「現在我明白了,」洛基慢慢說,「GSI 不是一個新功能,而是對『主鍵只能支援一種查詢』這個限制的解決方案。」
大師請 Hippo 在白板上顯示設計三個層次:
「這五天,你從第一層走到第二層,」大師說,「現在,你開始觸及第三層。」
洛基看著白板,感覺整個學習路徑清晰起來。
「但是,」大師轉過身,「GSI 不是銀彈,一如其他的事物,它也有它的代價。」
洛基收起筆記本,站起身準備離開。
大師叫住他:「第 6 天的時候,你聽到 Access Pattern 這個詞,感覺是什麼?」
洛基回想:「一個新概念,要背起來的設計原則。」
「現在呢?」
洛基沉思了一會兒:「現在我知道,它不是原則,而是必然。是 DynamoDB 的技術架構決定了設計必須從查詢出發。」
大師點頭:「這就是從『知道』到『理解』的距離。」
洛基走向門口,突然停下腳步,轉身問:「大師,為什麼要讓我學完五天的關聯設計,才教 GSI?」
大師看著他:「因為只有真正體會過主鍵設計的局限,才能理解 GSI 的價值。如果一開始就教 GSI,你只會把它當成另一個語法來背。」
洛基理解了。這五天的掙扎、選擇、權衡,都是為了今天的頓悟做準備。
他推開門走出去。夜空中,星球們沿著各自的軌道運行。
「不是我選擇軌道,」洛基喃喃自語,「是引力決定了軌道。」
他想起大師今天說的話:設計不是創造,而是在限制中找到最優解。
這個體悟,不只適用於 DynamoDB,也適用於人生的每個選擇。
時間設定說明:故事中使用星際曆(SY210 = 西元2210年),程式碼範例為確保正確執行,使用對應的西元年份。