Stateless Architecture

VCR runs multiple replicas of every application, and can scale further under load. Replicas can also be scaled down to zero when idle. This has important implications for how you manage state.

Why In-Memory State Is Unsafe

Any data stored in memory (global variables, module-level objects, in-process caches) is:

  • Local to one replica only
  • Lost on restart or scale-to-zero
  • Invisible to other replicas handling concurrent requests

Never assume that two requests will hit the same replica. If they do today, that will not hold as load increases.

Wrong:

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

let requestCount = 0;
requestCount++;

Correct:

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

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

Choosing the Right State Scope

Scope Access method Visibility
Session new State(session) One session only
Instance vcr.getInstanceState() All replicas of this deployed instance
Account vcr.getAccountState() All applications in the Vonage account

vcr.getInstanceState() is the correct default for shared state. It is a convenience wrapper that scopes State to the current instance ID, so all replicas of the same deployment share the same store.

vcr.getAccountState() is for cross-application data, such as a shared blocklist or configuration that multiple VCR apps need to read.

Session state (new State(session)) is appropriate for data that belongs to a single user interaction, such as conversation context or call state.

Common In-Memory Patterns to Replace

Pattern Replacement
const cache = {} at module level vcr.getInstanceState()
Singleton object holding request state vcr.getInstanceState() or vcr.createSessionWithId(userId)
global.something = ... vcr.getInstanceState()
Counter incremented per request state.increment('counter', 1)

Code Examples

Node.js — shared counter across replicas:

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 — per-user session state:

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 — shared state across replicas:

Session Scoping Best Practices

Critical: Misusing vcr.createSession() is one of the most common sources of bugs in VCR applications. Understanding session scoping is essential for building apps that scale correctly.

The Problem with vcr.createSession() at Global Scope

vcr.createSession() generates a random, ephemeral session ID each time it is called. If you call it at module/global scope and use the resulting session with new State(session):

  • Each replica creates its own random session on startup
  • Data written by one replica is invisible to all others
  • On restart or scale-to-zero, the session ID is lost and the data becomes permanently orphaned
  • This completely defeats the purpose of using the State provider for shared data

Anti-pattern — DO NOT do this:

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

Correct Patterns

For shared state across all replicas — use Instance State

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

For per-conversation/per-user state — use deterministic session IDs

Scope sessions to request context using IDs from callbacks. This ensures any replica handling a subsequent request for the same conversation/user can access the same state.

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

The key insight: the session ID must be deterministic and derivable from the request context. Good session IDs include:

Source Example ID Use case
Voice callback conversation_uuid Per-call IVR state
SMS/MMS callback sender phone number Per-sender conversation history
Authenticated user user ID or JWT subject Per-user preferences
Custom correlation order ID, ticket ID Per-workflow state

For subscription registration — use Global Session

vcr.getGlobalSession() returns a deterministic session shared across all replicas. Use it only for registering provider subscriptions (voice, messages), not for storing application state.

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

Summary

Need Method Scope
Shared counters, caches, config vcr.getInstanceState() All replicas
Per-user/per-conversation state vcr.createSessionWithId(id) + new State(session) Deterministic, request-scoped
Cross-app shared data vcr.getAccountState() All apps in account
Subscription registration vcr.getGlobalSession() Instance-wide singleton
Never at global scope for State vcr.createSession() Random, unreachable by other replicas