「大師,」洛基說,「昨天我學會了 Query 的技術邊界。您提到在這些邊界下,仍然還有一些更好的做法?」
諾斯克大師點頭:「讓我們從一個實際的場景開始探索。」
小行星5020前哨站的新需求:
查詢火星基地中報名人數超過 50 人的活動
洛基思考:「這個需求需要根據報名人數過濾。根據昨天的學習,這超出了 Query 的 Key Condition 範圍...」
他困惑地看著大師:「我只能先查詢出火星基地的所有活動,然後在應用程式中一個個檢查報名人數嗎?這和關聯式資料庫比起來真的有點不方便。」
大師說,「的確 DynamoDB 有提供不用在應用程式處理的做法,Hippo 你可以出場了。」
Hippo 的聲音響起:「我來!這個時候正是 Filter Expression 可以派上用場的地方!」
洛基好奇:「Filter Expression?那是什麼?」
「簡單來說,」Hippo 解釋,「它讓 DynamoDB 幫你過濾結果,而不是把所有資料傳回應用程式。讓我示範給你看:」
# Hippo 的示範
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "PK = :pk" \
--filter-expression "registered > :count" \
--expression-attribute-values '{
":pk": {"S": "PLANET#MARS"},
":count": {"N": "50"}
}' \
--endpoint-url http://localhost:8000
洛基:「看起來很簡潔!所以 DynamoDB 會自動過濾出報名人數超過 50 的活動?讓我試試看。」
大師在一旁微微搖頭,但沒有立即打斷。
執行查詢後,結果出現了:
{
"Items": [
// 10 個報名人數超過 50 的活動
],
"Count": 10,
"ScannedCount": 200,
"ConsumedCapacity": {
"CapacityUnits": 50
}
}
洛基一開始很高興:「太棒了!我得到了 10 個符合條件的活動!」
但他很快注意到一些奇怪的地方:「等等...Count 是 10,但 ScannedCount 是 200?這兩個數字為什麼不一樣?」
大師這時開口了:「你剛剛發現了 Filter Expression 的第一個陷阱。」
洛基開始分析這個結果:「讓我理解一下...ScannedCount 是 200,意味著 DynamoDB 掃描了 200 個項目?」
「正確,」大師說。
「但只有 10 個符合條件...」洛基開始計算,「10 除以 200,效率只有 5%?」
大師點頭:「繼續思考,這對成本意味著什麼?」
洛基臉色變了:「等等!如果 DynamoDB 的計費是基於讀取的項目數量...」
「沒錯,」大師在白板上寫下:
Filter Expression 的真實成本:
- 你為 ScannedCount(200個)付費
- 不是為 Count(10個)付費
- 這個查詢的效率:5%
- 浪費的讀取:95%
洛基驚訝地說:「所以如果火星基地有 1000 個活動,而只有 50 個報名人數超過 50...」
「你會為 1000 個項目的讀取付費,」大師確認,「卻只得到 50 個結果。Filter Expression 看起來方便,但它不是免費的午餐。」
洛基試圖理解:「那什麼時候使用 Filter Expression 才是明智的?」
「好問題,」大師說,「讓我們分析一下『選擇性』這個概念。」
洛基困惑:「選擇性?」
「就是符合條件的項目佔總項目的比例,」大師解釋,並在白板上畫出兩個對比場景:
場景 A:洛基的個人報名記錄
- 總共:20 個報名記錄
- 有效的:19 個
- 選擇性:19/20 = 95%
- Filter Expression 效率:優秀
場景 B:火星基地的高報名活動
- 總共:1000 個活動
- 高報名(>50人):50 個
- 選擇性:50/1000 = 5%
- Filter Expression 效率:糟糕
洛基恍然大悟:「所以選擇性越高,Filter Expression 越有效率!」
「正是,」大師說,「當選擇性過低時,你應該考慮其他方案。」
大師展示兩個對比案例:
案例 A:高選擇性(適合使用)
# 查詢洛基的有效課程報名
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "PK = :pk" \
--filter-expression "#status = :status" \
--expression-attribute-names '{"#status": "status"}' \
--expression-attribute-values '{
":pk": {"S": "USER#LOKI"},
":status": {"S": "ACTIVE"}
}'
# 結果分析:
# 洛基有 15 個報名記錄,其中 14 個是有效的
# 選擇性:93%
# 每日查詢:30 次
# 成本效益:優秀
案例 B:低選擇性(避免使用)
# 查詢所有星球的高報名活動
aws dynamodb scan \
--table-name IntergalacticEvents \
--filter-expression "registered > :count" \
--expression-attribute-values '{
":count": {"N": "80"}
}'
# 結果分析:
# 總共 5000 個活動,其中 250 個是高報名活動
# 選擇性:5%
# 每日查詢:20 次
# 成本效益:非常差
洛基看到差別:「案例 A 效率很高,案例 B 效率很低。但如果案例 B 是重要需求怎麼辦?」
大師點頭:「這就是『工具使用智慧』的體現。當單一工具無法高效解決問題時,我們需要混合策略。」
// 混合策略示例:先縮小範圍,再過濾
async function findHighRegistrationEvents() {
// 策略:先按星球查詢(高效),再在應用程式中過濾
const planets = ["MARS", "VENUS", "EARTH"];
const results = [];
for (const planet of planets) {
// Step 1: 用 Query 高效獲取單一星球的活動
const planetEvents = await dynamodb
.query({
KeyConditionExpression: "PK = :pk",
ExpressionAttributeValues: {
":pk": `PLANET#${planet}`,
},
})
.promise();
// Step 2: 在應用程式中過濾高報名活動
const highRegEvents = planetEvents.Items.filter(
(event) => event.registered > 80
);
results.push(...highRegEvents);
}
return results;
}
洛基分析:「這個策略將一個低效率的全域查詢,分解為多個高效率的局部查詢,然後在應用程式中合併結果?」
「正確,」大師說,「這是在 DynamoDB 限制下的務實選擇。」
洛基開始整理思路:「我覺得不能只看選擇性,還要考慮查詢頻率吧?」
「非常好的觀察!」大師讚許,「你能解釋你的想法嗎?」
洛基在筆記本上畫出自己的分析:「如果一個查詢每天只執行一次,即使選擇性低,總成本也有限。但如果每天執行 100 次...」
「繼續,」大師鼓勵。
洛基完成了他的決策矩陣:
洛基的 Filter Expression 決策框架:
查詢頻率 × 選擇性 = 建議策略
高頻查詢(>100次/天):
- 高選擇性(>50%)→ 可以使用 Filter Expression
- 低選擇性(<20%)→ 必須重新設計!成本會爆炸
中頻查詢(10-100次/天):
- 高選擇性(>50%)→ 可以使用 Filter Expression
- 低選擇性(<20%)→ 考慮混合策略
低頻查詢(<10次/天):
- 任何選擇性 → 可接受,因為總成本有限
大師點頭:「你已經掌握了工具使用的核心。」
洛基將框架應用到實際場景:
// 基於頻率和選擇性的實際決策
const queryStrategies = {
// 高頻 + 高選擇性 → 使用 Filter Expression
用戶活躍報名: {
frequency: "每日 150 次",
selectivity: "90%",
strategy: "Query + Filter Expression",
reasoning: "高效率,成本可控",
},
// 高頻 + 低選擇性 → 重新設計
高報名活動查詢: {
frequency: "每日 120 次",
selectivity: "5%",
strategy: "建立專用視角 PK='CATEGORY#HIGH_REGISTRATION'",
reasoning: "頻率太高,不能容忍低效率",
},
// 低頻 + 低選擇性 → 接受低效率
管理報表查詢: {
frequency: "每日 3 次",
selectivity: "8%",
strategy: "Scan + Filter Expression",
reasoning: "頻率低,成本可控",
},
};
大師強調:「使用 Filter Expression 時,監控是關鍵:」
// 簡單的效率監控
function monitorQueryEfficiency(result) {
const efficiency = result.Count / result.ScannedCount;
console.log(`查詢效率分析:
返回項目:${result.Count}
掃描項目:${result.ScannedCount}
效率:${(efficiency * 100).toFixed(1)}%
建議:${getEfficiencyRecommendation(efficiency)}
`);
}
function getEfficiencyRecommendation(efficiency) {
if (efficiency > 0.7) return "效率優秀,維持現狀";
if (efficiency > 0.3) return "效率可接受,持續監控";
if (efficiency > 0.1) return "效率偏低,考慮優化";
return "效率很差,需要重新設計";
}
大師最後說:「Filter Expression 只是眾多工具之一。成熟的工程師會評估所有選項:」
工具選擇光譜:
1. Query(純)→ 最高效率,但需要預先設計視角
2. Query + Filter Expression → 平衡效率與靈活性
3. 混合策略 → 多次 Query + 應用程式合併
4. Scan + Filter Expression → 最靈活,但效率最低
5. 外部工具(ElasticSearch)→ 複雜查詢的終極解決方案
洛基點頭:「所以關鍵是根據具體需求選擇合適的工具,而不是固執地使用單一工具?」
「完全正確,」大師說,「這是成熟度的體現。」