add README
This commit is contained in:
parent
699aa30b07
commit
f690d10ead
1 changed files with 127 additions and 1 deletions
128
README.md
128
README.md
|
|
@ -1,3 +1,129 @@
|
|||
# cubby
|
||||
|
||||
A tiny in-memory key-value cache shared between processes via a Unix socket.
|
||||
A tiny in-memory key-value cache shared between processes via a Unix socket.
|
||||
|
||||
Stash a value, fetch it from another process. That's it.
|
||||
|
||||
## Run the server
|
||||
|
||||
```bash
|
||||
go run ./cmd/cubby # default: /tmp/cubby.sock, owner-only (0600)
|
||||
go run ./cmd/cubby -socket /tmp/c.sock # custom path
|
||||
go run ./cmd/cubby -group cache # share with members of "cache" group (0660)
|
||||
go run ./cmd/cubby -group 1001 # numeric GID also works
|
||||
```
|
||||
|
||||
## Use it from Go
|
||||
|
||||
```go
|
||||
import "cubby"
|
||||
|
||||
c, err := cubby.Dial("/tmp/cubby.sock")
|
||||
if err != nil { ... }
|
||||
defer c.Close()
|
||||
|
||||
c.Set("hello", "world", 0) // no TTL
|
||||
c.Set("session", "abc", 5*time.Second) // expires in 5s
|
||||
|
||||
v, ok, err := c.Get("hello") // ok=false means missing (not an error)
|
||||
|
||||
existed, _ := c.Del("hello")
|
||||
keys, _ := c.Keys()
|
||||
```
|
||||
|
||||
A `Client` is a single connection and isn't safe for concurrent use. For
|
||||
goroutines, use a `Pool`:
|
||||
|
||||
```go
|
||||
pool := cubby.NewPool("/tmp/cubby.sock", 8) // up to 8 idle conns
|
||||
defer pool.Close()
|
||||
|
||||
err := pool.Do(func(c *cubby.Client) error {
|
||||
return c.Set("k", "v", 0)
|
||||
})
|
||||
```
|
||||
|
||||
See `examples/basic` for a runnable example.
|
||||
|
||||
## Sharing with a group
|
||||
|
||||
By default the socket is `chmod 0600` and chowned to the user running cubby —
|
||||
only that user can connect. With `-group <name>`, the socket is created `0660`
|
||||
and chowned to that group, so any user in the group can connect. Everyone
|
||||
else is still locked out.
|
||||
|
||||
To set this up:
|
||||
|
||||
```bash
|
||||
sudo groupadd cache # create the group (one-time)
|
||||
sudo usermod -aG cache alice # add each user who should connect
|
||||
sudo usermod -aG cache bob
|
||||
# users need to log out and back in for new group membership to take effect
|
||||
go run server.go -group cache
|
||||
```
|
||||
|
||||
## Talk to it
|
||||
|
||||
The protocol is line-based, so you can poke at it with `nc -U`:
|
||||
|
||||
```
|
||||
$ nc -U /tmp/cubby.sock
|
||||
PING
|
||||
+PONG
|
||||
SET hello world
|
||||
+OK
|
||||
GET hello
|
||||
$"world"
|
||||
SET token abc 5
|
||||
+OK
|
||||
DEL hello
|
||||
#1
|
||||
KEYS
|
||||
#1
|
||||
$"token"
|
||||
QUIT
|
||||
+BYE
|
||||
```
|
||||
|
||||
## Protocol
|
||||
|
||||
**Commands** (one per line, whitespace-separated, values can be Go-quoted to
|
||||
include spaces or newlines):
|
||||
|
||||
| Command | Meaning |
|
||||
| --- | --- |
|
||||
| `SET <key> <value> [ttl_seconds]` | Stash a value, optional TTL |
|
||||
| `GET <key>` | Fetch a value |
|
||||
| `DEL <key>` | Remove a key |
|
||||
| `KEYS` | List all live keys |
|
||||
| `PING` | Health check |
|
||||
| `QUIT` | Close connection |
|
||||
|
||||
**Replies** (single sigil, terminated by `\n`):
|
||||
|
||||
| Sigil | Meaning |
|
||||
| --- | --- |
|
||||
| `+text` | OK / simple string |
|
||||
| `-text` | Error |
|
||||
| `$"value"` | Go-quoted string value |
|
||||
| `_` | Nil (key not found) |
|
||||
| `#n` | Count (integer) |
|
||||
|
||||
`KEYS` replies with `#N` followed by N `$"..."` lines.
|
||||
|
||||
## Design notes
|
||||
|
||||
- **Storage** is a single `map[string]entry` behind a `sync.RWMutex`. Plenty
|
||||
for a process-local cache. Shard by key hash if you ever need more.
|
||||
- **Expiry** is checked lazily on read and swept once per second.
|
||||
- **Quoting** uses Go's `strconv.Quote`/`Unquote`, so values can contain
|
||||
arbitrary bytes including spaces, tabs, and newlines.
|
||||
- **One goroutine per connection** — idiomatic Go, no event loop needed.
|
||||
- **Socket permissions** are set via `umask` before `Listen`, so there's no
|
||||
race where the socket exists with looser bits.
|
||||
- **Clean shutdown** on `SIGINT`/`SIGTERM` removes the socket file.
|
||||
|
||||
## What it doesn't do
|
||||
|
||||
No persistence, pub/sub, transactions, or data types beyond strings. It's a
|
||||
shared in-memory cache. If you need any of that, reach for something bigger.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue