最後的最後,我們來談談一種有意思的資料庫設計,圖(graph database,GDB) 。
如果說 SQL 資料庫是把世界整理成一張張整齊的 Excel 表格,設計的核心是「正規化」,目標是資料的完整性與無冗餘,是「為資料本身而設計」。那麼圖資料庫,就是直接把世界的「關係網絡」本身給存了下來 「為你想問的問題而設計」。它的設計思維,更接近我們大腦的直覺,我們必須像個記者,不斷追問: 根據這個資料節點,我們未來最想知道的是什麼? 。
在圖資料庫的世界裡,「關係」不再是需要透過 JOIN 操作才能間接找到的東西,它本身就是一等公民,和「實體」一樣重要。這讓它在處理高度連接的資料時,擁有無與倫比的優勢。也正因為如此這個資料庫設計結構特別適合 情境(Domain)的連續。
關係導向的思考方式:
- 不是「這個實體有什麼屬性」
- 而是「這些實體間有什麼關係」
忘掉表格、欄位和主鍵。我們需要用一種新的眼光來看待資料 - 關聯
一個圖資料表最重要的有三個概念: 節點 (Nodes)-邊 (Edges / Relationships)-屬性 (Properties)
節點 (Nodes) - 實體
邊 (Edges / Relationships) - 關係
(A)-[關係]->(B)
和 (B)-[關係]->(A)
是不同的。屬性 (Properties) - 描述
當我們遇到的問題,可以被描述為 「尋找...的路徑」
、「分析...的關聯」
、「誰是...的中心」
、「這個群體有什麼特徵」
,等具有明顯 指標性 與 關連性 時,就應該把「圖資料庫」這個強大的工具納入我們的考量範圍。
設計思維的轉變:
在 SQL 中,我們想知道「A 的朋友的朋友」,我們需要把使用者表格自己跟自己 JOIN 兩次,效能會隨著資料量和關聯深度( 朋友的朋友的朋友... * N )急遽下降。在圖資料庫中,這個問題變成了「從 A 節點出發,沿著 FRIENDS_WITH
的邊走兩步,看看能到達哪些人」。這個操作對圖資料庫來說是原生且極度高效的,我們不再需要為了正規化而建立大量的「中間表」或「關聯表」。多對多的關係,在圖中就是一條直接的邊,非常直觀!
過去需要 JOIN {N} 個表格才能得到的資訊,現在可能只需要一條帶有豐富屬性的邊就能描述。
了解了設計思維,我們就會發現,圖資料庫特別適合那些「關係」比「實體」本身更重要的場景。
**常見應用場景:社交網路/金融風控/推薦系統/網路與 IT 維運 **
模型: (Person)-[:FRIENDS_WITH]->(Person)
、(Person)-[:LIKES]->(Post)
、(Person)-[:MEMBER_OF]->(Group)
。
應用:
(Person)-[:HAS_ACCOUNT]->(BankAccount)
、(Person)-[:USES_DEVICE]->(Device)
、(BankAccount)-[:SENT_TO]->(BankAccount)
。(Customer)-[:PURCHASED]->(Product)
、(Customer)-[:VIEWED]->(Product)
、(Product)-[:IN_CATEGORY]->(Category)
。(CustomerA)-[:PURCHASED]->(ProductA)<-[:PURCHASED]-(CustomerB)-[:PURCHASED]->(ProductB)
這樣的路徑,然後將 ProductB 推薦給 CustomerA。(Server)-[:CONNECTED_TO]->(Switch)
、(Application)-[:RUNS_ON]->(Server)
、(Database)-[:DEPENDS_ON]->(Server)
。我們來視覺化看看關於政黨傾向集群網絡分析系統
graph TD
%% --- 定義區塊 ---
subgraph "個人 (Individuals)"
P1(個人A)
P2(個人B)
P3(個人C)
P4(個人D)
end
subgraph "政黨 (Political Parties)"
style PartyA fill:#89cff0,stroke:#333,stroke-width:2px
style PartyB fill:#ffb3ba,stroke:#333,stroke-width:2px
PartyA(進步黨)
PartyB(保守黨)
end
subgraph "個人特質 (Traits)"
subgraph "核心信仰"
BeliefX(重視社會公平)
BeliefY(重視傳統價值)
end
subgraph "教育程度"
Edu1(大學)
Edu2(碩士/博士)
end
subgraph "年收入 (萬)"
Inc1(50-100)
Inc2(150-300)
end
subgraph "職業"
Job1(科技業/工程師)
Job2(金融業/律師)
Job3(教育/公部門)
end
end
%% --- 關係連結 ---
%% 個人A & B -> 歸屬於進步黨
P1 -->|屬於| PartyA
P2 -->|屬於| PartyA
%% 個人C & D -> 歸屬於保守黨
P3 -->|屬於| PartyB
P4 -->|屬於| PartyB
%% --- 描繪每個人的特質網絡 ---
%% 個人A: 高學歷、高收入、金融業、重視社會公平
P1 -->|持有| BeliefX
P1 -->|學歷| Edu2
P1 -->|年收| Inc2
P1 -->|職業| Job2
%% 個人B: 大學學歷、中等收入、科技業、重視社會公平
P2 -->|持有| BeliefX
P2 -->|學歷| Edu1
P2 -->|年收| Inc1
P2 -->|職業| Job1
%% 個人C: 大學學歷、中等收入、教育工作者、重視傳統價值
P3 -->|持有| BeliefY
P3 -->|學歷| Edu1
P3 -->|年收| Inc1
P3 -->|職業| Job3
%% 個人D: 高學歷、高收入、金融業、重視傳統價值
P4 -->|持有| BeliefY
P4 -->|學歷| Edu2
P4 -->|年收| Inc2
P4 -->|職業| Job2
在這個資料結構中,我們可以很簡單的發現幾個要點:
發現了嗎? 這是圖資料庫效用與威力最大的地方,根據 邊(關係) 我們可以輕而易舉的解讀資料意涵,我們不再需要思考如何透過中間表來「模擬」關係,而是可以直接地「描述」關係。圖資料庫,就是將人類在白板上描繪複雜關係的直觀思考過程,直接轉化為一種可儲存、可查詢的資料結構。它是一種對現實世界網絡關係的直接映射,而非表格化的抽象。我們在應用上的目標是設計一個「路網」,讓我們的核心查詢能像在高速公路上開車一樣順暢。不斷思考如何用最少的「步數」(遍歷)來回答我們的問題,並據此來調整資料結構中的節點、邊和屬性。
在應用上,當出現 關係優先 (Relationship-First) 的關鍵字時,他就代表著這個常態資料取用的情境是 關注情境(Domain)的連鎖 ,這時候就特別適合用圖思維去進行資料庫設計。
這時候其實我們其實發現了一件事,在這個 查找需求絕對大於寫入需求 的情境中,是不是很符合 CQRS(讀寫分離) 的應用? 我們是不是可以利用這個盡可能減少 查找 步數的設計脈絡,來盡可能地放大我讀取效能?
絕對可以,而且這正是許多資深架構師在處理高效能系統時,腦中所運行的「心法」之一。
這觸及到一個核心觀念:圖資料庫的設計思維,本質上就是一種極致的、以查詢為導向的「反正規化」哲學。
以下我們用 Uber Eats 來進行實戰範例
當我們打開 Uber Eats App 時,後端會執行類似這樣的查詢:
我們先依循 情境脈絡 來逐步想想我們可以怎麼設計節點 (Nodes)與邊 (Edges / Relationships)
點過 A(達美樂 Pizza + 百事可樂 真的很推) 的人還點了什麼?
User A
) 最近訂購過的餐廳 (Restaurant X
)Restaurant X
的其他用戶 (User B
, User C
)Restaurant Y
, Restaurant Z
)Restaurant Y
和 Restaurant Z
作為推薦結果Restaurant Z
有付錢購買企業推送方案,所以將 Restaurant Z
放置到推薦結果列表首位(index=0)我們(
User A
)可能喜歡的菜系
Cuisine A
, Cuisine B
)Restaurant W
Cuisine A
經常被一同喜愛的 Cuisine C
,並推薦提供 Cuisine C
的餐廳 (ex: 美系餐廳經常被推送墨系餐廳)但在 Uber Eats 這樣的平台上,有數百萬的用戶、數十萬的餐廳和數千萬的餐點,至少必須被滿足上述兩種情境,更別說來還有像是常見的「附近最熱門的餐廳」或「最多人點的餐點」
單單只有 General 的查找結果是無法滿足個人化推送的行銷利益最大化需求的,就像是:
這些問題的共同點是,它們都極度依賴 「關係」 ——用戶與餐廳的關係、用戶與菜系的關係、用戶與用戶之間的隱含關係。用傳統 SQL 來處理這些問題,會需要極其複雜且緩慢的 JOIN 查詢,根本無法滿足即時推薦的需求。同時,為了這樣子的需求特別根據每種不同情境去設計一個預測模型也是成本-效益極大不符合比例原則的。
那麼我們來進行我們的 圖譜模型設計 (The Blueprint)
節點 (Nodes):
邊 (Edges / Relationships):
:ORDERED {date, rating}
]-> (Restaurant):用戶訂購過某餐廳,邊上可以有日期和評分等屬性。:FAVORITED
]-> (Restaurant):用戶收藏了某餐廳。:SERVES
]-> (Cuisine):餐廳提供某種菜系。:CONTAINS
]-> (Ingredient):餐點包含某種食材。graph TD
subgraph "使用者端 (Client)"
UserDevice[📱<br>使用者手機 App]
end
subgraph "AWS 雲端架構"
APIGateway[🌐<br>API Gateway]
subgraph "即時交易路徑 (Real-time Transaction Path)"
LambdaOrder[λ<br>下單/評分 Lambda]
DynamoDB[(🗄️<br>DynamoDB)]
end
subgraph "非同步圖譜更新路徑 (Async Graph Update Path)"
Kinesis[🌊<br>Kinesis Data Streams]
LambdaGraph[λ<br>圖譜更新 Lambda]
Neptune[g<br>Amazon Neptune]
end
subgraph "推薦查詢路徑 (Recommendation Query Path)"
LambdaRecommend[λ<br>推薦服務 Lambda]
end
end
%% --- 資料流定義 ---
UserDevice -- "1. 下單/評分請求" --> APIGateway
APIGateway -- "2. 觸發" --> LambdaOrder
LambdaOrder -- "3a. 寫入訂單/評分" --> DynamoDB
LambdaOrder -- "3b. 發送事件" --> Kinesis
Kinesis -- "4. 觸發" --> LambdaGraph
LambdaGraph -- "5. 更新圖譜資料" --> Neptune
UserDevice -- "6. 請求推薦" --> APIGateway
APIGateway -- "7. 觸發" --> LambdaRecommend
LambdaRecommend -- "8. 查詢圖譜" --> Neptune
Neptune -- "9. 返回推薦 ID" --> LambdaRecommend
LambdaRecommend -- "10. (可選)從 DynamoDB 獲取細節" --> DynamoDB
LambdaRecommend -- "11. 返回推薦結果" --> UserDevice
%% --- 樣式 ---
style Neptune fill:#ff9900,stroke:#333,stroke-width:2px
style DynamoDB fill:#2c73d2,stroke:#333
style Kinesis fill:#c0392b,stroke:#333
實戰模擬場景:為「小名 (User_A)」推薦他可能喜歡的新餐廳。
觸發推薦: 小名打開 App,滑到推薦區塊。手機 App 發送一個「請求推薦」的 API 請求 (步驟 6)。
API 閘道與觸發: API Gateway 收到請求,驗證身分後,觸發 推薦服務 Lambda (LambdaRecommend) (步驟 7)。
核心圖查詢: LambdaRecommend 的核心任務是向 Amazon Neptune 發送一個圖查詢 (步驟 8)。這個查詢的邏輯是「協同過濾」:
- "尋找與小名 (User_A) 口味相似的人,看看他們還喜歡什麼小名沒吃過的餐廳。"
用圖查詢的語言來描述這個邏輯:
返回結果: Neptune 高效地完成這個遍歷,將 Rest_3 的 ID 返回給 LambdaRecommend (步驟 9)。
豐富化資料 (可選): LambdaRecommend 拿到 Rest_3 的 ID 後,可能會再去 DynamoDB 查詢這家餐廳的詳細資訊,如完整名稱、地址、圖片 URL 等 (步驟 10)。DynamoDB 非常適合這種 Key-Value 形式的快速查找。
呈現給使用者: LambdaRecommend 將完整的推薦結果(包含餐廳名稱、圖片等)打包成 JSON,透過 API Gateway 返回給小名的手機 App (步驟 11)。小名於是看到了「瓦城」出現在他的推薦列表上。
這個推薦能夠成功,前提是圖譜裡的資料是即時的。這就是「非同步圖譜更新路徑」的作用。
資料如何進入圖譜?(非同步路徑)
這個架構實現了完美的 讀寫分離 與 關注點分離
當我們遇到的問題,可以被描述為「尋找...的路徑」、「分析...的關聯」、「誰是...的中心」、「這個群體有什麼特徵」,這些特別強調 讀取絕對大於寫入 且重視 情境(Domain)的連續 ,就應該把 「圖資料庫」 這個強大的工具納入我們的考量範圍。