Skip to main content

Saizul Amin

Building High-Throughput Real-Time WebSocket Gateways with Go and Gorilla in 2026

📅 13 Sep 2026 ⏱ 2 min read
Building High-Throughput Real-Time WebSocket Gateways with Go and Gorilla in 2026
In this article

    Modern web applications demand instant, bi-directional communication for collaborative document editing, real-time cryptocurrency trading tickers, multiplayer gaming, and live AI agent streaming. While traditional HTTP polling and Server-Sent Events (SSE) offer partial solutions, full-duplex WebSockets remain the gold standard for low-latency interactive experiences. However, scaling WebSocket infrastructure to handle hundreds of thousands of concurrent connections introduces severe concurrency, memory, and thread-management challenges.

    In 2026, Go (Golang) paired with optimized WebSocket libraries remains the premier choice for production WebSocket gateways. Thanks to Go’s lightweight goroutines (consuming only ~2KB of stack memory compared to 1MB per OS thread in Java or C++), a single modest Linux server can easily sustain over 250,000 concurrent active WebSocket connections with sub-millisecond broadcast latency. This comprehensive engineering tutorial covers connection lifecycle management, Redis Pub/Sub horizontal clustering, and memory tuning.


    Why Go Outperforms Node.js and Python for WebSockets

    Evaluating server runtimes under high concurrent socket loads:

    Runtime & Framework Max Concurrent Sockets (16GB RAM) P99 Broadcast Latency Idle Memory per 10k Sockets
    Node.js (Socket.io) ~35,000 connections (event-loop bound) 48ms 420 MB
    Python (FastAPI + WebSockets) ~20,000 connections 85ms 680 MB
    Go (Gorilla / nhooyr.io) 250,000+ connections 1.8ms 45 MB (89% memory savings)

    Step-by-Step Production Architecture: The Hub Pattern

    1. Connection Hub and Client Structures (`hub.go`)

    Implement the thread-safe Hub pattern to coordinate client registrations, unregistrations, and message broadcasts:

    // hub.go
    package main
    
    import (
    	"sync"
    )
    
    type Hub struct {
    	clients    map[*Client]bool
    	broadcast  chan []byte
    	register   chan *Client
    	unregister chan *Client
    	mu         sync.RWMutex
    }
    
    func newHub() *Hub {
    	return &Hub{
    		broadcast:  make(chan []byte, 256),
    		register:   make(chan *Client),
    		unregister: make(chan *Client),
    		clients:    make(map[*Client]bool),
    	}
    }
    
    func (h *Hub) run() {
    	for {
    		select {
    		case client := <-h.register:
    			h.mu.Lock()
    			h.clients[client] = true
    			h.mu.Unlock()
    
    		case client := <-h.unregister:
    			h.mu.Lock()
    			if _, ok := h.clients[client]; ok {
    				delete(h.clients, client)
    				close(client.send)
    			}
    			h.mu.Unlock()
    
    		case message := <-h.broadcast:
    			h.mu.RLock()
    			for client := range h.clients {
    				select {
    				case client.send <- message:
    				default:
    					close(client.send)
    					delete(h.clients, client)
    				}
    			}
    			h.mu.RUnlock()
    		}
    	}
    }

    2. Managing Client Read and Write Pumps (`client.go`)

    Decouple socket reading from writing to prevent slow clients from blocking the hub:

    // client.go
    package main
    
    import (
    	"log"
    	"time"
    	"github.com/gorilla/websocket"
    )
    
    const (
    	writeWait      = 10 * time.Second
    	pongWait       = 60 * time.Second
    	pingPeriod     = (pongWait * 9) / 10
    	maxMessageSize = 512
    )
    
    type Client struct {
    	hub  *Hub
    	conn *websocket.Conn
    	send chan []byte
    }
    
    func (c *Client) readPump() {
    	defer func() {
    		c.hub.unregister <- c
    		c.conn.Close()
    	}()
    
    	c.conn.SetReadLimit(maxMessageSize)
    	c.conn.SetReadDeadline(time.Now().Add(pongWait))
    	c.conn.SetPongHandler(func(string) error {
    		c.conn.SetReadDeadline(time.Now().Add(pongWait))
    		return nil
    	})
    
    	for {
    		_, message, err := c.conn.ReadMessage()
    		if err != nil {
    			break
    		}
    		c.hub.broadcast <- message
    	}
    }
    
    func (c *Client) writePump() {
    	ticker := time.NewTicker(pingPeriod)
    	defer func() {
    		ticker.Stop()
    		c.conn.Close()
    	}()
    
    	for {
    		select {
    		case message, ok := <-c.send:
    			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
    			if !ok {
    				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
    				return
    			}
    			c.conn.WriteMessage(websocket.TextMessage, message)
    
    		case <-ticker.C:
    			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
    			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
    				return
    			}
    		}
    	}
    }

    Scaling Across Multi-Server Clusters with Redis Pub/Sub

    A single server is bounded by file descriptors; scaling horizontally across multiple gateway nodes requires linking hubs via a distributed Redis Pub/Sub backplane:

    // When message arrives on local node, publish to Redis cluster
    rdb.Publish(ctx, "global_broadcast_channel", message)
    
    // Background goroutine subscribes to Redis and forwards to local Hub
    go func() {
        pubsub := rdb.Subscribe(ctx, "global_broadcast_channel")
        ch := pubsub.Channel()
        for msg := range ch {
            hub.broadcast <- []byte(msg.Payload)
        }
    }()

    Linux OS Kernel Tuning for 250,000+ Sockets

    By default, Linux limits open file descriptors per process to 1,024. Tune /etc/security/limits.conf and sysctl:

    # /etc/security/limits.conf
    * soft nofile 1048576
    * hard nofile 1048576
    
    # /etc/sysctl.conf
    fs.file-max = 2097152
    net.ipv4.tcp_max_syn_backlog = 8192
    net.core.somaxconn = 65535

    Conclusion

    Building real-time systems in Go delivers unmatched concurrency, reliability, and cost-efficiency. By structuring connections using the Hub pattern, implementing decoupled read/write pumps, and scaling across nodes via Redis Pub/Sub, engineering teams build real-time communication backbones capable of serving millions of concurrent global users.

    Share this article
    Md Saizul Amin
    Written by

    Digital Systems Strategist · AI Agent Developer · 9+ years in digital & IT

    Md Saizul Amin is a digital systems strategist and AI automation engineer from Dhaka, Bangladesh. With 9+ years across IT, web development and digital marketing, he writes practical guides on technology, business, education and online growth — and builds the same kinds of systems (AI agents, websites, SEO and ad campaigns) for clients in Bangladesh, the UK and the USA.

    9+Years in digital & IT
    50+Websites developed
    7Live AI agent systems
    $150K+Ad spend managed

    Specialist in

    AI agents & n8n automationLLM integration (Gemini, GPT-4o, Claude)Custom software & web appsWordPress developmentSEO · AEO · GEOMeta & Google AdsEmail deliverabilityCRM & funnelsIT infrastructure & cybersecurity

    Credentials & experience

    • 🎓
      B.Sc. in Computer Science & EngineeringBangladesh University of Business & Technology (BUBT), 2017–2021
    • 🤖
      AI Automation Engineer · Web Developer · SEO SpecialistUnique Mark Limited, Birmingham UK — 2024 to present
    • 🌱
      IT SpecialistGreen Fund Initiative Inc., USA — 2024 to present
    • 🏥
      Junior IT Consultant — Ministry of Health (DGHS)Built the first national Shareable Health Record website; supported 6,000+ facilities
    • 🧭
      Head of ITWEDO Bangladesh — led IT for 50+ staff, 2021–2023

    ✔ Researched and written by the author, informed by hands-on work on real projects. Articles are updated when facts, tools or guidelines change.

    Want results like this for your business?

    AI agents, custom software, websites and growth systems — let’s talk about yours.