Session Management
Session Management
Section titled “Session Management”Manage server-sent event (SSE) sessions and client subscriptions in Mantis.
Overview
Section titled “Overview”SSE Sessions
Section titled “SSE Sessions”Purpose
Section titled “Purpose”Server-Sent Events (SSE) provide real-time updates to Lens UI clients:
- Deployment progress updates
- Target status changes
- System notifications
- Audit events
Session Lifecycle
Section titled “Session Lifecycle”Configuration
Section titled “Configuration”Mandible SSE Settings
Section titled “Mandible SSE Settings”[sse]# Single-use SSE token lifetime (seconds)token_ttl_secs = 60
# SSE session lifetime in Redis (seconds)session_ttl_secs = 3600
# Audience claim embedded in SSE tokensaudience = "mantis-sse"
# Redis connection used for SSE session/event fan-outredis_url = "redis://127.0.0.1:6379"
# Redis key namespace (isolates multiple Mantis deployments on one Redis)redis_namespace = "default"Redis Session Storage
Section titled “Redis Session Storage”For multi-instance Mandible deployments, point redis_url at a shared Redis. The SSE
subsystem uses the same [sse] table — there is no separate [sse.redis] section.
[sse]redis_url = "redis://:password@redis:6379"redis_namespace = "default"session_ttl_secs = 3600Environment Variables
Section titled “Environment Variables”# Standalone override read directly by MandibleSSE_REDIS_URL="redis://:password@redis:6379"
# Or the prefixed config-loader formMANDIBLE__SSE__REDIS_URL="redis://:password@redis:6379"MANDIBLE__SSE__REDIS_NAMESPACE="default"MANDIBLE__SSE__TOKEN_TTL_SECS=60MANDIBLE__SSE__SESSION_TTL_SECS=3600SSE Endpoints
Section titled “SSE Endpoints”SSE streams are nested under /api/v1/sse:
GET /api/v1/sse/{resource_type}— stream events for a resource type (list view)GET /api/v1/sse/{resource_type}/{id}— stream events for a specific entity (detail view)
Valid resource_type values include deployments and targets.
Authentication
Section titled “Authentication”SSE connections are not authenticated with the regular Authorization: Bearer JWT
header (which would leak the token into server logs and proxy access logs). Instead, the
client first exchanges its session for a short-lived, single-use SSE token, then passes it
as the sse_token query parameter:
# 1. Obtain a single-use SSE token (uses the authenticated session/JWT)SSE_TOKEN=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/auth/sse-token" | jq -r '.sse_token')
# 2. Open the event stream with the token as a query parametercurl -N "https://api.mantis.local/api/v1/sse/deployments?sse_token=$SSE_TOKEN"Detail Streams
Section titled “Detail Streams”To stream events for a single entity, append its ID:
curl -N "https://api.mantis.local/api/v1/sse/deployments/abc123?sse_token=$SSE_TOKEN"Event Types
Section titled “Event Types”The SSE stream emits nameless events (no event: field) whose data: payload is a
JSON-serialized entity event:
data: {"type":"updated","resource_type":"deployments","resource_id":"abc123","data":{...},"changed_fields":["status"],"timestamp":"2024-01-01T12:00:00Z"}
data: {"type":"created","resource_type":"targets","resource_id":"42","data":{...},"timestamp":"2024-01-01T12:00:00Z"}type is created, updated, or deleted; resource_type is the entity kind
(deployments, targets, …). Switch on those fields to react to changes.
The only named events are connected (sent on stream open, payload {}) and
lagged (sent when the broadcast buffer overflows, payload {"missed_events": N}).
Session Storage
Section titled “Session Storage”SSE configuration
Section titled “SSE configuration”The SSE subsystem is configured by the single [sse] table (there is no
[sse.redis] section and no enabled flag). Its fields are token_ttl_secs,
session_ttl_secs, audience, redis_url, and redis_namespace:
[sse]token_ttl_secs = 60session_ttl_secs = 3600audience = "mantis-sse"redis_url = "redis://:password@redis:6379"redis_namespace = "mantis:sse"Redis Key Structure
Section titled “Redis Key Structure”| Key Pattern | Type | TTL | Description |
|---|---|---|---|
sse:sessions:{namespace}:{session_id} | String | session_ttl | JSON-serialized session data (one key per session) |
The {namespace} segment is the redis_namespace config value (default: default), so
the default prefix is sse:sessions:default:.
Session Data
Section titled “Session Data”Sessions are stored as a JSON string via SET … EX:
GET sse:sessions:default:sess_123# Returns a JSON object, e.g.:# {"user_id":"...","username":"alice","email":"alice@example.com","roles":["admin"],"tenant_id":"...","refresh_token":"...","expires_at":1704153600,"revoked":false}Load Balancing
Section titled “Load Balancing”Sticky Sessions
Section titled “Sticky Sessions”For multi-instance Mandible with SSE:
upstream mandible { ip_hash; # Sticky sessions by client IP server mandible-1:3000; server mandible-2:3000;}Without Sticky Sessions
Section titled “Without Sticky Sessions”If using Redis session storage, any instance can serve events:
# kubernetes ingressapiVersion: networking.k8s.io/v1kind: Ingressmetadata: annotations: # No sticky session needed with Redis nginx.ingress.kubernetes.io/proxy-read-timeout: '3600' nginx.ingress.kubernetes.io/proxy-send-timeout: '3600'HAProxy Configuration
Section titled “HAProxy Configuration”frontend http bind *:443 ssl crt /etc/ssl/mantis.pem acl is_sse path_beg /api/v1/sse use_backend mandible_sse if is_sse default_backend mandible
backend mandible_sse balance source # Sticky by source IP option http-server-close timeout server 3600s server mandible-1 10.0.0.1:3000 check server mandible-2 10.0.0.2:3000 check
backend mandible balance roundrobin server mandible-1 10.0.0.1:3000 check server mandible-2 10.0.0.2:3000 checkConnection Management
Section titled “Connection Management”Keep-Alive
Section titled “Keep-Alive”SSE connections use periodic keep-alive comments sent by Axum’s KeepAlive
mechanism. The keep-alive is a bare SSE comment line with no event name or data
payload — browsers receive it silently to prevent the connection from being
closed by proxies or firewalls:
:The interval is 3 seconds (SSE_KEEPALIVE_SECS). There is no event: heartbeat
field; the comment line is invisible to EventSource.onmessage handlers.
Reconnection
Section titled “Reconnection”Clients should handle reconnection:
const eventSource = new EventSource( `/api/v1/sse/deployments?sse_token=${sseToken}`);
eventSource.onerror = () => { // Reconnect with exponential backoff setTimeout( () => { eventSource.close(); // Create new connection }, Math.min(1000 * Math.pow(2, retryCount), 30000) );};Reconnection After Drops
Section titled “Reconnection After Drops”The server does not set id: fields on SSE events, so the browser’s
Last-Event-ID tracking has nothing to send and no resume point is
available server-side. On reconnection, the client must obtain a fresh SSE
token and re-open the stream from the current server state:
eventSource.onerror = () => { eventSource.close(); // Fetch a new SSE token, then re-open EventSource};Monitoring
Section titled “Monitoring”Session Metrics
Section titled “Session Metrics”# List all sessions (use SCAN in production to avoid blocking)redis-cli SCAN 0 MATCH "sse:sessions:default:*" COUNT 100
# Read a specific session (returns JSON string)redis-cli GET "sse:sessions:default:sess_123"Prometheus Metrics
Section titled “Prometheus Metrics”There are no dedicated SSE Prometheus metrics at present. Monitor SSE session counts via
Redis (see the redis-cli examples above) and watch Mandible’s structured logs for
connection lifecycle events.
Health Check
Section titled “Health Check”Mandible’s health endpoint reports only overall liveness/readiness (database connectivity); it does not expose an SSE sub-object:
curl -s https://localhost:3000/api/v1/health{ "status": "ok"}Scaling Considerations
Section titled “Scaling Considerations”Connection Limits
Section titled “Connection Limits”SSE session/token lifetimes are bounded by session_ttl_secs and
token_ttl_secs in the [sse] table. (There are no max_sessions_per_tenant /
max_total_sessions settings — those keys are not part of SseConfig.)
Memory Usage
Section titled “Memory Usage”SSE connections consume memory:
| Resource | Per Connection |
|---|---|
| Connection state | ~1 KB |
| Event buffer | ~10 KB |
| Total | ~11 KB |
For 1000 connections: ~11 MB
Event Broadcasting
Section titled “Event Broadcasting”For high-volume deployments, consider:
- Event aggregation: Batch small updates
- Subscription filtering: Only send relevant events
- Rate limiting: SSE event throttling is not configurable via
[sse](there are noevent_rate_limit/aggregation_window_mskeys). Thelaggedcontrol event signals when a slow consumer overflows its buffer.
Security
Section titled “Security”Authentication
Section titled “Authentication”SSE endpoints require a short-lived, single-use SSE token (not the regular JWT header).
Obtain it from POST /api/v1/auth/sse-token using the authenticated session, then pass it
as the sse_token query parameter:
SSE_TOKEN=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/auth/sse-token" | jq -r '.sse_token')curl -N "https://api.mantis.local/api/v1/sse/deployments?sse_token=$SSE_TOKEN"Tenant Isolation
Section titled “Tenant Isolation”Events are filtered by tenant:
- Users only receive events for their tenant
- Session storage is namespaced by tenant
- Cross-tenant access is prevented
Connection Validation
Section titled “Connection Validation”SSE tokens are short-lived (token_ttl_secs) and validated at connection time;
the stream is bounded by session_ttl_secs. (There are no
revalidate_interval_secs / disconnect_on_token_expiry settings — these are
not part of SseConfig.)
Troubleshooting
Section titled “Troubleshooting”Connection Drops
Section titled “Connection Drops”-
Confirm Mandible is reachable and healthy:
Terminal window curl -s https://localhost:3000/api/v1/health -
Verify proxy timeouts:
proxy_read_timeout 3600s;proxy_send_timeout 3600s; -
Check for buffering issues:
proxy_buffering off;
Events Not Received
Section titled “Events Not Received”-
Confirm the resource type is valid (e.g.
deployments,targets) — an unknownresource_typeis rejected before any events are streamed. -
Verify the stream opens with a fresh SSE token (tokens are single-use):
Terminal window curl -s -N "https://localhost:3000/api/v1/sse/deployments?sse_token=$SSE_TOKEN" -v
Redis Session Issues
Section titled “Redis Session Issues”-
Check Redis connectivity:
Terminal window redis-cli -a password ping -
List all sessions (use SCAN in production):
Terminal window redis-cli SCAN 0 MATCH "sse:sessions:default:*" COUNT 100 -
Check session TTL (replace
defaultwith yourredis_namespaceif changed):Terminal window redis-cli TTL "sse:sessions:default:sess_123"
High Memory Usage
Section titled “High Memory Usage”-
Check connection count via Redis (use SCAN in production):
Terminal window redis-cli SCAN 0 MATCH "sse:sessions:default:*" COUNT 100 -
Lower the session TTL so idle sessions expire sooner:
[sse]session_ttl_secs = 1800
Next Steps
Section titled “Next Steps”- Redis Setup - Redis configuration
- Cluster Coordination - Multi-instance setup
- Load Balancing - High availability
