Definition
- Mismatch – RSocket is connection-oriented and stateful (streams, resume tokens, backpressure), which conflicts with serverless functions that are stateless, ephemeral, and scale to zero.
- Cold starts – new function instances add latency that is poor for a protocol assuming a warm, persistent connection.
- Function timeouts – invocations are bounded to seconds or minutes, ruling out indefinite streams and channels.
- Always-on edge – keep the RSocket server on a long-running container that owns persistent connections and delegates to serverless.
- API Gateway WebSocket – the gateway holds the WebSocket while invoking a function per incoming message, decoupling socket lifetime from function lifetime.
- Serverless client – a function acts as a short-lived RSocket client making a request-response call to an always-on server within its timeout.
A serverless function is like a temporary clerk hired for a few seconds to answer one question and then goes home. RSocket expects a dedicated clerk who stays on a long live phone call with Rajesh for minutes. You cannot keep a live streaming call running through clerks who vanish after a few seconds. So National Bank of India keeps one always-on operator who holds Rajesh's live call (always-on RSocket server), and whenever a specific task needs doing, that operator quickly delegates to a temporary clerk (serverless function) for a short job and relays the answer back.
Understanding the Concepts
Why does RSocket clash with serverless?
The mismatch has three roots. Serverless functions are stateless and ephemeral; they cannot hold a connection open for a streaming session that lasts minutes. Cold starts add latency when a new function instance spins up. Function timeouts bound how long any single invocation can run, ruling out indefinite streams.
RSocket needs: Serverless provides:
Long-lived connections Ephemeral functions (seconds)
Stateful streams Stateless invocations
Resume tokens Scale-to-zero (no state)
Backpressure over time Cold starts + timeouts
What is Pattern 1: always-on server with serverless compute?
An always-on RSocket server (a container on ECS, Kubernetes, or a VM) owns persistent connections and delegates stateless compute to serverless functions.
package com.bank.rsocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.core.SdkBytes;
public class AlwaysOnServer implements RSocket {
private final LambdaClient lambda = LambdaClient.create();
@Override
public Mono<Payload> requestResponse(Payload payload) {
String accountId = payload.getDataUtf8();
// Delegate the stateless compute to a Lambda function
return Mono.fromCallable(() -> {
InvokeRequest request = InvokeRequest.builder()
.functionName("calculate-balance")
.payload(SdkBytes.fromUtf8String(
"{\"accountId\":\"" + accountId + "\"}"))
.build();
String result = lambda.invoke(request)
.payload().asUtf8String();
return DefaultPayload.create(result);
});
}
}
Architecture:
Rajesh's app ──RSocket──▶ Always-on server ──Lambda──▶ Calculate
(persistent) (delegates) (stateless)
What is Pattern 2: API Gateway WebSocket proxy?
API Gateway maintains the WebSocket connection on behalf of the backend and invokes a function per incoming message. The socket lifetime is decoupled from the function lifetime.
package com.bank.serverless;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.math.BigDecimal;
import java.util.Map;
public class WebSocketMessageHandler
implements RequestHandler<Map<String, Object>, Map<String, Object>> {
@Override
public Map<String, Object> handleRequest(
Map<String, Object> event, Context context) {
// API Gateway holds the WebSocket
// This function handles one message
String body = (String) event.get("body");
String connectionId = (String) ((Map<?, ?>) event
.get("requestContext")).get("connectionId");
BigDecimal balance = lookupBalance(body);
// Post response back through API Gateway
return Map.of(
"statusCode", 200,
"body", "{\"balance\":" + balance + "}"
);
}
private BigDecimal lookupBalance(String accountId) {
return new BigDecimal("150000.00");
}
}
What is Pattern 3: serverless function as RSocket client?
A serverless function acts as a short-lived RSocket client making a request-response call to an always-on RSocket server within its timeout.
package com.bank.serverless;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import io.rsocket.core.RSocketConnector;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.util.DefaultPayload;
import java.math.BigDecimal;
public class RSocketClientFunction
implements RequestHandler<String, String> {
@Override
public String handleRequest(String accountId, Context context) {
// Short-lived RSocket client: connect, call, disconnect
String result = RSocketConnector.create()
.connect(TcpClientTransport.create(
"rsocket-server.bank.internal", 7000))
.flatMap(rSocket ->
rSocket.requestResponse(
DefaultPayload.create(accountId))
.map(p -> p.getDataUtf8())
.doFinally(s -> rSocket.dispose()))
.block();
return "{\"balance\":" + result + "}";
}
}
Which interaction models fit serverless?
Request-response and fire-and-forget can fit short-lived functions. Request-stream and request-channel need a durable connection that serverless does not provide.
| Model | Serverless Compatible | Reason |
|---|---|---|
| Request-response | Yes | Short, bounded, fits within timeout |
| Fire-and-forget | Yes | No response needed |
| Request-stream | No | Needs a long-lived connection |
| Request-channel | No | Needs a persistent bidirectional connection |
// Fits serverless: request-response within timeout
rSocket.requestResponse(DefaultPayload.create("SAV2001"))
.timeout(Duration.ofSeconds(25)) // Lambda timeout minus margin
.block();
// Does NOT fit serverless: indefinite stream
// rSocket.requestStream(DefaultPayload.create("RELIANCE"))
// .subscribe(); // function will be killed before stream ends
What about connection pooling in serverless?
Functions cannot maintain connection pools across invocations reliably. Repeatedly opening RSocket connections adds setup cost and can exhaust server connection limits.
// WRONG: opening a new RSocket connection per invocation
public String handleRequest(String input, Context context) {
// Every cold start opens a new TCP connection
// Warm invocations might reuse, but no guarantee
RSocket rSocket = RSocketConnector.create()
.connect(TcpClientTransport.create("server", 7000))
.block();
// ...
}
// BETTER: use a static field to reuse across warm invocations
private static RSocket rSocket;
static {
rSocket = RSocketConnector.create()
.connect(TcpClientTransport.create("server", 7000))
.block();
}
What are common mistakes with RSocket and serverless?
The most common mistake is trying to run a long-lived stream inside a serverless function.
// WRONG: streaming inside a Lambda function
public String handleRequest(String input, Context context) {
rSocket.requestStream(DefaultPayload.create("RELIANCE"))
.subscribe(p -> process(p));
// Function times out and is killed, stream is lost
return "started"; // misleading
}
What should a senior engineer know?
Put durable, stateful connectivity on the always-on tier and reserve serverless for stateless, bounded units of work. At National Bank of India Rajesh's live price stream lives on the always-on edge because no function can hold it, while a one-off balance calculation is a perfect stateless job that the edge delegates to a serverless function.
- Backpressure and resumption are meaningless if the endpoint vanishes mid-stream.
- Translate at the boundary: RSocket terminates on the persistent edge, and beyond that use queues and function triggers.
- Account for connection setup cost when using functions as RSocket clients.
Visual Overview
Pattern 1: Always-On Server + Serverless Compute
┌────────┐ RSocket ┌────────────────┐ invoke ┌──────────┐
│ Rajesh │───────────▶│ Always-on │─────────▶│ Lambda │
│ (app) │◀───────────│ RSocket Server │◀─────────│ Function │
└────────┘ stream │ (ECS/K8s) │ result │(stateless│
└────────────────┘ └──────────┘
Pattern 2: API Gateway WebSocket
┌────────┐ WebSocket ┌────────────────┐ invoke ┌──────────┐
│ Rajesh │───────────▶│ API Gateway │─────────▶│ Lambda │
│ (app) │◀───────────│ (holds socket) │◀─────────│ per msg │
└────────┘ └────────────────┘ └──────────┘
Pattern 3: Serverless as RSocket Client
┌──────────┐ trigger ┌────────────────┐ RSocket ┌────────┐
│ SQS/API │──────────▶│ Lambda (client)│──────────▶│Always- │
│ event │ │ req-response │◀──────────│on │
└──────────┘ └────────────────┘ response │server │
└────────┘
Key Takeaway
RSocket can work in a serverless environment, but only with the right architecture, because its core design clashes with serverless in three ways. RSocket is connection-oriented and stateful, relying on long-lived connections, streams, and backpressure, while serverless functions are stateless, ephemeral, scale to zero, suffer cold starts, and are bounded by short invocation timeouts. A streaming session that lasts minutes cannot live inside a function that is torn down in seconds.
The way to make it work is to keep the durable, stateful connectivity on an always-on tier and reserve serverless for stateless, bounded units of work. An always-on RSocket server owns the persistent connections and the streaming, applies backpressure and resumption, and delegates short computations to serverless functions. Alternatively, an API Gateway WebSocket proxy holds the socket and invokes a function per message, decoupling socket lifetime from function lifetime. A third option inverts the roles, where a function acts as a short-lived RSocket client doing a request-response call to an always-on server within its timeout.
At National Bank of India Rajesh's live price stream lives on the always-on edge because no function can hold it, while a one-off balance calculation is a perfect stateless job that the edge delegates to a serverless function that returns well within its timeout. Past the edge boundary, translate to queues and topics so the serverless tier stays stateless and event-driven.
Remember one sentence: terminate persistent RSocket connections on an always-on edge, and use serverless only for bounded, stateless compute behind that boundary, because request-stream and request-channel need a durable connection serverless cannot provide.
---