Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
1faa3ac
change structure for pebble and logger
hertzcodes Jun 4, 2026
d927230
improve configuration
hertzcodes Jun 4, 2026
591154f
compile proto files
hertzcodes Jun 4, 2026
f29ea3d
add changelog
hertzcodes Jun 4, 2026
3bca9a1
update grpc
hertzcodes Jun 4, 2026
33145c8
update go version
hertzcodes Jun 11, 2026
0c07d81
app init
hertzcodes Jun 11, 2026
385c594
fix lint issues
hertzcodes Jun 11, 2026
6c6a5b7
add grpc server boilerplate
hertzcodes Jun 11, 2026
3768f28
add go test to pipeline
hertzcodes Jun 11, 2026
40281e0
setup event repository
hertzcodes Jun 11, 2026
c2ef0e9
fix graceful shutdown
hertzcodes Jun 11, 2026
e6551e5
add event storage
hertzcodes Jun 11, 2026
c759ed0
add producer handler for event storage and bucket size support
hertzcodes Jun 11, 2026
d0bd9c0
fix bucket size (set 0ms as min)
hertzcodes Jun 11, 2026
806e463
checkpoint
hertzcodes Jun 12, 2026
b883e06
upgrade pebble
hertzcodes Jun 12, 2026
8478d67
add more configs to raft
hertzcodes Jun 12, 2026
284aa9e
config validation on snapshot entries and compaction
hertzcodes Jun 12, 2026
7e16e3f
ignore main executable
hertzcodes Jun 12, 2026
7e402be
add raft enable config
hertzcodes Jun 13, 2026
cb34985
fix wal validation when raft is disabled
hertzcodes Jun 18, 2026
caf0563
checkpoint - consumer
hertzcodes Jun 18, 2026
cbbf380
make proto a separate package
hertzcodes Jun 18, 2026
4258d00
add sdk
hertzcodes Jun 18, 2026
e1930dc
update gitignore
hertzcodes Jun 18, 2026
3d93897
add grpc server and consumer config
hertzcodes Jun 18, 2026
e9aa00c
fix raft tests
hertzcodes Jun 18, 2026
f1143fd
move calculatebucket to utils
hertzcodes Jun 19, 2026
1b691b0
shit vibe coded version of consumer
hertzcodes Jun 20, 2026
79c78f4
remove pb and sdk from project
hertzcodes Jun 23, 2026
f280f31
upgrade proto version
hertzcodes Jun 23, 2026
d8faf02
add new config
hertzcodes Jun 24, 2026
0aeb817
remove bucket and add key utils
hertzcodes Jun 24, 2026
e7bf660
add vendor to gitignore
hertzcodes Jun 24, 2026
3646c91
add bucket cache
hertzcodes Jun 24, 2026
5ac0c92
make producer support batching
hertzcodes Jun 27, 2026
d9b415f
deduplicate producers loop
hertzcodes Jun 27, 2026
02e32ca
add shitty consumer and cluster metadata
hertzcodes Jun 27, 2026
195c076
set QUORUM as default ACK level and update proto
hertzcodes Jun 27, 2026
36e5eed
upgrade proto version
hertzcodes Jun 27, 2026
49070da
add support for custom index mappings to delivery_tags (pebble keys)
hertzcodes Jun 27, 2026
1804f67
change from string delivery tags to byte
hertzcodes Jun 28, 2026
93ec21b
add abstraction layer for storage
hertzcodes Jul 4, 2026
f45a902
remove newsnapshot from storage API
hertzcodes Jul 4, 2026
aab27bf
adapt new storage interface everywhere
hertzcodes Jul 4, 2026
0002a96
add a vibe coded bolt db implementation for storage
hertzcodes Jul 4, 2026
b3e2ed2
add a Scan generator for storage to prevent dynamic dispatch in iter
hertzcodes Jul 4, 2026
3dd2810
make state machine snapshots use scan interface and change scan inter…
hertzcodes Jul 4, 2026
0c842c7
add changelog
hertzcodes Jul 4, 2026
2531ea1
fix typo in app logs when closing db
hertzcodes Jul 4, 2026
c1a55f0
add min ack_level to configuration
hertzcodes Jul 4, 2026
79e342b
add validation to batch delays and ttl
hertzcodes Jul 4, 2026
4010fc3
remove swim protocols
hertzcodes Jul 4, 2026
892532e
move event state machine to event package
hertzcodes Jul 4, 2026
3122511
add actual support for boltdb
hertzcodes Jul 4, 2026
0df7c4e
fix lint issues
hertzcodes Jul 4, 2026
0bd3953
use topic bucket event id as key, add gitnexus claude skills
hertzcodes Jul 30, 2026
c36fc87
add dockerfile
hertzcodes Jul 30, 2026
6ff9474
add metadata raft group and change some utils
hertzcodes Jul 30, 2026
76b53ae
new fresh consumers and node join strategy with metadata groups
hertzcodes Jul 30, 2026
5f9262b
add getclusterinfo
hertzcodes Jul 30, 2026
c956b79
fix lint issues
hertzcodes Jul 30, 2026
0ae67cf
add grpc addr to cluster info
hertzcodes Jul 30, 2026
dd949f7
fix join issues
hertzcodes Jul 30, 2026
4f34288
add readme
hertzcodes Jul 30, 2026
bb2f4af
add tests for key utils
hertzcodes Jul 31, 2026
4ed2fc4
remove unused cache
hertzcodes Jul 31, 2026
0923c40
add tests for pebble
hertzcodes Jul 31, 2026
4a035be
remove gogo and use google proto. add tests for statemachines
hertzcodes Jul 31, 2026
118d637
add tests for metadata statemachine
hertzcodes Jul 31, 2026
d3c448a
ignore golangci lint on tests
hertzcodes Jul 31, 2026
a79e4ae
add tests for event statemachine
hertzcodes Jul 31, 2026
288ee0a
add dispatcher tests
hertzcodes Jul 31, 2026
3339838
make the storage engine tests the same and use a contract
hertzcodes Jul 31, 2026
e2ea0d7
fix closer bug panic
hertzcodes Jul 31, 2026
6acac44
fix event statemachine bugs in zero value headers
hertzcodes Jul 31, 2026
c847b14
remove unnecessary comment
hertzcodes Jul 31, 2026
aa84df3
fix golang-ci
hertzcodes Jul 31, 2026
fe8e451
fix topology info
hertzcodes Aug 1, 2026
1d48fed
fix cmd package
hertzcodes Aug 2, 2026
cca05b9
cmd package
hertzcodes Aug 2, 2026
b61ab95
remove claude skills
hertzcodes Aug 5, 2026
2c8bd42
update gitignore
hertzcodes Aug 5, 2026
7de9ec7
add metrics
hertzcodes Aug 5, 2026
53d72a1
add deleter and consumer metrics
hertzcodes Aug 5, 2026
9a83cbd
add metrics to janitor
hertzcodes Aug 5, 2026
b013989
add metrics to producer
hertzcodes Aug 5, 2026
51bcc53
fix dispatchers bug and add metrics
hertzcodes Aug 5, 2026
4070109
fix config and app bootstrap
hertzcodes Aug 6, 2026
d6c9b1f
fix topic label lol
hertzcodes Aug 6, 2026
fbd7f06
change topic label to a set
hertzcodes Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ on:

permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
pull-requests: read

jobs:
golangci:
Expand All @@ -22,5 +21,5 @@ jobs:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
# with:
# version: v1.64
with:
version: latest
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Go Tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
test:
name: Run Tests
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.2'
cache: true

- name: Install Dependencies
run: go mod download

- name: Run Tests
run: go test -race -v ./...
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
config.yaml
config.yml
!config.example.yaml
dev-config.yaml
*.pdf
*.html
e2e-tests*
main
go.work
go.work.sum
vendor
*.txt
notes.md
.claude
AGENTS.md
CLAUDE.md
45 changes: 45 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
version: "2"

run:
timeout: 5m
tests: true

linters:
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- path: _test\.go
linters:
- errcheck
- govet
- staticcheck
- unused
- revive
- gocritic
- gocyclo
- dupl
- funlen
- goconst
- lll
- misspell
- unparam
- unconvert
- gosec
- gochecknoinits
- gochecknoglobals

issues:
max-issues-per-linter: 0
max-same-issues: 0

output:
formats:
text:
path: stdout
print-linter-name: true
print-issued-lines: true
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

All notable changes to this project will be documented in this file.
37 changes: 37 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Build stage
FROM golang:1.26-alpine AS builder

WORKDIR /app

# Install build dependencies
RUN apk add --no-cache git ca-certificates

# Copy go mod files and vendor directory first for better layer caching
COPY go.mod go.sum ./
COPY vendor/ ./vendor/

# Copy source code
COPY . .

# Build the binary using vendor dependencies
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=vendor -ldflags="-w -s" -o /futureq ./cmd/futureq

# Runtime stage
FROM alpine:3.24

WORKDIR /app

# Install ca-certificates for HTTPS and runas non-root user
RUN apk add --no-cache ca-certificates tzdata && \
adduser -D -u 1000 appuser

USER appuser

# Copy binary from builder
COPY --from=builder /futureq /app/futureq

# Expose default gRPC port (can be overridden via config)
EXPOSE 50051

ENTRYPOINT ["/app/futureq"]
CMD ["start"]
177 changes: 177 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,180 @@
# FutureQ

**A high-performance, distributed delayed-message queue broker written in Go.**

FutureQ lets producers publish messages with a relative delay and guarantees reliable dispatch to consumers when the delay expires. It combines durable embedded storage, Raft-based replication, and bidirectional gRPC streaming into a single, easy-to-operate binary.

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](go.mod)

---

## Features

- **Delayed messaging** — enqueue a message with a `delay_ms`; it becomes visible to consumers only after the delay expires.
- **Durable storage** — disk-backed by [Pebble](https://github.com/cockroachdb/pebble) (CockroachDB's LSM store) with a time-optimized key schema, or pure in-memory for ephemeral workloads.
- **High availability** — multi-node replication via [Dragonboat](https://github.com/lni/dragonboat) (multi-group Raft), with a metadata Raft group for cluster membership.
- **Dynamic membership** — nodes join and leave a running cluster over gRPC (`JoinCluster` / `LeaveCluster`); no static bootstrap list required after the first node.
- **Consumer groups & topics** — topic-based routing with fan-out across groups and round-robin dispatch within a group.
- **At-least-once delivery** — in-flight tracking with automatic re-dispatch of unacknowledged messages; batched deletes amortize LSM tombstone costs.
- **Message TTL** — a background janitor removes expired messages that were never consumed.
- **Observability** — Prometheus metrics endpoint and structured logging (zap) out of the box.

## Technology Stack

| Concern | Choice |
| ------------ | --------------------------------------------- |
| Language | Go 1.26 |
| Storage | Pebble (LSM tree) |
| Consensus | Dragonboat (multi-group Raft) |
| Transport | gRPC (bidirectional streaming) |
| Metrics | Prometheus |
| Protocol | [`futureq-io/protocol`](https://github.com/futureq-io/protocol) (Protobuf) |

## Architecture Overview

```
┌──────────────┐ PublishStream ┌──────────────────────────┐
│ Producer │ ─────────────────▶│ │
└──────────────┘ │ FutureQ Node │
│ │
┌──────────────┐ Subscribe │ ┌────────────────────┐ │
│ Consumer │ ◀──────────────── │ │ gRPC API (8443) │ │
└──────────────┘ │ └─────────┬──────────┘ │
│ ▼ │
┌──────────────┐ Raft (50005) │ ┌────────────────────┐ │
│ Other Nodes │ ◀───────────────▶ │ │ Dispatcher / Hub │ │
└──────────────┘ │ │ Deleter · Janitor │ │
│ └─────────┬──────────┘ │
┌──────────────┐ Prometheus │ ▼ │
│ Metrics │ ◀── (9090) ──────│ │ Pebble + Raft log │ │
└──────────────┘ │ └────────────────────┘ │
└──────────────────────────┘
```

- **Writes** go to the Raft leader (or straight to Pebble in standalone mode) and are stored under time-bucketed keys for efficient expiry scans.
- **Reads** are push-based: a dispatcher continuously scans for matured messages and routes them to connected consumers through a hub using a round-robin strategy.
- **Acks** are batched by a deleter and committed as a single Raft proposal, keeping write amplification low.

Delivery is **at-least-once** — consumers should be idempotent.

## Quick Start

### Prerequisites

- Go 1.26+
- (Optional) Docker

### Build & run a standalone node

```bash
git clone https://github.com/futureq-io/futureq.git
cd futureq
go build -o futureq ./cmd/futureq

cp config.example.yaml config.yaml # adjust as needed
./futureq start -c config.yaml
```

A standalone node (no `raft` section, or `raft.enabled: false`) writes directly to Pebble — perfect for local development.

### Run a 3-node cluster

On the first node, enable Raft and list all initial members:

```yaml
raft:
enabled: true
nodeId: 1
clusterId: 1
listenAddress: "0.0.0.0:50005"
initialMembers:
1: "10.0.0.1:50005"
```

Additional nodes join dynamically — no need to edit `initialMembers`:

```bash
./futureq start -c node2.yaml --join 10.0.0.1:8443
./futureq start -c node3.yaml --join 10.0.0.1:8443
```

On first start the node contacts each seed until one accepts its `JoinCluster` request; membership is registered on both the event shard and the metadata group. Restarts detect local Raft data and skip the join flow automatically.

### Docker

```bash
docker build -t futureq .
docker run -p 8443:8443 -p 9090:9090 -p 50005:50005 \
-v $(pwd)/config.yaml:/app/config.yaml \
futureq start -c /app/config.yaml
```

## Configuration

Every value is documented in [`config.example.yaml`](config.example.yaml), which mirrors the built-in defaults. Key sections:

| Section | Highlights |
| ---------------- | ----------------------------------------------------------------- |
| `server` | gRPC listen address, connection limits, message size caps |
| `storage` | Engine (`pebble`), persistence toggle, time-bucket granularity |
| `storage.pebble` | WAL toggle, data path, cache/memtable sizing |
| `raft` | Node/cluster IDs, listen address, initial members, snapshot tuning |
| `consumer` | Dispatch poll interval, batched-delete interval, in-flight timeout, TTL janitor interval |
| `observability` | Log level, Prometheus listen address |

Every value can be overridden with environment variables using the `FUTUREQ_` prefix, replacing dots with underscores:

```bash
export FUTUREQ_STORAGE_PEBBLE_DATAPATH="/var/lib/futureq/data"
export FUTUREQ_OBSERVABILITY_LOGGER_LEVEL="debug"
```

## API

FutureQ speaks gRPC; protobuf definitions live in [`futureq-io/protocol`](https://github.com/futureq-io/protocol).

| RPC | Type | Description |
| ------------------ | ------------------- | -------------------------------------------------- |
| `PublishStream` | bidi streaming | Publish batches of delayed messages; receive per-batch acks |
| `Subscribe` | bidi streaming | Receive messages for a topic/consumer group; ack over the same stream |
| `GetClusterInfo` | unary | Cluster topology, leader and member metadata |
| `JoinCluster` | unary | Add a node to the event shard and metadata group |
| `LeaveCluster` | unary | Gracefully remove a node from the cluster |
| `LeaveMetadata` | unary | Remove a node from the metadata group only |

Metrics are exposed at `observability.metrics.addr` (default `:9090`) in Prometheus format.

## Project Layout

```
internal/
main.go # entrypoint
cmd/ # Cobra CLI (start, leave)
app/ # wiring: storage, repositories, Raft lifecycle
api/grpc/ # gRPC server + handlers (producer, consumer, cluster)
dispatcher/ # scan/dispatch loop, hub, deleter, TTL janitor
storage/ # Pebble engine, time-bucket key schema
raft/ # Dragonboat state machine & event commands
repository/ # event repository abstraction
config/ # config loading + env overrides
metrics/ # Prometheus server
pkg/
raft/metadata/ # metadata-group Raft (cluster membership)
log/ # zap logger setup
utils/ # shared helpers
```

## Development

```bash
go build ./... # build
go test ./... # run tests
golangci-lint run # lint
```

Contributions are welcome — please open an issue to discuss substantial changes before sending a PR.

## License

[MIT](LICENSE)
Loading
Loading