iT邦幫忙

2025 iThome 鐵人賽

DAY 29
1
Software Development

Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程系列 第 29

Day29 - Temporal Search Attributes(上)入門實作

  • 分享至 

  • xImage
  •  

在分散式系統中,如何快速找到特定的 Workflow 是一個常見的挑戰。

Temporal 提供了 Search Attributes 的功能為 Workflow 強化查詢和過濾能力。

1. 什麼是 Search Attributes?

Search Attributes 是附加在 Workflow 執行上的索引化 Meta Data,可以使用查詢語法來搜尋和過濾 Workflow。

它們就像是為你的 Workflow 加上標籤,讓你能快速找到符合特定條件的 Workflow。

1.1 核心概念

  • Search Attributes 會被索引到資料庫中
  • 支援高效的查詢操作
  • 可在 Workflow 執行過程中動態更新
  • 每個 Search Attribute 都有明確的資料類型
  • 類型必須在使用前確定
  • 支援多種資料類型

2. 以兩個常用 Search Attributes 為例

在本指南中,我們聚焦在兩個最核心且最常用的 Search Attributes 作為範例:

Search Attribute 類型 使用場景 為什麼重要
CustomerId Text 使用者/客戶識別 幾乎所有業務都需要按使用者查詢
OrderStatus Keyword 訂單狀態管理 狀態是 Workflow 最核心的屬性

2.1 CustomerId (Text 類型)

CustomerId: 'user_12345'

// 特點:
// - 精確匹配:CustomerId = "user_12345"
// - 適用於:使用者 ID、訂單號碼等識別符

2.2 OrderStatus (Keyword 類型)

OrderStatus: 'pending'      // 待處理
OrderStatus: 'processing'   // 處理中
OrderStatus: 'completed'    // 已完成

// 特點:
// - 只能精確匹配:OrderStatus = "pending"
// - 效能最佳
// - 值應該是預定義的有限集合(如狀態列表)

這兩個 Search Attributes 可以滿足大多數業務場景:

  • 識別查詢:找到特定使用者的所有訂單 (CustomerId)
  • 狀態過濾:找到所有待處理的訂單 (OrderStatus)
  • 組合查詢:找到特定使用者的特定狀態訂單

3. 建立 Search Attributes

3.1 建立

建立 Search Attributes 是在 Temporal Server 的註冊操作,就像是在資料庫中建立表格 schema。

❌ 如果沒有先建立,直接使用會報錯:

client.workflow.start(orderWorkflow, {
  searchAttributes: {
    CustomerId: ['user_123'],  // Error: CustomerId is not defined!
  },
});

這是基礎設施的一部分,通常由運維團隊或系統管理員執行,只需要執行一次,你需要告訴 Temporal:

  1. 指定 Search Attribute 名字
  2. 它屬於哪個 namespace
  3. 指定資料類型,才可以
    • 正確建立索引
    • 驗證資料格式
    • 優化查詢效能

3.2 使用 Temporal CLI 建立與查看

# 建立兩個 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  |
# +-------------+----------+

3.3 建立的時機與責任

角色 職責 時機
運維/DevOps 在 Temporal 中定義 Search Attributes 部署前或新增功能時
開發人員 在程式碼中設定 Search Attributes 的值 Workflow 執行時

4. 設定 Search Attributes

4.1 設定

設定 Search Attributes 是在 應用程式層面 的操作,就是向資料表中插入資料,發生在 Workflow 執行時。你需要:

  1. 使用已經在 Temporal 中建立好的 Search Attribute
  2. 為它們賦予具體的值
  3. 這些值會被索引,用於後續查詢

4.2 設定的兩個時機點

時機 1: 啟動 Workflow 時設定(Client 端)

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'],
  },
});

使用場景:

  • 設定不會改變的屬性(如 CustomerId)
  • 設定初始狀態(如 OrderStatus: 'pending')
  • 需要立即可查詢的屬性

時機 2: Workflow 執行中更新(Workflow 內部)

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'],
  });
}

使用場景:

  • 狀態變化時更新
  • 計算出新值後更新
  • 階段性更新進度

4.2 設定必須對應已定義的 Search Attributes

// ❌ 錯誤:使用未定義的 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 已定義
  },
});

4.3 重要注意事項:值必須是陣列格式

// ✅ 正確:所有值都要用陣列包裝
searchAttributes: {
  CustomerId: ['user_123'],
  OrderStatus: ['pending'],
}

// ❌ 錯誤:直接賦值
searchAttributes: {
  CustomerId: 'user_123',      // 缺少陣列包裝
  OrderStatus: 'pending',      // 缺少陣列包裝
}

5. 查詢 Search Attributes

5.1 基本查詢語法

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"

5.2 在程式中執行查詢

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);
}

5.3 常用查詢模式

// 1. 查找特定使用者的所有訂單
'CustomerId = "user_123"'

// 2. 查找特定狀態的訂單
'OrderStatus = "pending"'

// 3. 查找特定使用者的待處理訂單
'CustomerId = "user_123" AND OrderStatus = "pending"'

// 4. 查找多個狀態的訂單
'OrderStatus = "pending" OR OrderStatus = "processing"'

// 5. 排除已取消的訂單
'OrderStatus != "cancelled"'

6. 設計最佳實踐

6.1 選擇合適的 Search Attributes

✅ 應該作為 Search Attributes 的:

  • 業務關鍵識別符(如 CustomerId
  • 狀態標記(如 OrderStatus
  • 需要查詢和過濾的欄位

❌ 不應該作為 Search Attributes 的:

  • 大量文字內容
  • 頻繁變動的資料
  • 敏感資訊
  • 不需要查詢的資料

6.2 命名規範

// ✅ 好的命名
CustomerId          // 清晰、具體
OrderStatus         // 容易理解

// ❌ 不好的命名
id                  // 太模糊
status              // 哪個 status?

建議命名規則:

  • 使用 PascalCase
  • 具有描述性
  • 包含業務含義

6.3 數量建議

每個 Workflow 的 Search Attributes:

  • 理想數量:2-3 個
  • ⚠️ 可接受:最多 5 個
  • 避免:超過 10 個

兩個核心屬性已經可以滿足大多數查詢需求:

  • CustomerId - 使用者識別
  • OrderStatus - 狀態過濾

6.4 更新策略

// ✅ 好的做法:只在狀態變化時更新
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'] 
    }); // 重複設定相同值,無意義
  }
}

6.5 類型一致性

// ✅ 定義明確的介面
interface OrderSearchAttributes {
  CustomerId: string;      // Text
  OrderStatus: string;     // Keyword
}

// 在程式中使用
const searchAttrs: Record<string, [any]> = {
  CustomerId: ['user_123'],
  OrderStatus: ['pending'],
};

7. 效能優化

7.1 索引效能考量

兩個核心屬性的選擇性分析:

Search Attribute 選擇性 適合索引 原因
CustomerId ✅ 非常適合 每個使用者唯一,查詢精確
OrderStatus ✅ 適合 雖然值有限,但常用於過濾
// ✅ 優先使用高選擇性欄位
CustomerId = "user_12345"           // 選擇性高,效能佳

// ✅ 組合查詢更精確
CustomerId = "user_12345" AND OrderStatus = "pending"

// ✅ 可接受的查詢:狀態過濾
OrderStatus = "pending"             // 選擇性中等,但業務必需

7.2 查詢優化

// ✅ 優先使用高選擇性欄位
CustomerId = "user_123"                          // 好:直接定位使用者

// ✅ 組合條件更精確
CustomerId = "user_123" AND OrderStatus = "pending"'

// ⚠️ 避免過於寬鬆的查詢
OrderStatus != "cancelled"                       // 差:否定條件效能較差

// ✅ 建議的查詢模式
CustomerId = "user_123" AND OrderStatus = "pending"

8. 常見錯誤與解決方案

8.1 錯誤 1:值格式錯誤

// ❌ 錯誤:值不是陣列
searchAttributes: {
  CustomerId: 'user_123',
  OrderStatus: 'pending',
}

// ✅ 正確
searchAttributes: {
  CustomerId: ['user_123'],
  OrderStatus: ['pending'],
}

8.2 錯誤 2:查詢語法錯誤

// ❌ 錯誤
'OrderStatus == "pending"'                    // 應該用 = 不是 ==
'CustomerId = "user_123" && OrderStatus = "pending"'  // 應該用 AND 不是 &&

// ✅ 正確
'OrderStatus = "pending"'
'CustomerId = "user_123" AND OrderStatus = "pending"'

8.3 錯誤 3:安全性考量

// ❌ 不要直接儲存敏感資訊
searchAttributes: {
  CreditCardNumber: ['1234-5678-9012-3456'],  // 危險!
  Email: ['user@example.com'],                 // 可能違反隱私規範
}

// ✅ 使用不敏感的識別符
searchAttributes: {
  CustomerId: ['user_12345'],                  // 使用系統 ID
  OrderStatus: ['pending'],                    // 狀態資訊
}

結語

Search Attributes 是讓 Workflow「可查、可篩、可管理」的關鍵工具。

只要事先設計好幾個核心欄位,就能快速定位特定流程、追蹤業務狀態,讓整個系統在日後的營運、除錯與觀測上都更有彈性。

開始時不用太複雜,先從少數幾個關鍵做起,即可提升可觀測性與維運效率。


上一篇
Day28 - AI 對話平台:整合 Temporal 與 AI Agents (下)
下一篇
Day30 - Temporal Search Attributes(下)設計策略
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言