8.3 KiB
Pekoe - Redis Pub/Sub Server
Project Overview
Pekoe is a Redis-compatible server implementing only the Pub/Sub functionality, written in Go using only the standard library (minimal external dependencies).
Purpose
A lightweight, focused Pub/Sub message broker that provides core Redis pub/sub capabilities without the complexity of full Redis functionality. Ideal for applications that need only pub/sub messaging.
Key Design Decisions
- Go stdlib only - Avoid external dependencies for simplicity and security
- Per-channel locking - Fine-grained concurrency control in the pubsub hub to maximize throughput
- RESP protocol - Full Redis-compatible wire protocol for client compatibility
- JSON config - Human-readable configuration with validation and sensible defaults
- Graceful shutdown - Signal handling for clean server termination (SIGINT, SIGTERM)
- Log re-opening - SIGUSR1 (Unix) or SIGHUP (Windows) for log rotation support
- Case-insensitive commands - Commands are case-insensitive per Redis specification
- CLIENT command support - Minimal CLIENT command for client library compatibility
Fundamental Rules
Do not apply changes without explicit user consent. When asked to analyze, review, or suggest any changes, present your proposal first and wait for confirmation before editing any files. Always ask "Should I implement this?" or "Should I apply these changes?" before modifying anything.
Keep codebase simple, small, maintainable and human-readable. Favour minimal, clear solutions over clever or abstract ones. Prefer clarity over cleverness. Prioritize explicitness. Minimize magic. Do not introduce patterns, layers, or indirection unless they directly solve a concrete problem. Every line of code must earn its place.
Create small focused modules and separate functionality over files. Do not make universal implementation; try to move functions to separate files and name files after function names.
Use a consistent structure. Subsequent searches will be faster and consume less context.
Avoid duplicate names. This is not strict rule. If codebase contain multiple files with the same name (e.g., index.ts), we have to read them all to discover the one exactly we need. This consumes time and context.
When stuck, ask clarification questions, or present a short plan, or both. Do not try to guess intent or make assumptions - ask.
Always make TODO plan before any job and follow it closely.
Report any found defects, issues, bugs. And keep track of them.
Comment the code. Add code comments to explain corner cases, complex logic, business rules, and design decisions. Focus on what problem the code is solving rather than how it is solving it. Write comments in easy to read and understand language, for humans to comprehend. Every public API must have a docstring.
Use domain names. Use naming conventions that reflect their purpose and domain concepts, not their technical implementation (e.g., OrderProcessor instead of OrderServiceFactory).
Test corner cases. Write comprehensive test suites that cover various scenarios and edge cases.
Use tags and labels. In comments, add tags or labels to files, classes, or functions to categorize them based on functionality, domain concepts, or design patterns.
Avoid obvious comments that do not add value.
Remove dead code. A half-finished refactoring can actually harm the contributions.
Update AGENTS.md and TODO.md automatically. Keep AGENTS.md dense, detailed and concise.
Never commit AGENTS.md, llms.txt, or TODO.md.
Architecture
pekoe/
├── cmd/pekoe/ # Entry point (main.go)
├── internal/
│ ├── config/ # Configuration loading and validation
│ │ └── configpath_*.go # Platform-specific default config paths
│ ├── logger/ # Timestamp-based logging with level filtering
│ ├── protocol/ # RESP protocol parsing and serialization
│ ├── pubsub/ # Pub/Sub core (Hub, Client)
│ ├── server/ # TCP server, connection handling, command routing
│ │ ├── server.go # Server lifecycle, connection handling, command routing
│ │ ├── handlers.go # Command handlers (subscribe, publish, client, etc.)
│ │ ├── conn.go # RESP connection reader/writer
│ │ └── signals*.go # Platform-specific signal handling
│ └── compatibility/ # Client library compatibility tests
├── contrib/ # Init scripts for system service management
├── Makefile # Build automation
├── config_example.json # Example configuration
└── README.md # User documentation
Dependencies
- Go 1.24+
- Standard library only for production code
- Test dependencies (dev only):
github.com/redis/go-redis/v9- go-redis client for compatibility testsgithub.com/redis/rueidis- rueidis client for compatibility testsgithub.com/gomodule/redigo- redigo client for compatibility tests
Platform Support
Primary platform: Linux (amd64). Proven on Alpine Linux 3.16 x86_64.
Cross-compile targets: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, freebsd/amd64, windows/amd64.
Config file lookup:
- Linux (regular):
~/.pekoe.json→/etc/pekoe/config.json - Linux (root):
/etc/pekoe/config.json - macOS/BSD:
~/.pekoe.json→/usr/local/etc/pekoe/config.json - Windows:
--configflag required
Building: make (builds pekoe binary), make build-all (cross-compile), make install (to /usr/local/bin/).
Testing
- Go standard testing package
- Run all tests:
make test(with race detection) orgo test ./... - Run compatibility tests:
go test ./internal/compatibility/... - Run with verbose:
go test -v ./internal/compatibility/...
Test Coverage
Compatibility tests validate pekoe against multiple Redis client libraries:
| Library | Tests | Coverage |
|---|---|---|
| go-redis/v9 | 27 | Subscribe, publish, patterns, pubsub commands, unicode, binary |
| rueidis | 9 | Connectivity, publish, unicode messages |
| redigo | 19 | Raw protocol, subscribe, publish, patterns |
Test scenarios include:
- Basic SUBSCRIBE/PUBLISH/UNSUBSCRIBE
- Pattern PSUBSCRIBE/PUNSUBSCRIBE with wildcards
- Multiple channel/pattern subscriptions
- PUBSUB NUMSUB, CHANNELS, NUMPAT commands
- PING command (with and without message)
- QUIT command
- Binary-safe messages
- Empty and long messages
- Unicode content
- Concurrent operations
- Special characters in channel names
- Mixed channel and pattern subscriptions
Commands Supported
- SUBSCRIBE - Subscribe to channels
- UNSUBSCRIBE - Unsubscribe from channels
- PSUBSCRIBE - Subscribe to patterns
- PUNSUBSCRIBE - Unsubscribe from patterns
- PUBLISH - Publish message to channel
- PUBSUB - Pub/Sub introspection (NUMSUB, CHANNELS, NUMPAT, HELP)
- PING - Health check
- QUIT - Disconnect
- CLIENT - Connection introspection and control:
- CLIENT ID - Return unique connection ID
- CLIENT SETNAME/GETNAME - Connection naming
- CLIENT LIST - List connected clients
- CLIENT INFO - Client connection information
- CLIENT KILL - Close connections
- CLIENT PAUSE/UNPAUSE - Pause control
- CLIENT REPLY - Reply mode control (ON/OFF/SKIP)
- CLIENT TRACKING - Client-side caching (no-op for compatibility)
- CLIENT NO-EVICT/NO-TOUCH/CACHING - Compatibility stubs
Logging
Pekoe logs in a structured key=value format with timestamp and log level prefix:
Format: YYYY Mmm dd hh:mm:ss LEVEL pekoe[pid]: key=value ...
Example: 2024 Apr 2 14:30:15 INFO pekoe[12345]: event=startup addr=0.0.0.0:6379
Log levels:
debug- All messages including DEBUGinfo- INFO and above (default)warn- WARN, ERROR, CRITICALerror- ERROR, CRITICAL only
Signal handling:
- SIGINT/SIGTERM - Graceful shutdown
- SIGHUP - Reserved for future config reload
- SIGUSR1 (Unix) - Reopen log files for rotation
- SIGHUP (Windows) - Reopen log files for rotation
PID File Support
When pid_file is configured, pekoe will:
- Create the PID file on startup with the process ID
- Remove the PID file on graceful shutdown
- Log PID file events (created, removed, errors)
The PID file is optional - if pid_file is empty, no PID file is created.