Created
March 14, 2026 23:26
-
-
Save mohashari/4c6ec8a44c4c886177e380eba4881a01 to your computer and use it in GitHub Desktop.
WebSockets at Scale: Real-Time Backend Architecture
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| func (c *Client) writePump(conn *websocket.Conn) { | |
| ticker := time.NewTicker(30 * time.Second) | |
| defer func() { | |
| ticker.Stop() | |
| conn.Close() | |
| }() | |
| conn.SetPongHandler(func(string) error { | |
| conn.SetReadDeadline(time.Now().Add(65 * time.Second)) | |
| return nil | |
| }) | |
| for { | |
| select { | |
| case message, ok := <-c.Send: | |
| conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) | |
| if !ok { | |
| conn.WriteMessage(websocket.CloseMessage, []byte{}) | |
| return | |
| } | |
| if err := conn.WriteMessage(websocket.TextMessage, message); err != nil { | |
| return | |
| } | |
| case <-ticker.C: | |
| conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) | |
| if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil { | |
| return | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Per-process file descriptor limits — add to /etc/security/limits.conf | |
| * soft nofile 1000000 | |
| * hard nofile 1000000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package hub | |
| import "sync" | |
| type Client struct { | |
| ID string | |
| Send chan []byte | |
| done chan struct{} | |
| } | |
| type Hub struct { | |
| clients map[string]*Client | |
| broadcast chan []byte | |
| register chan *Client | |
| unregister chan *Client | |
| mu sync.RWMutex | |
| } | |
| func NewHub() *Hub { | |
| return &Hub{ | |
| clients: make(map[string]*Client), | |
| broadcast: make(chan []byte, 256), | |
| register: make(chan *Client), | |
| unregister: make(chan *Client), | |
| } | |
| } | |
| func (h *Hub) Run() { | |
| for { | |
| select { | |
| case client := <-h.register: | |
| h.mu.Lock() | |
| h.clients[client.ID] = client | |
| h.mu.Unlock() | |
| case client := <-h.unregister: | |
| h.mu.Lock() | |
| if _, ok := h.clients[client.ID]; ok { | |
| delete(h.clients, client.ID) | |
| close(client.Send) | |
| } | |
| h.mu.Unlock() | |
| case message := <-h.broadcast: | |
| h.mu.RLock() | |
| for _, client := range h.clients { | |
| select { | |
| case client.Send <- message: | |
| default: | |
| // Buffer full — client is too slow, disconnect it | |
| close(client.Send) | |
| delete(h.clients, client.ID) | |
| } | |
| } | |
| h.mu.RUnlock() | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package hub | |
| import ( | |
| "hash/fnv" | |
| ) | |
| type ShardedHub struct { | |
| shards []*Hub | |
| count uint32 | |
| } | |
| func NewShardedHub(shardCount int) *ShardedHub { | |
| shards := make([]*Hub, shardCount) | |
| for i := range shards { | |
| shards[i] = NewHub() | |
| go shards[i].Run() | |
| } | |
| return &ShardedHub{shards: shards, count: uint32(shardCount)} | |
| } | |
| func (s *ShardedHub) shard(clientID string) *Hub { | |
| h := fnv.New32a() | |
| h.Write([]byte(clientID)) | |
| return s.shards[h.Sum32()%s.count] | |
| } | |
| func (s *ShardedHub) Register(c *Client) { s.shard(c.ID).register <- c } | |
| func (s *ShardedHub) Unregister(c *Client) { s.shard(c.ID).unregister <- c } | |
| func (s *ShardedHub) Send(clientID string, msg []byte) { | |
| s.shard(clientID).broadcast <- msg | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package pubsub | |
| import ( | |
| "context" | |
| "github.com/redis/go-redis/v9" | |
| ) | |
| type RedisBroker struct { | |
| client *redis.Client | |
| hub *ShardedHub | |
| } | |
| func (b *RedisBroker) Subscribe(ctx context.Context, channel string) { | |
| sub := b.client.Subscribe(ctx, channel) | |
| ch := sub.Channel() | |
| go func() { | |
| defer sub.Close() | |
| for { | |
| select { | |
| case msg := <-ch: | |
| // Fan out to all local connections in this topic | |
| b.hub.BroadcastToTopic(channel, []byte(msg.Payload)) | |
| case <-ctx.Done(): | |
| return | |
| } | |
| } | |
| }() | |
| } | |
| func (b *RedisBroker) Publish(ctx context.Context, channel string, payload []byte) error { | |
| return b.client.Publish(ctx, channel, payload).Err() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- Track which server node holds each connection | |
| CREATE TABLE connection_registry ( | |
| connection_id UUID PRIMARY KEY, | |
| server_node_id TEXT NOT NULL, | |
| user_id BIGINT, | |
| connected_at TIMESTAMPTZ DEFAULT NOW(), | |
| last_seen_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| CREATE INDEX idx_connection_registry_user ON connection_registry(user_id); | |
| CREATE INDEX idx_connection_registry_node ON connection_registry(server_node_id); | |
| -- Periodically clean stale records from crashed nodes | |
| DELETE FROM connection_registry | |
| WHERE last_seen_at < NOW() - INTERVAL '2 minutes'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| upstream websocket_backend { | |
| ip_hash; # sticky sessions via client IP | |
| server ws1.internal:8080; | |
| server ws2.internal:8080; | |
| server ws3.internal:8080; | |
| keepalive 1000; | |
| } | |
| server { | |
| listen 443 ssl; | |
| location /ws { | |
| proxy_pass http://websocket_backend; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade $http_upgrade; | |
| proxy_set_header Connection "upgrade"; | |
| proxy_set_header Host $host; | |
| proxy_set_header X-Real-IP $remote_addr; | |
| proxy_read_timeout 3600s; # 1 hour — match your heartbeat interval | |
| proxy_send_timeout 3600s; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM golang:1.22-alpine AS builder | |
| WORKDIR /app | |
| COPY go.mod go.sum ./ | |
| RUN go mod download | |
| COPY . . | |
| RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o wsserver ./cmd/server | |
| FROM gcr.io/distroless/static-debian12 | |
| COPY --from=builder /app/wsserver /wsserver | |
| EXPOSE 8080 | |
| ENTRYPOINT ["/wsserver"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # kubernetes deployment excerpt | |
| spec: | |
| terminationGracePeriodSeconds: 60 | |
| containers: | |
| - name: wsserver | |
| image: your-registry/wsserver:latest | |
| ports: | |
| - containerPort: 8080 | |
| lifecycle: | |
| preStop: | |
| exec: | |
| command: ["/bin/sh", "-c", "sleep 5"] # drain LB before SIGTERM | |
| resources: | |
| limits: | |
| memory: "2Gi" | |
| requests: | |
| memory: "1Gi" | |
| cpu: "500m" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /etc/sysctl.conf — apply with: sysctl -p | |
| fs.file-max = 10000000 | |
| net.core.somaxconn = 65535 | |
| net.ipv4.tcp_max_syn_backlog = 65535 | |
| net.ipv4.ip_local_port_range = 1024 65535 | |
| net.ipv4.tcp_tw_reuse = 1 | |
| net.ipv4.tcp_fin_timeout = 15 | |
| net.core.rmem_max = 16777216 | |
| net.core.wmem_max = 16777216 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment