Skip to content

Commit 4990aff

Browse files
authored
refactor: add support for postgres store(s) (#28)
* refactor: add support for postgres store(s) closes #27 * fix: introduce MigratedPool type for proper startup * refactor: remove job queue from postgres agent store
1 parent 0188d29 commit 4990aff

50 files changed

Lines changed: 3404 additions & 248 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ Sprue is the upload coordination service for Storacha local development. It rout
4242

4343
**Stores (pkg/store/)**
4444
- Each domain has its own store interface in `pkg/store/<domain>/`
45-
- Each store has two implementations: AWS (DynamoDB/S3) in `<domain>/aws/` and in-memory in `<domain>/memory/`
45+
- Each store has three implementations: AWS (DynamoDB/S3) in `<domain>/aws/`, PostgreSQL (+ S3 for blob payloads) in `<domain>/postgres/`, and in-memory in `<domain>/memory/`
4646
- Store interfaces: `agent.Store`, `blob_registry.Store`, `consumer.Store`, `customer.Store`, `delegation.Store`, `metrics.Store`, `replica.Store`, `revocation.Store`, `space_diff.Store`, `storage_provider.Store`, `subscription.Store`, `upload.Store`
47-
- AWS stores are wired in `internal/fx/store/aws/provider.go`, memory stores in `internal/fx/store/memory/provider.go`
47+
- Backends are wired in `internal/fx/store/<backend>/provider.go` (aws, postgres, memory)
48+
- Backend selection is driven by `storage.type` in config (`memory` | `postgres` | `aws`; default `postgres`). Per-backend settings live under `storage.postgres`, `storage.dynamodb`, and `storage.s3`.
49+
- Postgres schema is managed by goose migrations in `internal/migrations/sql/`, embedded and applied on startup. Set `storage.postgres.skip_migrations: true` to disable.
4850

4951
**Services (pkg/)**
5052
- `provisioning`: Manages space provisioning (consumers + subscriptions)
@@ -66,18 +68,21 @@ Sprue is the upload coordination service for Storacha local development. It rout
6668

6769
### Configuration
6870

69-
Configuration via YAML file or environment variables with `UPLOAD_` prefix:
70-
- `UPLOAD_SERVER_HOST`, `UPLOAD_SERVER_PORT`
71-
- `UPLOAD_IDENTITY_KEY_FILE`, `UPLOAD_IDENTITY_PRIVATE_KEY`, `UPLOAD_IDENTITY_SERVICE_DID`
72-
- `UPLOAD_PIRI_ENDPOINT`, `UPLOAD_INDEXER_ENDPOINT`
73-
- `UPLOAD_DYNAMODB_*` for DynamoDB settings
74-
75-
Legacy env vars without prefix (e.g., `HOST`, `PORT`, `KEY_FILE`) also supported.
71+
Configuration via YAML file or environment variables with `SPRUE_` prefix:
72+
- `SPRUE_STORAGE_TYPE` — selects the store backend (`memory`, `postgres`, `aws`; default `postgres`)
73+
- `SPRUE_SERVER_HOST`, `SPRUE_SERVER_PORT`
74+
- `SPRUE_IDENTITY_KEY_FILE`, `SPRUE_IDENTITY_PRIVATE_KEY`, `SPRUE_IDENTITY_SERVICE_DID`
75+
- `SPRUE_INDEXER_ENDPOINT`
76+
- `SPRUE_STORAGE_POSTGRES_DSN`, `SPRUE_STORAGE_POSTGRES_MAX_CONNS`, `SPRUE_STORAGE_POSTGRES_SKIP_MIGRATIONS`
77+
- `SPRUE_STORAGE_DYNAMODB_*` for DynamoDB settings (AWS backend)
78+
- `SPRUE_STORAGE_S3_*` for S3/MinIO settings
7679

7780
### Key Dependencies
7881

7982
- **go-ucanto**: UCAN RPC framework for capability-based authorization
8083
- **go-libstoracha**: Storacha capability definitions (blob, space, upload, etc.)
8184
- **echo/v4**: HTTP server framework
82-
- **aws-sdk-go-v2**: DynamoDB client
85+
- **aws-sdk-go-v2**: DynamoDB + S3 client
86+
- **jackc/pgx/v5**: PostgreSQL driver
87+
- **pressly/goose/v3**: SQL schema migrations
8388
- **viper/cobra**: Configuration and CLI

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ VERSION=$(shell awk -F'"' '/"version":/ {print $$4}' version.json)
22
COMMIT=$(shell git rev-parse --short HEAD)
33
DATE=$(shell date -u -Iseconds)
44
GOFLAGS=-ldflags="-X github.com/storacha/sprue/pkg/build.version=$(VERSION) -X github.com/storacha/sprue/pkg/build.Commit=$(COMMIT) -X github.com/storacha/sprue/pkg/build.Date=$(DATE) -X github.com/storacha/sprue/pkg/build.BuiltBy=make"
5+
DOCKER := $(shell which docker)
56

67
.PHONY: all build test lint clean docker-build
78

@@ -35,4 +36,4 @@ clean:
3536
rm -f ./sprue
3637

3738
docker-build:
38-
docker build -t sprue:latest .
39+
$(DOCKER) build -t sprue:latest .

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
# Sprue
22

3-
The Storacha upload service in Go.
3+
The Forge upload service in Go (formerly the Storacha upload service).
4+
5+
## Running locally
6+
7+
The repo ships a `docker-compose.yaml` that brings up sprue alongside
8+
PostgreSQL and MinIO for self-hosted development:
9+
10+
```bash
11+
docker compose up -d postgres minio
12+
SPRUE_STORAGE_POSTGRES_DSN="postgres://sprue:sprue@localhost:5432/sprue?sslmode=disable" \
13+
./sprue serve
14+
```
15+
16+
Postgres is the default store backend, so no extra flag is required.
17+
18+
## Store backends
19+
20+
Sprue supports three store backends, selected by
21+
`storage.type` (or `SPRUE_STORAGE_TYPE`; defaults to `postgres`):
22+
23+
- `memory` — in-process only; all data is lost on restart. Dev/test only.
24+
- `postgres` — PostgreSQL for metadata + S3-compatible storage (MinIO, Ceph, AWS S3)
25+
for blob payloads. Schema is managed by goose migrations embedded in
26+
`internal/migrations/sql/` and applied on startup.
27+
- `aws` — DynamoDB for metadata + S3 for blob payloads.
428

529
## Notes
630

config.example.yaml

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Sprue Configuration
22
#
33
# This file documents all configuration options. Copy to config.yaml and customize.
4-
# All values can also be set via environment variables with UPLOAD_ prefix.
4+
# All values can also be set via environment variables with SPRUE_ prefix.
55
# Environment variables take precedence over config file values.
66
#
77
# Examples:
8-
# server.host -> UPLOAD_SERVER_HOST
9-
# dynamodb.endpoint -> UPLOAD_DYNAMODB_ENDPOINT
8+
# server.host -> SPRUE_SERVER_HOST
9+
# storage.type -> SPRUE_STORAGE_TYPE
10+
# storage.postgres.dsn -> SPRUE_STORAGE_POSTGRES_DSN
1011
#
1112

1213
# Deployment configuration
@@ -20,11 +21,6 @@ deployment:
2021
# includes the original blob that was uploaded, so only values above 1 will
2122
# allow users to have multiple copies of their data.
2223
max_replicas: 3
23-
# Indicates whether to use in-memory stores instead of DynamoDB/S3. All data
24-
# will be lost on service restart when this is true, so it should only be used
25-
# for development or testing. It overrides all other store-related config when
26-
# true.
27-
in_memory_stores: false
2824

2925
server:
3026
# Host address to bind the HTTP server to
@@ -63,33 +59,51 @@ mailer:
6359
# Secret for CRAMMD5 SMTP authentication
6460
smtp_auth_secret: ""
6561

66-
dynamodb:
67-
# DynamoDB endpoint (for local development with DynamoDB Local)
68-
endpoint: "http://dynamodb-local:8000"
69-
# AWS region for DynamoDB
70-
region: "us-west-1"
71-
agent_index_table: "agent-index"
72-
blob_registry_table: "blob-registry"
73-
consumer_table: "consumer"
74-
customer_table: "customer"
75-
delegation_table: "delegation"
76-
space_metrics_table: "space-metrics"
77-
admin_metrics_table: "admin-metrics"
78-
replica_table: "replica"
79-
revocation_table: "revocation"
80-
storage_provider_table: "storage-provider"
81-
subscription_table: "subscription"
82-
space_diff_table: "space-diff"
83-
upload_table: "upload"
62+
# Storage backend selection and per-backend configuration.
63+
storage:
64+
# Selects the backend. Valid values: "memory", "postgres", "aws". Default "postgres".
65+
# - memory: in-process stores — all data lost on restart; dev/test only.
66+
# - postgres: PostgreSQL for metadata + S3-compatible storage for blob payloads.
67+
# - aws: DynamoDB for metadata + S3 for blob payloads.
68+
type: "postgres"
8469

85-
s3:
86-
# S3 endpoint (for local development with MinIO)
87-
endpoint: "http://minio:9000"
88-
# S3 region
89-
region: "us-west-1"
90-
agent_message_bucket: "agent-message"
91-
delegation_bucket: "delegation"
92-
upload_shards_bucket: "upload-shards"
70+
postgres:
71+
# libpq-style connection string (used when storage.type is "postgres").
72+
dsn: "postgres://sprue:sprue@postgres:5432/sprue?sslmode=disable"
73+
# Maximum number of pool connections. 0 uses the pgx default.
74+
max_conns: 10
75+
# Minimum idle connections to keep warm.
76+
min_conns: 0
77+
# Set to true to skip goose migrations at startup (they run by default).
78+
skip_migrations: false
79+
80+
dynamodb:
81+
# DynamoDB endpoint (for local development with DynamoDB Local).
82+
endpoint: "http://dynamodb-local:8000"
83+
# AWS region for DynamoDB.
84+
region: "us-west-1"
85+
agent_index_table: "agent-index"
86+
blob_registry_table: "blob-registry"
87+
consumer_table: "consumer"
88+
customer_table: "customer"
89+
delegation_table: "delegation"
90+
space_metrics_table: "space-metrics"
91+
admin_metrics_table: "admin-metrics"
92+
replica_table: "replica"
93+
revocation_table: "revocation"
94+
storage_provider_table: "storage-provider"
95+
subscription_table: "subscription"
96+
space_diff_table: "space-diff"
97+
upload_table: "upload"
98+
99+
s3:
100+
# S3 endpoint (for local development with MinIO).
101+
endpoint: "http://minio:9000"
102+
# S3 region.
103+
region: "us-west-1"
104+
agent_message_bucket: "agent-message"
105+
delegation_bucket: "delegation"
106+
upload_shards_bucket: "upload-shards"
93107

94108
log:
95109
# Log level: debug, info, warn, error

go.mod

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,41 @@ require (
1313
github.com/ipfs/go-cid v0.6.0
1414
github.com/ipfs/go-log/v2 v2.9.0
1515
github.com/ipld/go-ipld-prime v0.21.1-0.20240917223228-6148356a4c2e
16+
github.com/jackc/pgx/v5 v5.9.1
1617
github.com/labstack/echo/v4 v4.14.0
1718
github.com/multiformats/go-multiaddr v0.16.0
1819
github.com/multiformats/go-multibase v0.2.0
1920
github.com/multiformats/go-multihash v0.2.3
2021
github.com/olekukonko/tablewriter v0.0.5
22+
github.com/pressly/goose/v3 v3.27.0
2123
github.com/spf13/cobra v1.10.2
2224
github.com/spf13/viper v1.21.0
2325
github.com/storacha/go-libstoracha v0.7.5
2426
github.com/storacha/go-ucanto v0.7.2
2527
github.com/stretchr/testify v1.11.1
26-
github.com/testcontainers/testcontainers-go v0.41.0
28+
github.com/testcontainers/testcontainers-go v0.42.0
2729
github.com/testcontainers/testcontainers-go/modules/dynamodb v0.41.0
2830
github.com/testcontainers/testcontainers-go/modules/minio v0.40.0
31+
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0
2932
go.uber.org/fx v1.24.0
3033
go.uber.org/zap v1.27.0
3134
)
3235

36+
require (
37+
github.com/jackc/pgpassfile v1.0.0 // indirect
38+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
39+
github.com/jackc/puddle/v2 v2.2.2 // indirect
40+
github.com/mfridman/interpolate v0.0.2 // indirect
41+
github.com/moby/moby/api v1.54.1 // indirect
42+
github.com/moby/moby/client v0.4.0 // indirect
43+
github.com/moby/sys/atomicwriter v0.1.0 // indirect
44+
github.com/sethvargo/go-retry v0.3.0 // indirect
45+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
46+
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
47+
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
48+
golang.org/x/sync v0.20.0 // indirect
49+
)
50+
3351
require (
3452
dario.cat/mergo v1.0.2 // indirect
3553
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
@@ -59,7 +77,6 @@ require (
5977
github.com/containerd/log v0.1.0 // indirect
6078
github.com/containerd/platforms v0.2.1 // indirect
6179
github.com/cpuguy83/dockercfg v0.3.2 // indirect
62-
github.com/creack/pty v1.1.24 // indirect
6380
github.com/davecgh/go-spew v1.1.1 // indirect
6481
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
6582
github.com/distribution/reference v0.6.0 // indirect
@@ -78,7 +95,6 @@ require (
7895
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
7996
github.com/gobwas/glob v0.2.3 // indirect
8097
github.com/gogo/protobuf v1.3.2 // indirect
81-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
8298
github.com/hashicorp/golang-lru v1.0.2 // indirect
8399
github.com/inconshreveable/mousetrap v1.1.0 // indirect
84100
github.com/ipfs/bbloom v0.0.4 // indirect
@@ -99,7 +115,7 @@ require (
99115
github.com/ipld/go-car v0.6.2 // indirect
100116
github.com/ipld/go-codec-dagpb v1.6.0 // indirect
101117
github.com/ipni/go-libipni v0.6.18 // indirect
102-
github.com/klauspost/compress v1.18.2 // indirect
118+
github.com/klauspost/compress v1.18.5 // indirect
103119
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
104120
github.com/labstack/gommon v0.4.2 // indirect
105121
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
@@ -112,12 +128,11 @@ require (
112128
github.com/minio/sha256-simd v1.0.1 // indirect
113129
github.com/moby/docker-image-spec v1.3.1 // indirect
114130
github.com/moby/go-archive v0.2.0 // indirect
115-
github.com/moby/patternmatcher v0.6.0 // indirect
131+
github.com/moby/patternmatcher v0.6.1 // indirect
116132
github.com/moby/sys/sequential v0.6.0 // indirect
117133
github.com/moby/sys/user v0.4.0 // indirect
118134
github.com/moby/sys/userns v0.1.0 // indirect
119135
github.com/moby/term v0.5.2 // indirect
120-
github.com/morikuni/aec v1.0.0 // indirect
121136
github.com/mr-tron/base58 v1.2.0 // indirect
122137
github.com/multiformats/go-base32 v0.1.0 // indirect
123138
github.com/multiformats/go-base36 v0.2.0 // indirect
@@ -136,8 +151,8 @@ require (
136151
github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect
137152
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
138153
github.com/sagikazarmark/locafero v0.11.0 // indirect
139-
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
140-
github.com/sirupsen/logrus v1.9.3 // indirect
154+
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
155+
github.com/sirupsen/logrus v1.9.4 // indirect
141156
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
142157
github.com/spaolacci/murmur3 v1.1.0 // indirect
143158
github.com/spf13/afero v1.15.0 // indirect
@@ -153,24 +168,22 @@ require (
153168
github.com/yusufpapurcu/wmi v1.2.4 // indirect
154169
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
155170
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
156-
go.opentelemetry.io/otel v1.42.0 // indirect
157-
go.opentelemetry.io/otel/metric v1.42.0 // indirect
158-
go.opentelemetry.io/otel/trace v1.42.0 // indirect
171+
go.opentelemetry.io/otel v1.43.0 // indirect
172+
go.opentelemetry.io/otel/metric v1.43.0 // indirect
173+
go.opentelemetry.io/otel/trace v1.43.0 // indirect
159174
go.uber.org/atomic v1.11.0 // indirect
160175
go.uber.org/dig v1.19.0 // indirect
161176
go.uber.org/multierr v1.11.0 // indirect
162177
go.yaml.in/yaml/v3 v3.0.4 // indirect
163-
golang.org/x/crypto v0.48.0 // indirect
164-
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
165-
golang.org/x/net v0.51.0 // indirect
166-
golang.org/x/sync v0.20.0 // indirect
167-
golang.org/x/sys v0.41.0 // indirect
168-
golang.org/x/text v0.34.0 // indirect
178+
golang.org/x/crypto v0.49.0 // indirect
179+
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
180+
golang.org/x/net v0.52.0 // indirect
181+
golang.org/x/sys v0.42.0 // indirect
182+
golang.org/x/text v0.35.0 // indirect
169183
golang.org/x/time v0.14.0 // indirect
170184
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
171-
google.golang.org/genproto/googleapis/api v0.0.0-20260316172706-e463d84ca32d // indirect
172-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
173-
google.golang.org/grpc v1.79.2 // indirect
185+
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
186+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
174187
google.golang.org/protobuf v1.36.11 // indirect
175188
gopkg.in/yaml.v3 v3.0.1 // indirect
176189
lukechampine.com/blake3 v1.4.1 // indirect

0 commit comments

Comments
 (0)