Skip to content

Service Commands

Service commands let you control running Strata services at runtime without restarting them. You can adjust concurrency, trigger graceful shutdowns, query service info, and tune event loop settings -- all by sending a message over the same backend your services already use.

Commands use a pub/sub model, not request/response. They are best-effort delivery: the backend does not retry if a service is unavailable, and delivery is not guaranteed. For the Redis backends, commands are sent via Redis pub/sub.

How Commands Work

Commands are lightweight JSON messages published to a target channel. Services subscribe to these channels on startup and handle incoming commands automatically.

Command Targeting

Commands can be sent at three levels of specificity:

Target PatternScope
ServicesEvery service connected to this backend
Services:<ServiceGroup>Every instance in a specific service group
Services:<ServiceGroup>:<ServiceID>A single service instance

This gives you fine-grained control. Broadcast a shutdown to an entire service group, or adjust concurrency on one specific instance.

Command Envelope

Every command is a JSON object with this structure:

typescript
interface ServiceCommand
{
    id : string;                          // Unique command ID
    command : ValidCommandName;           // 'info' | 'concurrency' | 'shutdown' | 'toobusy'
    payload ?: Record<string, unknown>;   // Command-specific data
}

The info command adds a responseChannel field so the service knows where to send the reply:

typescript
interface StrataInfoCommand extends ServiceCommand
{
    command : 'info';
    responseChannel : string;   // Channel for the response
}

Built-in Commands

Strata defines four built-in commands. These are the only valid command names (ValidCommandName).

info

Requests service information from any service that receives the command. This is the only command that returns a response -- the service sends back a ServiceInfo payload containing its ID, version, concurrency, registered contexts, and more.

typescript
const response = await client.command('info', 'Services:UserService');

console.log(response?.payload);
// {
//     id: 'l7aNdfrRDZW8nt0FBaSS',
//     serviceName: 'User Service',
//     serviceGroup: 'UserService',
//     version: '1.2.0',
//     strataVersion: '2.0.0',
//     concurrency: 32,
//     outstanding: 3,
//     hostname: 'worker-01',
//     contexts: { service: ['info'], users: ['get', 'create'] },
//     ...
// }

TIP

The info command targets a specific instance and gets a direct response. The service/info operation (available via client.request()) goes through the normal request queue and is handled by whichever instance picks it up first. Use the command when you need info from a specific instance; use the operation for general health checks.

concurrency

Changes the maximum number of concurrent requests a service will process. Takes effect immediately.

typescript
// Set concurrency to 50 for all instances in UserService
await client.command('concurrency', 'Services:UserService', { concurrency: 50 });

// Set concurrency on a specific instance
await client.command('concurrency', 'Services:UserService:abc123', { concurrency: 10 });

The payload requires a concurrency field with a number greater than or equal to 0.

shutdown

Initiates a shutdown of the target service(s).

typescript
// Graceful shutdown (default) -- finishes in-flight requests, then exits with code 0
await client.command('shutdown', 'Services:UserService');

// Graceful shutdown with a custom exit code
await client.command('shutdown', 'Services:UserService', {
    graceful: true,
    exitCode: 0,
});

// Immediate shutdown -- process exits immediately
await client.command('shutdown', 'Services:UserService', {
    graceful: false,
    exitCode: 1,
});
Payload FieldTypeDefaultDescription
gracefulbooleantrueIf false, calls process.exit() immediately
exitCodenumber0Exit code for the process

If the payload is omitted entirely, the service performs a graceful shutdown with exit code 0.

toobusy

Adjusts the node-toobusy parameters that control dynamic concurrency. This lets you tune how aggressively Strata backs off when the event loop is under load.

typescript
await client.command('toobusy', 'Services:UserService', {
    maxLag: 70,
    interval: 500,
    smoothingFactorOnRise: 0.33,
    smoothingFactorOnFall: 0.75,
});
Payload FieldTypeDescription
maxLagnumberMaximum event loop lag (ms) before the service is considered too busy
intervalnumberHow often (ms) to check event loop lag
smoothingFactorOnRisenumberSmoothing factor when lag is increasing (0-1)
smoothingFactorOnFallnumberSmoothing factor when lag is decreasing (0-1)

All fields are optional. Only the fields you include will be updated.

Sending Commands

Via StrataClient

The client.command() method is the primary way to send commands:

typescript
import { StrataClient } from '@strata-js/strata';

const client = new StrataClient({
    client: { name: 'AdminClient' },
    backend: { type: 'redis-streams', redis: { host: 'localhost', port: 6379 } },
});

await client.start();

// client.command(command, target?, payload?)
await client.command('concurrency', 'Services:UserService', { concurrency: 25 });

The method signature:

typescript
client.command(
    command : ValidCommandName,   // 'info' | 'concurrency' | 'shutdown' | 'toobusy'
    target ?: string,             // Default: 'Services' (all services)
    payload ?: Record<string, unknown>
) : Promise<ServiceCommandResponse | undefined>
  • Returns a response only for commands that produce one (currently just info).
  • target defaults to 'Services' -- which broadcasts to every service on the backend.

Directly via Redis

Since commands are just pub/sub messages, you can send them from any Redis client without Strata:

typescript
import Redis from 'ioredis';

const redis = new Redis();

const command = {
    id: 'manual-cmd-001',
    command: 'concurrency',
    payload: { concurrency: 5 },
};

await redis.publish('Services:UserService', JSON.stringify(command));

This is useful for ops tooling, scripts, or any situation where you don't want to instantiate a full StrataClient.

Command Responses

Only the info command returns a response. The response envelope is:

typescript
interface ServiceCommandResponse
{
    id : string;                          // Matches the command's ID
    payload ?: Record<string, unknown>;   // Response data
}

When you call client.command('info', ...), Strata automatically sets up a responseChannel and waits for the reply. The response is returned directly from the command() call:

typescript
const response = await client.command('info', 'Services:UserService:abc123');
if(response)
{
    console.log(`Service ${ response.payload.serviceGroup } is running v${ response.payload.version }`);
}

For commands without responses (concurrency, shutdown, toobusy), the method returns undefined.

Unknown Commands

If a service receives a command it doesn't recognize, it logs a warning and ignores it. No error response is sent. There is currently no mechanism for registering custom command handlers on the service side -- the four built-in commands are the only ones supported.

Next Steps