ステートレス・アーキテクチャ

VCRは、すべてのアプリケーションの複数のレプリカを実行し、負荷に応じてさらにスケールすることができる。また、アイドル時にはレプリカをゼロにスケールダウンすることもできる。これは、ステートの管理方法にとって重要な意味を持つ。

インメモリー状態が安全でない理由

メモリに保存されているデータ(グローバル変数、モジュールレベルのオブジェクト、プロセス内キャッシュ)はすべてそうである:

  • 1つのレプリカにのみローカル
  • 再起動またはスケール・トゥ・ゼロで失われる
  • 同時リクエストを処理する他のレプリカからは見えない

2つのリクエストが同じレプリカをヒットすると仮定してはならない。今日そうであったとしても、負荷が増えればそうではなくなります。

違う:

// Replica-local — invisible to other replicas and lost on restart
const cache = {};
cache['userId'] = { name: 'Alice' };

let requestCount = 0;
requestCount++;

その通りだ:

import { vcr } from '@vonage/vcr-sdk';

const state = vcr.getInstanceState();
await state.set('userId', { name: 'Alice' });
await state.increment('requestCount', 1);

正しい州スコープの選択

スコープ アクセス方法 視認性
セッション new State(session) 1セッションのみ
インスタンス vcr.getInstanceState() このデプロイされたインスタンスのすべてのレプリカ
Account vcr.getAccountState() Vonageアカウントのすべてのアプリケーション

vcr.getInstanceState() は共有ステートの正しいデフォルトです。これは、Stateを現在のインスタンスIDにスコープする便利なラッパーで、同じデプロイのすべてのレプリカが同じストアを共有します。

vcr.getAccountState() は、複数のVCRアプリケーションが読み取る必要のある共有ブロックリストやコンフィギュレーションなど、クロスアプリケーションデータ用です。

セッションの状態 (new State(session))は、会話コンテキストや通話状態など、単一のユーザーとの対話に属するデータに適している。

置き換えるべき一般的なインメモリ・パターン

パターン 交換
const cache = {} モジュールレベル vcr.getInstanceState()
リクエストの状態を保持するシングルトンオブジェクト vcr.getInstanceState() または vcr.createSessionWithId(userId)
global.something = ... vcr.getInstanceState()
リクエストごとにカウンタがインクリメントされる state.increment('counter', 1)

コード例

Node.js - レプリカ間でカウンタを共有:

import { vcr } from '@vonage/vcr-sdk';

const state = vcr.getInstanceState();

// Increment atomically — safe across replicas
await state.increment('requestCount', 1);
const count = await state.get('requestCount');

Node.js - ユーザーごとのセッション状態:

import { vcr, State } from '@vonage/vcr-sdk';

app.post('/message', async (req, res) => {
  // Each user gets an isolated state namespace
  const session = vcr.createSessionWithId(req.body.userId);
  const state = new State(session);
  await state.set('lastSeen', new Date().toISOString());
  res.sendStatus(200);
});

Python - レプリカ間で状態を共有:

from vonage_cloud_runtime.vcr import VCR

vcr = VCR()
state = vcr.getInstanceState()

await state.set('key', 'value')
value = await state.get('key')

セッション・スコープのベストプラクティス

クリティカルだ: 誤用 vcr.createSession() は、VCRアプリケーションで最も一般的なバグの原因の1つです。セッションのスコープを理解することは、正しくスケールするアプリケーションを構築するために不可欠です。

の問題点 vcr.createSession() グローバル・スコープにて

vcr.createSession() を生成する。 泡沫夢幻 セッションIDは呼び出されるたびに変わる。モジュール/グローバル・スコープでこのメソッドを呼び、その結果のセッションを new State(session):

  • 各レプリカは起動時に独自のランダムセッションを作成する
  • あるレプリカが書き込んだデータは インビジブル その他すべて
  • 再起動またはスケール・トゥ・ゼロの際、セッションIDは失われ、データは永久に孤児となる。
  • これでは、共有データのために州のプロバイダーを利用する目的が完全に失われてしまう

アンチパターン - これは絶対にやってはいけない:

import { vcr, State } from '@vonage/vcr-sdk';

// WRONG: random session at global scope
const session = vcr.createSession();
const state = new State(session);

app.post('/message', async (req, res) => {
  // This state is only accessible by this exact replica, using this exact session ID.
  // Other replicas have their own random session and cannot see this data.
  await state.set('messageCount', (await state.get('messageCount') || 0) + 1);
  res.sendStatus(200);
});

正しいパターン

すべてのレプリカで状態を共有するには、インスタンス状態を使用します。

import { vcr } from '@vonage/vcr-sdk';

// Shared across all replicas of this instance — no session needed
const state = vcr.getInstanceState();

app.post('/message', async (req, res) => {
  await state.increment('messageCount', 1);
  res.sendStatus(200);
});

会話ごと/ユーザーごとの状態 - 決定論的なセッションIDを使う

コールバックからのIDを使用して、リクエストコンテキストにセッションをスコープする。これは、同じ会話/ユーザーに対する後続のリクエストをハンドリングするどのレプリカも、同じステートにアクセスできることを保証します。

import { vcr, State } from '@vonage/vcr-sdk';

// Voice: scope to the conversation UUID from the callback
app.post('/onCall', async (req, res) => {
  const session = vcr.createSessionWithId(req.body.conversation_uuid);
  const state = new State(session);
  await state.set('step', 'greeting');
  await state.set('startTime', Date.now());
  res.json([{ action: 'talk', text: 'Welcome!' }]);
});

// SMS: scope to the sender's phone number
app.post('/onMessage', async (req, res) => {
  const session = vcr.createSessionWithId(req.body.from);
  const state = new State(session);
  await state.increment('messageCount', 1);
  await state.set('lastMessage', req.body.text);
  res.sendStatus(200);
});

重要な洞察である: セッションIDは決定論的で、リクエストコンテキストから導出可能でなければならない。.良いセッションIDは以下の通り:

ソース IDの例 使用例
音声コールバック conversation_uuid コールごとのIVR状態
SMS/MMSコールバック 送信者電話番号 送信者ごとの会話履歴
認証されたユーザー ユーザーIDまたはJWTサブジェクト ユーザーごとの設定
カスタム相関 オーダーID、チケットID ワークフローごとの状態

購読登録には、グローバルセッションを使用します。

vcr.getGlobalSession() は全レプリカで共有される決定論的セッションを返します。使用方法 のみ プロバイダのサブスクリプション(音声、メッセージ)を登録するためであり、アプリケーションの状態を保存するためではない。

import { vcr, Voice, Messages } from '@vonage/vcr-sdk';

// Correct: global session for subscription registration
const globalSession = vcr.getGlobalSession();
const voice = new Voice(globalSession);
const messages = new Messages(globalSession);

await voice.onCall('onCall');
await messages.onMessage('onMessage',
  { type: 'sms', number: process.env.VONAGE_NUMBER },
  { type: 'sms', number: undefined }
);

概要

必要性 方法 スコープ
共有カウンター、キャッシュ、コンフィグ vcr.getInstanceState() すべてのレプリカ
ユーザーごと/会話ごとの状態 vcr.createSessionWithId(id) + new State(session) 決定論的、リクエスト・スコープ
アプリ間の共有データ vcr.getAccountState() アカウント内のすべてのアプリ
サブスクリプション登録 vcr.getGlobalSession() インスタンス全体のシングルトン
国家のグローバルな範囲では決してない vcr.createSession() ランダム、他のレプリカから到達不可能