podshare is a small Go library. Every pod holds the same
map[string]T; writes broadcast over a pluggable transport.
Reads are local — about 17 nanoseconds. Pick the wire: Redis pub/sub,
P2P TCP, or roll your own.
You can't ship a *websocket.Conn between pods. You can ship
two bytes of "who owns it." That's the whole idea.
Every pod holds an identical map[string]Route. When
a user connects to pod-1, pod-1 publishes
"ws:alice" → "pod-1"; every other pod sees the claim
in under 100 milliseconds.
When any pod wants to push to alice, it looks up the route locally (~17ns), publishes a small call envelope to the owning pod's inbox, and awaits the reply on its own inbox.
The connection itself never moves. The route — a tiny string keyed by user ID — is the only thing that crosses the network.
Generic over T. JSON by default; swap with
WithCodec for msgpack, protobuf, anything.
type Config struct { RateLimit int `json:"rate_limit"` FeatureFlags map[string]bool `json:"feature_flags"` } tr := transport.NewMemoryTransport() defer tr.Close() store, _ := podshare.New[Config](ctx, "app-config", tr) defer store.Close() store.Set(ctx, "global", Config{RateLimit: 1000}) cfg, _ := store.Get("global") // ~17ns, local
// Two pods on the same Redis converge automatically. tr, _ := transport.NewRedisTransport(transport.RedisOptions{ Addr: "localhost:6379", }) defer tr.Close() store, _ := podshare.New[Flag](ctx, "feature-flags", tr, podshare.WithNodeID[Flag]("pod-1"), podshare.WithErrorHandler[Flag](func(e error) { slog.Error("podshare", "err", e) }), ) defer store.Close() // After a Redis failover, recover missed events: store.Refresh(ctx)
// Live propagation — non-blocking dispatch. // WatchPrefix filters server-side; misses don't consume buffer. for ev := range store.Watch(ctx, podshare.WatchPrefix("ws:")) { switch ev.Kind { case podshare.EventSet: cache.Store(ev.Key, &ev.Value) case podshare.EventDelete: cache.Delete(ev.Key) } } // Channel closes on ctx cancel OR slow-consumer drop. // Closure = "re-Watch + Refresh".
// Call a method on whichever pod owns "ws:alice" — // even if her WebSocket lives somewhere else. ep, _ := callreply.New(tr, callreply.Options{SelfID: "pod-B"}) defer ep.Close() // On the pod that owns alice's WS: ep.Register("ws:alice", "Send", func(ctx context.Context, args []byte) ([]byte, error) { return nil, conn.WriteMessage(websocket.TextMessage, args) }) // From any other pod: ep.Call(ctx, "ws:alice", "Send", []byte("hello"))
No leaky abstractions, no hidden allocations on hot paths, every option in the godoc.
Store[T] uses Go generics — no any casts,
no codegen, no reflection on the hot path.
Memory for tests, Redis pub/sub for prod, P2P TCP for no-deps.
Implement Transport for anything else.
AddPeer / RemovePeer at runtime, plus a
bundled DNSDiscoverer for K8s headless Services.
Survives autoscaling without code changes.
Conflicts ordered by (time, origin, seq) — same-instant
same-node writes are deterministically resolved.
WatchKey / WatchPrefix apply server-side.
Misses skip dispatch entirely.
New pods hydrate from a peer snapshot. Reconnects recover with
Refresh(ctx).
WithMerger for a true commutative join — G-Sets,
counters, OR-Sets converge across pods.
Deletes retained long enough that late writes can't resurrect them. Periodic compaction tick.
Forward calls to whichever pod owns a target. Self-call short-circuits to in-process.
Stats() counters, WithLogger for slog,
OnError for everything else.
podshare is intentionally narrow. Here's where it fits — and where it doesn't.
| library | model | consistency | embedded | sweet spot |
|---|---|---|---|---|
| podshare | full replication, LWW | eventual | yes (lib) | small shared map, in-cluster |
| olric | sharded DMap + replicas | tunable | yes | large embedded KV |
| NATS JetStream KV | sharded, history | per-key strong | no (cluster) | durable shared state |
| etcd v3 | Raft KV | linearizable | no (cluster) | locks, leader election |
| memberlist | gossip primitive | eventual | yes (lib) | cluster membership |
| groupcache | peer cache, consistent hash | n/a (no replication) | yes (lib) | content distribution |
Six real patterns the library handles in roughly the same shape.
Operator pod sets a flag; every worker sees it via
Watch within ms. Lock-free hot path via
atomic.Pointer.
Recent conversations replicated across all chat-server pods. Any pod answers a follow-up turn — no DB hit.
Push a message to a user from any pod; the routing table forwards to whichever pod holds the WS connection.
Replicate a tiny "who's online" registry across the fleet. Local
Get is ~17 nanoseconds.
One pod trips a circuit-breaker flag; every other pod backs off within milliseconds. Same store as your config.
"user X lives on pod-3" — broadcast a small map of routing decisions for sticky-session-style affinity.
$ go get github.com/thanhphuchuynh/podshare@latest $ go run github.com/thanhphuchuynh/podshare/examples/basic@latest