今日のゴール
- イベント駆動プログラミングの概念を理解する
- nindaのサブスクリプションシステムを使いこなす
- リアクティブなアプリケーションを構築する
ポーリング vs イベント駆動
ポーリングの問題点
# ❌ ポーリング: 非効率
while true:
let result = await ts.tryReadAsync(pattern)
if result.isSome:
break
await sleepAsync(100) # CPUを使い続ける
| 問題 | 影響 |
|---|---|
| CPU時間の無駄 | バッテリー消費、コスト増 |
| 遅延 | ポーリング間隔分の反応遅れ |
| スケール困難 | 監視対象が増えると負荷も増加 |
イベント駆動の利点
# ✅ イベント駆動: 効率的
let sub = await ts.subscribeAsync(pattern, {evWrite})
while sub.isActive:
let event = await sub.recvEvent()
# 変更があったときだけここに来る
イベントタイプ
nindaは3種類のイベントを提供します:
| イベント | 発生条件 | ユースケース |
|---|---|---|
evWrite |
タプルが書き込まれた | 新規データの監視 |
evTake |
タプルが取り出された | 消費の追跡 |
evExpire |
タプルが期限切れ | TTLベースのクリーンアップ |
基本的な使い方
# サブスクリプション作成
let sub = await ts.subscribeAsync(
toPattern(strVal("order"), nilValue()), # パターン
{evWrite, evTake} # 監視するイベント
)
# イベント受信
let event = await sub.recvEvent()
echo event.eventType # evWrite or evTake
echo event.data # 対象のタプル
# 購読解除(必須!)
sub.unsubscribe()
パターンでフィルタリング
パターンを使って、関心のあるタプルだけを監視できます。
# "order"タプルのみを購読
let orderSub = await ts.subscribeAsync(
toPattern(strVal("order"), nilValue(), nilValue()),
{evWrite}
)
# "log"タプルのみを購読
let logSub = await ts.subscribeAsync(
toPattern(strVal("log"), nilValue()),
{evWrite}
)
# "user"タプルは誰も受信しない
ブロッキング vs 非ブロッキング
| メソッド | 動作 | 用途 |
|---|---|---|
recvEvent() |
イベントが来るまで待機 | 専用の監視ループ |
tryRecvEvent() |
即座にNone or Someを返す | 他処理と並行 |
# 非ブロッキング: イベントループ内で使用
while running:
let event = sub.tryRecvEvent()
if event.isSome:
processEvent(event.get())
# 他の処理も実行
await doOtherWork()
実践例: リアルタイム監視
# 監視プロセス
proc monitor() {.async.} =
let sub = await ts.subscribeAsync(
toPattern(strVal("metric"), nilValue(), nilValue()),
{evWrite}
)
while sub.isActive:
let event = await sub.recvEvent()
let name = event.data[1].strVal
let value = event.data[2].intVal
# しきい値チェック
if name == "cpu" and value > 80:
echo "[ALERT] High CPU: ", value, "%"
ポイント:
-
evWriteを監視してリアルタイム検知 - パターンで
metricタプルだけをフィルタ - 即座にアラート発生
ベストプラクティス
1. 必ずunsubscribeする
let sub = await ts.subscribeAsync(pattern, {evWrite})
try:
# イベント処理
while sub.isActive:
let event = await sub.recvEvent()
processEvent(event)
finally:
sub.unsubscribe() # リソースリーク防止
2. isActiveをチェック
while sub.isActive: # unsubscribe後はfalseになる
let event = await sub.recvEvent()
3. バッファサイズを適切に設定
# 高頻度イベント用
let highFreqSub = await ts.subscribeAsync(pattern, {evWrite}, bufferSize = 1000)
# 低頻度イベント用(メモリ節約)
let lowFreqSub = await ts.subscribeAsync(pattern, {evWrite}, bufferSize = 10)
💡 バッファが溢れると古いイベントが破棄されます。想定イベント頻度に合わせて設定しましょう。
まとめ
学んだこと
| トピック | ポイント |
|---|---|
| サブスクリプション |
subscribeAsyncでイベントを購読 |
| イベントタイプ |
evWrite, evTake, evExpire
|
| パターンフィルタ | 関心のあるタプルだけを監視 |
| リソース管理 | 必ずunsubscribe()を呼ぶ |
いつ使うか
- リアルタイム監視: メトリクス、ログ
- 変更通知: データ更新の伝播
- イベントソーシング: 状態変更の追跡
演習問題
問題9-1: チャットシステム
メッセージタプルの書き込みを監視して、リアルタイムにメッセージを表示するシステムを作成してください。
問題9-2: 在庫アラート
在庫数が10以下になったときにアラートを出すシステムを、イベント駆動で実装してください。
問題9-3: イベント集計
5秒間に発生したイベント数をカウントして、1秒ごとに表示するプログラムを書いてください。
💡 完全な実装例は GitHubリポジトリ を参照してください。