在分散式系統中,如何快速找到特定的 Workflow 是一個常見的挑戰。
Temporal 提供了 Search Attributes 的功能為 Workflow 強化查詢和過濾能力。
Search Attributes 是附加在 Workflow 執行上的索引化 Meta Data,可以使用查詢語法來搜尋和過濾 Workflow。
它們就像是為你的 Workflow 加上標籤,讓你能快速找到符合特定條件的 Workflow。
在本指南中,我們聚焦在兩個最核心且最常用的 Search Attributes 作為範例:
Search Attribute | 類型 | 使用場景 | 為什麼重要 |
---|---|---|---|
CustomerId | Text | 使用者/客戶識別 | 幾乎所有業務都需要按使用者查詢 |
OrderStatus | Keyword | 訂單狀態管理 | 狀態是 Workflow 最核心的屬性 |
CustomerId: 'user_12345'
// 特點:
// - 精確匹配:CustomerId = "user_12345"
// - 適用於:使用者 ID、訂單號碼等識別符
OrderStatus: 'pending' // 待處理
OrderStatus: 'processing' // 處理中
OrderStatus: 'completed' // 已完成
// 特點:
// - 只能精確匹配:OrderStatus = "pending"
// - 效能最佳
// - 值應該是預定義的有限集合(如狀態列表)
這兩個 Search Attributes 可以滿足大多數業務場景:
CustomerId
)OrderStatus
)建立 Search Attributes 是在 Temporal Server 的註冊操作,就像是在資料庫中建立表格 schema。
❌ 如果沒有先建立,直接使用會報錯:
client.workflow.start(orderWorkflow, {
searchAttributes: {
CustomerId: ['user_123'], // Error: CustomerId is not defined!
},
});
這是基礎設施的一部分,通常由運維團隊或系統管理員執行,只需要執行一次,你需要告訴 Temporal:
# 建立兩個 Search Attributes
temporal operator search-attribute create --namespace default --name CustomerId --type Text
temporal operator search-attribute create --namespace default --name OrderStatus --type Keyword
# 查看已建立的 Search Attributes
temporal operator search-attribute list --namespace default
# 輸出範例:
# +-------------+----------+
# | NAME | TYPE |
# +-------------+----------+
# | CustomerId | Text |
# | OrderStatus | Keyword |
# +-------------+----------+
角色 | 職責 | 時機 |
---|---|---|
運維/DevOps | 在 Temporal 中定義 Search Attributes | 部署前或新增功能時 |
開發人員 | 在程式碼中設定 Search Attributes 的值 | Workflow 執行時 |
設定 Search Attributes 是在 應用程式層面 的操作,就是向資料表中插入資料,發生在 Workflow 執行時。你需要:
import { Client } from '@temporalio/client';
const client = new Client();
// ✅ 在啟動時設定初始 Search Attributes
await client.workflow.start(orderWorkflow, {
taskQueue: 'order-queue',
workflowId: 'order-12345',
args: [orderData],
searchAttributes: {
CustomerId: ['user_789'],
OrderStatus: ['pending'],
},
});
使用場景:
import { upsertSearchAttributes } from '@temporalio/workflow';
export async function orderWorkflow(orderData: OrderData) {
// 初始處理
console.log('Order created');
// 👇 處理支付後更新狀態
await processPayment();
upsertSearchAttributes({
OrderStatus: ['payment_completed'],
});
// 👇 發貨後再次更新
await shipOrder();
upsertSearchAttributes({
OrderStatus: ['shipped'],
});
// 👇 完成訂單
upsertSearchAttributes({
OrderStatus: ['completed'],
});
}
使用場景:
// ❌ 錯誤:使用未定義的 Search Attribute
await client.workflow.start(orderWorkflow, {
searchAttributes: {
UnknownField: ['value'], // Error! UnknownField 未在 Temporal 中定義
},
});
// ✅ 正確:只使用已定義的 Search Attributes
// 前提:CustomerId 已透過 CLI 定義為 Text 類型
await client.workflow.start(orderWorkflow, {
searchAttributes: {
CustomerId: ['user_123'], // ✅ CustomerId 已定義
},
});
// ✅ 正確:所有值都要用陣列包裝
searchAttributes: {
CustomerId: ['user_123'],
OrderStatus: ['pending'],
}
// ❌ 錯誤:直接賦值
searchAttributes: {
CustomerId: 'user_123', // 缺少陣列包裝
OrderStatus: 'pending', // 缺少陣列包裝
}
Temporal 使用類似 SQL 的查詢語言:
// 1. 精確匹配
CustomerId = "user_123"
OrderStatus = "completed"
// 2. 組合查詢
CustomerId = "user_123" AND OrderStatus = "pending"
OrderStatus = "pending" OR OrderStatus = "processing"
CustomerId = "user_123" AND OrderStatus != "cancelled"
const client = new Client();
// 查詢特定使用者的待處理訂單
const workflows = await client.workflow.list({
query: 'CustomerId = "user_123" AND OrderStatus = "pending"',
});
for await (const workflow of workflows) {
console.log(`Workflow ID: ${workflow.workflowId}`);
console.log(`Search Attributes:`, workflow.searchAttributes);
}
// 1. 查找特定使用者的所有訂單
'CustomerId = "user_123"'
// 2. 查找特定狀態的訂單
'OrderStatus = "pending"'
// 3. 查找特定使用者的待處理訂單
'CustomerId = "user_123" AND OrderStatus = "pending"'
// 4. 查找多個狀態的訂單
'OrderStatus = "pending" OR OrderStatus = "processing"'
// 5. 排除已取消的訂單
'OrderStatus != "cancelled"'
✅ 應該作為 Search Attributes 的:
CustomerId
)OrderStatus
)❌ 不應該作為 Search Attributes 的:
// ✅ 好的命名
CustomerId // 清晰、具體
OrderStatus // 容易理解
// ❌ 不好的命名
id // 太模糊
status // 哪個 status?
建議命名規則:
每個 Workflow 的 Search Attributes:
兩個核心屬性已經可以滿足大多數查詢需求:
CustomerId
- 使用者識別OrderStatus
- 狀態過濾// ✅ 好的做法:只在狀態變化時更新
export async function orderWorkflow(orderData: OrderData) {
// 初始狀態
upsertSearchAttributes({
OrderStatus: ['pending']
});
// 支付完成後更新
await processPayment();
upsertSearchAttributes({
OrderStatus: ['paid']
});
// 出貨後更新
await shipOrder();
upsertSearchAttributes({
OrderStatus: ['shipped']
});
}
// ❌ 避免:頻繁無意義的更新
export async function badWorkflow() {
for (let i = 0; i < 1000; i++) {
upsertSearchAttributes({
OrderStatus: ['processing']
}); // 重複設定相同值,無意義
}
}
// ✅ 定義明確的介面
interface OrderSearchAttributes {
CustomerId: string; // Text
OrderStatus: string; // Keyword
}
// 在程式中使用
const searchAttrs: Record<string, [any]> = {
CustomerId: ['user_123'],
OrderStatus: ['pending'],
};
兩個核心屬性的選擇性分析:
Search Attribute | 選擇性 | 適合索引 | 原因 |
---|---|---|---|
CustomerId |
高 | ✅ 非常適合 | 每個使用者唯一,查詢精確 |
OrderStatus |
中 | ✅ 適合 | 雖然值有限,但常用於過濾 |
// ✅ 優先使用高選擇性欄位
CustomerId = "user_12345" // 選擇性高,效能佳
// ✅ 組合查詢更精確
CustomerId = "user_12345" AND OrderStatus = "pending"
// ✅ 可接受的查詢:狀態過濾
OrderStatus = "pending" // 選擇性中等,但業務必需
// ✅ 優先使用高選擇性欄位
CustomerId = "user_123" // 好:直接定位使用者
// ✅ 組合條件更精確
CustomerId = "user_123" AND OrderStatus = "pending"'
// ⚠️ 避免過於寬鬆的查詢
OrderStatus != "cancelled" // 差:否定條件效能較差
// ✅ 建議的查詢模式
CustomerId = "user_123" AND OrderStatus = "pending"
// ❌ 錯誤:值不是陣列
searchAttributes: {
CustomerId: 'user_123',
OrderStatus: 'pending',
}
// ✅ 正確
searchAttributes: {
CustomerId: ['user_123'],
OrderStatus: ['pending'],
}
// ❌ 錯誤
'OrderStatus == "pending"' // 應該用 = 不是 ==
'CustomerId = "user_123" && OrderStatus = "pending"' // 應該用 AND 不是 &&
// ✅ 正確
'OrderStatus = "pending"'
'CustomerId = "user_123" AND OrderStatus = "pending"'
// ❌ 不要直接儲存敏感資訊
searchAttributes: {
CreditCardNumber: ['1234-5678-9012-3456'], // 危險!
Email: ['user@example.com'], // 可能違反隱私規範
}
// ✅ 使用不敏感的識別符
searchAttributes: {
CustomerId: ['user_12345'], // 使用系統 ID
OrderStatus: ['pending'], // 狀態資訊
}
Search Attributes 是讓 Workflow「可查、可篩、可管理」的關鍵工具。
只要事先設計好幾個核心欄位,就能快速定位特定流程、追蹤業務狀態,讓整個系統在日後的營運、除錯與觀測上都更有彈性。
開始時不用太複雜,先從少數幾個關鍵做起,即可提升可觀測性與維運效率。