# Connection Management Maintain reliable WebSocket connections for production subscriptions. Handle reconnections, monitor health, and manage resources efficiently. ## Setup ### Node.js ```javascript import { createClient } from 'graphql-ws'; import WebSocket from 'ws'; const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', webSocketImpl: WebSocket, keepAlive: 10000, retryAttempts: Infinity, shouldRetry: () => true }); ``` ### React/Apollo ```javascript import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client'; import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; import { getMainDefinition } from '@apollo/client/utilities'; import { createClient } from 'graphql-ws'; const httpLink = new HttpLink({ uri: 'https://graph.kadindexer.io/graphql' }); const wsLink = new GraphQLWsLink( createClient({ url: 'wss://graph.kadindexer.io/graphql', retryAttempts: Infinity, keepAlive: 10000 }) ); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink ); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache() }); ``` ## Reconnection Strategy ### Exponential Backoff Prevent server overload during connection issues: ```javascript const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', retryAttempts: Infinity, retryWait: async (retries) => { const baseDelay = 1000; const maxDelay = 30000; const jitter = Math.random() * 1000; const delay = Math.min( baseDelay * Math.pow(2, retries) + jitter, maxDelay ); await new Promise(resolve => setTimeout(resolve, delay)); } }); ``` **Backoff progression:** - Attempt 1: ~1s - Attempt 2: ~2s - Attempt 3: ~4s - Attempt 4: ~8s - Attempt 5+: ~30s (capped) ### Connection State Tracking Monitor connection status for UI feedback: ```javascript let connectionState = 'disconnected'; const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', on: { connecting: () => { connectionState = 'connecting'; updateUI('Connecting...'); }, connected: () => { connectionState = 'connected'; updateUI('Connected'); }, closed: (event) => { connectionState = 'disconnected'; console.error('Disconnected:', event.reason); updateUI('Disconnected'); } } }); ``` ### Automatic Resubscription Restore active subscriptions after reconnection: ```javascript const activeSubscriptions = new Map(); function subscribe(query, variables, handlers) { const id = `${query}_${JSON.stringify(variables)}`; // Remove old subscription if exists if (activeSubscriptions.has(id)) { activeSubscriptions.get(id).unsubscribe(); } const unsubscribe = client.subscribe( { query, variables }, { next: handlers.next, error: (error) => { console.error('Subscription error:', error); // Retry after delay setTimeout(() => { subscribe(query, variables, handlers); }, 5000); }, complete: () => { activeSubscriptions.delete(id); if (handlers.complete) handlers.complete(); } } ); activeSubscriptions.set(id, { unsubscribe, query, variables }); return () => { unsubscribe(); activeSubscriptions.delete(id); }; } ``` ## Health Monitoring ### Keepalive Configuration Maintain connection with periodic pings: ```javascript const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', keepAlive: 10000, // Ping every 10 seconds on: { ping: (received) => { if (!received) console.log('Sent ping'); }, pong: (received) => { if (received) console.log('Received pong'); } } }); ``` **Recommended keepAlive values:** - 10000ms (10s) - Standard - 30000ms (30s) - Low-frequency updates - 5000ms (5s) - High-reliability requirements ### Latency Tracking Monitor subscription performance: ```javascript const metrics = { messageCount: 0, totalLatency: 0, lastMessageTime: null }; client.subscribe({ query }, { next: (data) => { const now = Date.now(); if (metrics.lastMessageTime) { const latency = now - metrics.lastMessageTime; metrics.totalLatency += latency; } metrics.messageCount++; metrics.lastMessageTime = now; const avgLatency = metrics.totalLatency / metrics.messageCount; if (avgLatency > 5000) { console.warn(`High latency: ${avgLatency}ms`); } processData(data); } }); ``` ### Error Classification Handle errors appropriately by type: ```javascript function handleSubscriptionError(error) { if (error.message.includes('rate limit')) { console.error('Rate limit exceeded'); // Reduce subscription count or upgrade tier } else if (error.message.includes('timeout')) { console.error('Connection timeout'); // Trigger reconnection } else if (error.extensions?.code === 'UNAUTHENTICATED') { console.error('Authentication failed'); // Refresh credentials } else { console.error('Unknown error:', error); // Generic handling } } ``` ## Resource Management ### Subscription Lifecycle Prevent memory leaks with proper cleanup: ```javascript class SubscriptionManager { constructor() { this.subscriptions = new Set(); } subscribe(query, variables, handlers) { const unsubscribe = client.subscribe( { query, variables }, handlers ); this.subscriptions.add(unsubscribe); return () => { unsubscribe(); this.subscriptions.delete(unsubscribe); }; } unsubscribeAll() { this.subscriptions.forEach(unsub => unsub()); this.subscriptions.clear(); } getCount() { return this.subscriptions.size; } } // Usage const manager = new SubscriptionManager(); const unsub1 = manager.subscribe(query1, vars1, handlers1); const unsub2 = manager.subscribe(query2, vars2, handlers2); // Cleanup manager.unsubscribeAll(); ``` ### Connection Pooling Share single WebSocket connection across subscriptions: ```javascript // ✅ Single shared client const sharedClient = createClient({ url: 'wss://graph.kadindexer.io/graphql', keepAlive: 10000 }); // Multiple subscriptions use one connection const unsub1 = sharedClient.subscribe(query1, handlers1); const unsub2 = sharedClient.subscribe(query2, handlers2); const unsub3 = sharedClient.subscribe(query3, handlers3); // ❌ Don't create multiple clients const client1 = createClient({ url: '...' }); const client2 = createClient({ url: '...' }); const client3 = createClient({ url: '...' }); ``` ### Graceful Shutdown Clean up before application exit: ```javascript // Node.js process.on('SIGTERM', async () => { console.log('Shutting down...'); // Close all subscriptions manager.unsubscribeAll(); // Dispose client await client.dispose(); process.exit(0); }); // Browser window.addEventListener('beforeunload', () => { manager.unsubscribeAll(); client.dispose(); }); ``` ## Rate Limit Management ### Monitor Active Subscriptions Track subscription count against tier limits: ```javascript const tierLimits = { free: 5, developer: 25, team: Infinity }; const currentTier = 'developer'; let activeCount = 0; function canSubscribe() { return activeCount < tierLimits[currentTier]; } function subscribe(query, variables, handlers) { if (!canSubscribe()) { throw new Error(`Subscription limit reached: ${activeCount}/${tierLimits[currentTier]}`); } activeCount++; const unsubscribe = client.subscribe({ query, variables }, { ...handlers, complete: () => { activeCount--; if (handlers.complete) handlers.complete(); } }); return () => { unsubscribe(); activeCount--; }; } ``` ### Prioritize Critical Subscriptions Drop low-priority subscriptions when hitting limits: ```javascript const subscriptions = new Map(); function subscribe(query, variables, handlers, priority = 0) { const id = generateId(); // Check limit if (subscriptions.size >= tierLimit) { // Find lowest priority subscription const [lowestId, lowestSub] = [...subscriptions.entries()] .sort((a, b) => a[1].priority - b[1].priority)[0]; if (priority > lowestSub.priority) { console.log(`Dropping subscription ${lowestId} (priority ${lowestSub.priority})`); lowestSub.unsubscribe(); subscriptions.delete(lowestId); } else { throw new Error('Cannot add lower priority subscription'); } } const unsubscribe = client.subscribe({ query, variables }, handlers); subscriptions.set(id, { unsubscribe, priority }); return () => { unsubscribe(); subscriptions.delete(id); }; } // Usage subscribe(criticalQuery, vars, handlers, 10); // High priority subscribe(optionalQuery, vars, handlers, 1); // Low priority ``` ## Backpressure Handling Process high-frequency updates without blocking: ```javascript const messageQueue = []; let processing = false; const BATCH_SIZE = 10; client.subscribe({ query }, { next: (data) => { messageQueue.push(data); processQueue(); } }); async function processQueue() { if (processing || messageQueue.length === 0) return; processing = true; while (messageQueue.length > 0) { const batch = messageQueue.splice(0, BATCH_SIZE); await processBatch(batch); } processing = false; } async function processBatch(batch) { // Process multiple messages efficiently const results = await Promise.all( batch.map(data => processData(data)) ); updateUI(results); } ``` ## Production Patterns ### Connection Health Check Verify connection before critical operations: ```javascript async function ensureConnected() { if (connectionState !== 'connected') { throw new Error('Not connected to Kadindexer'); } } async function subscribeToCriticalEvent() { await ensureConnected(); return client.subscribe({ query: criticalQuery }, handlers); } ``` ### Metrics Export Track connection metrics for monitoring: ```javascript const metrics = { connections: 0, disconnections: 0, errors: 0, messagesReceived: 0, subscriptionCount: 0, avgLatency: 0 }; const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', on: { connected: () => { metrics.connections++; metrics.subscriptionCount = activeSubscriptions.size; }, closed: () => { metrics.disconnections++; metrics.subscriptionCount = 0; } } }); // Export for monitoring (Prometheus, DataDog, etc.) setInterval(() => { console.log('WebSocket metrics:', JSON.stringify(metrics)); }, 60000); ``` ### Circuit Breaker Stop attempting connections after repeated failures: ```javascript class CircuitBreaker { constructor(threshold = 5, timeout = 60000) { this.failureCount = 0; this.threshold = threshold; this.timeout = timeout; this.state = 'CLOSED'; this.nextAttempt = Date.now(); } canAttempt() { if (this.state === 'OPEN') { if (Date.now() >= this.nextAttempt) { this.state = 'HALF_OPEN'; return true; } return false; } return true; } recordSuccess() { this.failureCount = 0; this.state = 'CLOSED'; } recordFailure() { this.failureCount++; if (this.failureCount >= this.threshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; console.error('Circuit breaker opened - stopping connection attempts'); } } } const breaker = new CircuitBreaker(); const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', shouldRetry: () => breaker.canAttempt(), on: { connected: () => breaker.recordSuccess(), closed: () => breaker.recordFailure() } }); ``` ## Troubleshooting ### Connection Fails Immediately **Check:** - URL: `wss://graph.kadindexer.io/graphql` - WebSocket support in environment - Firewall/proxy allows WebSocket traffic - Client supports `graphql-ws` protocol **Debug:** ```javascript const client = createClient({ url: 'wss://graph.kadindexer.io/graphql', on: { error: (error) => { console.error('Connection error:', error); } } }); ``` ### Frequent Disconnections **Solutions:** - Implement exponential backoff - Increase `keepAlive` (10-30 seconds) - Check network stability - Verify tier rate limits ### Missing Messages **Check:** - Subscription is active during event - Filters aren't too restrictive - Connection state is 'connected' - Not exceeding tier subscription limits ### High Memory Usage **Solutions:** - Close unused subscriptions - Limit data requested per subscription - Implement proper cleanup on unmount - Use `SubscriptionManager` for tracking ## Checklist Before production: - [ ] Exponential backoff configured - [ ] Keepalive enabled (10-30s) - [ ] Connection state tracked - [ ] Automatic resubscription implemented - [ ] Proper cleanup on exit - [ ] Subscription count monitored - [ ] Error handling by type - [ ] Metrics exported - [ ] Resource limits respected ## Next Steps - **Monitor events:** [Real-Time Subscriptions →](/guides/realtime/subscriptions) - **Query historical data:** [Query Optimization →](/guides/advanced/query-optimization) - **Deploy safely:** [Production Readiness →](/guides/advanced/production-readiness) **Need help?** [toni@hackachain.io](mailto:toni@hackachain.io)