mirror of
https://github.com/tiennm99/keepalive.git
synced 2026-10-11 03:13:31 +00:00
A failed connect or tick now closes the connection and reconnects after one minute, re-running initialization so a dropped table or row is recreated. Before, a failing tick logged every interval forever and connect retries logged every 10 seconds. Connection URLs that fail to parse no longer appear in logs, since their error repeats the password. An unknown adapter or a missing required config key now stops keepalive at startup instead of silently idling one service.
151 lines
6.8 KiB
Markdown
151 lines
6.8 KiB
Markdown
# keepalive
|
|
|
|
Pluggable Go daemon that periodically touches external services to prevent idle shutdowns, pauses, or cold starts.
|
|
|
|
The current adapters perform cheap datastore writes for Redis Cloud, Valkey, Aiven, Neon, Supabase, MongoDB Atlas, Couchbase Capella, and similar hosted services.
|
|
|
|
Successor to the `*-keepalive` family: one binary, one image, five datastore adapters. Valkey and other Redis-compatible stores use the `redis` adapter.
|
|
|
|
## Configuration
|
|
|
|
By default, keepalive reads the first config file it finds: `config.yml`, `config.yaml`, `/config.yml`, then `/config.yaml`. One deployment can keep any number of services alive.
|
|
|
|
```yaml
|
|
# Default interval for every service.
|
|
interval: 1m
|
|
counter_key: counter
|
|
|
|
services:
|
|
- adapter: redis
|
|
config:
|
|
url: redis://default@redis-a.example.com:6379
|
|
namespace: keepalive
|
|
|
|
# Valkey, Dragonfly, KeyDB and other Redis-compatible stores use the redis
|
|
# adapter with a redis:// or rediss:// (TLS) URL.
|
|
- name: valkey-a
|
|
adapter: redis
|
|
# One service can override the global interval and counter key.
|
|
interval: 30s
|
|
counter_key: valkey-counter
|
|
config:
|
|
url: rediss://default@valkey-a.example.com:6379
|
|
namespace: keepalive
|
|
|
|
- adapter: postgresql
|
|
config:
|
|
url: postgresql://user:pass@postgres-a.example.com:5432/keepalive?sslmode=require
|
|
|
|
- adapter: mysql
|
|
config:
|
|
dsn: user:pass@tcp(mysql-a.example.com:3306)/keepalive
|
|
|
|
- adapter: mongodb
|
|
config:
|
|
uri: mongodb+srv://user:pass@mongo-a.example.com
|
|
database: keepalive
|
|
collection: counter
|
|
|
|
- adapter: couchbase
|
|
config:
|
|
connection_string: couchbases://couchbase-a.example.com
|
|
username: user
|
|
password: pass
|
|
bucket_name: keepalive
|
|
scope_name: _default
|
|
collection_name: _default
|
|
```
|
|
|
|
`name` is optional. When omitted, keepalive generates a name from `adapter` and the connection host, such as `redis-redis-a-example-com`. Duplicate generated names get suffixes like `redis-redis-a-example-com-2`.
|
|
|
|
`interval` at the root sets the default schedule for every service and defaults to `1m`.
|
|
`interval` inside a service overrides that default only for that service.
|
|
In the example above, every service runs every `1m` except `valkey-a`, which runs every `30s`.
|
|
Interval values use Go duration syntax, for example `30s`, `5m`, `1h`, `1h30m`, or `1.5h`. Plain integers are treated as seconds, so `90` means `90s`.
|
|
|
|
`counter_key` at the root sets the default counter key for every service and defaults to `counter`.
|
|
`counter_key` inside a service overrides that default only for that service.
|
|
In the example above, every service writes `counter` except `valkey-a`, which writes `keepalive:valkey-counter`.
|
|
|
|
## Supported adapters
|
|
|
|
| `adapter` | Driver | `config` keys |
|
|
| ------------ | ----------------------------------- | ------------- |
|
|
| `redis` | `github.com/redis/go-redis/v9` | `url` (`redis://` or `rediss://`), optional `namespace` |
|
|
| `postgresql` | `github.com/lib/pq` | `url` |
|
|
| `mysql` | `github.com/go-sql-driver/mysql` | `dsn` |
|
|
| `mongodb` | `go.mongodb.org/mongo-driver/v2` | `uri`, `database`, `collection` |
|
|
| `couchbase` | `github.com/couchbase/gocb/v2` | `connection_string`, `username`, `password`, `bucket_name`, `scope_name`, `collection_name`, optional `ready_timeout`, optional `bucket_ram_quota_mb` |
|
|
|
|
## Quick start (Compose)
|
|
|
|
```bash
|
|
cp config.example.yml config.yml
|
|
docker compose up -d --build
|
|
```
|
|
|
|
`compose.yml` also deploys on Coolify with the Docker Compose build pack (compose file `/compose.yml`). Coolify turns the `./config.yml` bind mount into an editable file storage; paste your config there. keepalive is a background worker with no port, so leave the service without a domain. Keep the real `config.yml` out of git, since it holds datastore credentials.
|
|
|
|
## Quick start (Docker)
|
|
|
|
```bash
|
|
docker build -t keepalive:local .
|
|
docker run -d --name keepalive --restart unless-stopped \
|
|
-v "$PWD/config.yml:/config.yml:ro" \
|
|
keepalive:local
|
|
```
|
|
|
|
If you prefer to mount the config into a working directory instead of the container root, set the container working directory and mount the file there:
|
|
|
|
```bash
|
|
docker run -d --name keepalive --restart unless-stopped \
|
|
--workdir /workspace \
|
|
-v "$PWD/config.yml:/workspace/config.yml:ro" \
|
|
keepalive:local
|
|
```
|
|
|
|
## Quick start (local)
|
|
|
|
```bash
|
|
git clone https://github.com/tiennm99/keepalive
|
|
cd keepalive
|
|
cp config.example.yml config.yml
|
|
go run .
|
|
```
|
|
|
|
## How it works
|
|
|
|
On startup each adapter initializes the minimum resource it owns, then every tick performs the cheapest write that proves the cluster is alive. `counter_key` selects the key/doc ID and defaults to `counter`.
|
|
|
|
- **Redis** (also Valkey, Dragonfly, KeyDB, Garnet, Upstash) — initialize with `SETNX key 0`, then `INCR key`. When `namespace` is empty, the key is `counter`; when `namespace: keepalive`, the key is `keepalive:counter`.
|
|
- **PostgreSQL** — `CREATE TABLE IF NOT EXISTS keepalive`, seed `key`, then `UPDATE ... RETURNING`
|
|
- **MySQL** — `CREATE TABLE IF NOT EXISTS keepalive`, seed `key`, then `UPDATE` + `SELECT`
|
|
- **MongoDB** — upsert `{_id: key, count: 0}` on connect, then `FindOneAndUpdate({_id: key}, {$inc: {count: 1}}, upsert)`
|
|
- **Couchbase** — optionally create the bucket when `bucket_ram_quota_mb` is set, create configured scope/collection when missing, insert `key = 0` if missing, then an atomic binary `INCREMENT key`
|
|
|
|
Each configured service starts independently. When a connect or a tick fails, the service logs the error, closes its connection, and reconnects after 1 minute, which also re-runs initialization (for example, recreating a dropped table). Other services in the same deployment keep running. An unknown `adapter` or a missing required `config` key stops keepalive at startup.
|
|
|
|
For hosted Couchbase/Capella clusters, `ready_timeout` defaults to `30s`. If Couchbase reports `CONNECTION_ERROR`, check the connection string, bucket name, database user permissions, and Capella allowed IP/network access.
|
|
|
|
## Adding a new adapter
|
|
|
|
1. Create `adapter/<name>.go`.
|
|
2. Implement the `Adapter` interface in `adapter/adapter.go` (`Connect`, `Increment`, `Close`).
|
|
3. Register the factory in `init()`:
|
|
```go
|
|
func init() { Registry["<name>"] = func(cfg Config) (Adapter, error) { return &myAdapter{}, nil } }
|
|
```
|
|
4. Add an `import _ "your driver"` if needed, and the adapter config keys to `config.example.yml` and the table above.
|
|
|
|
## Migrated from
|
|
|
|
This repo replaces six single-datastore repos (`redis-keepalive`, `valkey-keepalive`,
|
|
`postgresql-keepalive`, `mysql-keepalive`, `mongodb-keepalive`, `couchbase-keepalive`).
|
|
Their full histories were absorbed into this repository — browse earlier commits to find
|
|
each implementation under its own subfolder (`redis/`, `valkey/`, `postgresql/`, `mysql/`,
|
|
`mongodb/`, `couchbase/`).
|
|
|
|
## License
|
|
|
|
Apache-2.0 — see [LICENSE](LICENSE).
|