You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current architecture conflates two distinct concepts under a single backend identifier:
The database engine and its driver (Postgres, MongoDB, Elasticsearch, …).
The index/query strategy you actually want to benchmark on that engine (native FTS, BM25 via pg_search, Atlas Search, GIN-on-tsvector, pg_trgm, pgvector, …).
This conflation forces three concrete problems:
1. Driver-level code duplication for non-driver reasons
backends/postgresfts/register.go and backends/paradedb/register.go are both "backends" today, but the postgres driver they use is identical. postgresfts literally points at postgres.New unchanged. paradedb wraps it only to attach two extra GUCs and a version query. The real difference between them is the index DDL in their dataset directories — not the driver. Registering them as separate backends exists solely so the loader can find different datasets/<dataset>/<backend>/pre.sql files.
2. Can't express "multiple workloads on the same backend"
You cannot today set up:
postgres with native FTS andpostgres with pg_trgm andpostgres with pgvector — in one benchmark — without inventing three fake backend names.
mongodb with Atlas Search vs. mongodb with native $text indexes.
Two ElasticSearch index strategies (e.g., lexical vs. kNN).
Every comparison axis must go through the "backend" identifier, which is the wrong slot.
3. Config metadata is keyed by the wrong thing
Default connection URI, container name, env var, and dashboard color are all attached to backend types via Go init() registration. That means adding a new index-strategy workload (a small SQL file change) requires a new Go package, a new registered backend type, and a new build. The cost of trying things is much higher than it should be.
Why the in-flight rename PR doesn't solve it
The current postgresfts → postgres rename PR fixes the naming lie (Postgres FTS isn't an extension, it's a core feature, so the backend should just be called postgres). But it doesn't change the underlying model — paradedb is still a separate registered backend, and you still can't express multiple workloads on one engine.
Solution
Separate the two concepts:
Backend stays a registered Go driver. One postgres backend, one mongodb backend, etc.
Workload becomes first-class: a schema/index/query strategy that runs on a specific backend. Workloads have their own manifest, container coupling, default connection, and dataset content.
Per-dataset workload.yaml is optional and shallow-merged on top of the global manifest. Use it only when one dataset needs to override a specific field (e.g., a different connection URL).
extras declares extension- or workload-specific metadata to capture into benchmark results, on top of the generic metadata the driver always collects (Postgres GUCs like shared_buffers, server version, etc.). For Postgres-based workloads, this is how extension-specific GUCs and version queries get recorded.
Each queries entry is a single SQL statement returning one row with one column. The column name (AS …) becomes the key in the captured config map. The Go-side postgres driver already supports custom config queries via SetExtraQueries (today hardcoded in backends/paradedb/register.go); under the workload model these become declarative manifest content instead of Go code. Adding pgvector capture later is a YAML change, not a code change.
backend: mongodbcontainer: mongodb-atlasconnection: mongodb://localhost:27017description: MongoDB with Atlas Search ($search operator)
Driver-level changes
backends/paradedb/ deleted. ParadeDB becomes a workload (workloads/paradedb.yaml) on the postgres backend. The GUCs and version query that this package hardcodes today move into the workload manifest's extras block.
backends/shared/postgres/driver.go already supports SetExtraGUCs / SetExtraQueries. Wire the workload manifest's extras through these existing hooks at driver construction.
backends/elasticsearch/ and backends/opensearch/ stay as separate backends. Their differences (VersionInfoField, TLS skip behavior) are genuine driver-level differences, not just dataset content.
Loader CLI changes (cmd/loader/main.go)
New --workload <name> flag. Picks the dataset subdir (datasets/<dataset>/<workload>/) and resolves connection/container/env-var from the manifest.
When --workload is omitted, defaults to the --backend value (preserves 1:1 ergonomics for simple cases like elasticsearch, mongodb).
Connection resolution order: --connection flag → manifest envVar → manifest connection field → error.
PR 2: introduce the workload concept, backward-compatible. --workload defaults to backend type when absent. Add manifest support, workload field in k6 config, plumb extras into the existing SetExtraGUCs / SetExtraQueries hooks. ParadeDB stays as a backend in this PR.
PR 3: migrate paradedb from backend to workload. Delete backends/paradedb/. Add workloads/paradedb.yaml (with the GUCs/queries in extras) and workloads/postgresfts.yaml. Update dataset directory naming as needed. Update docs.
Acceptance criteria
workloads/ directory exists with manifests for all current workloads.
loader load --workload paradedb ./datasets/sample reads the manifest and uses the right container/connection/dir.
k6 backend blocks accept a workload field; omitting it falls back to type.
backends/paradedb/ is removed; paradedb runs as a workload on the postgres backend.
The extras block in workloads/paradedb.yaml produces the same captured GUCs/queries that backends/paradedb/register.go produces today.
Per-dataset workload.yaml (when present) overrides the global manifest field-by-field.
Existing k6 scripts using backends.get("paradedb") continue to work unchanged.
PARADEDB_URL and POSTGRES_URL env vars continue to work unchanged.
Documentation (CONTRIBUTING, docs/datasets, docs/loader, docs/scripting) reflects the new model.
Out of scope
Adding new workloads beyond what exists today. Once the framework lands, things like postgres-pgvector, postgres-pgtrgm, mongodb-atlas vs mongodb-text, elasticsearch-knn become trivial follow-ups.
Problem
The current architecture conflates two distinct concepts under a single
backendidentifier:This conflation forces three concrete problems:
1. Driver-level code duplication for non-driver reasons
backends/postgresfts/register.goandbackends/paradedb/register.goare both "backends" today, but the postgres driver they use is identical.postgresftsliterally points atpostgres.Newunchanged.paradedbwraps it only to attach two extra GUCs and a version query. The real difference between them is the index DDL in their dataset directories — not the driver. Registering them as separate backends exists solely so the loader can find differentdatasets/<dataset>/<backend>/pre.sqlfiles.2. Can't express "multiple workloads on the same backend"
You cannot today set up:
postgreswith native FTS andpostgreswith pg_trgm andpostgreswith pgvector — in one benchmark — without inventing three fake backend names.mongodbwith Atlas Search vs.mongodbwith native$textindexes.Every comparison axis must go through the "backend" identifier, which is the wrong slot.
3. Config metadata is keyed by the wrong thing
Default connection URI, container name, env var, and dashboard color are all attached to backend types via Go
init()registration. That means adding a new index-strategy workload (a small SQL file change) requires a new Go package, a new registered backend type, and a new build. The cost of trying things is much higher than it should be.Why the in-flight rename PR doesn't solve it
The current
postgresfts → postgresrename PR fixes the naming lie (Postgres FTS isn't an extension, it's a core feature, so the backend should just be calledpostgres). But it doesn't change the underlying model —paradedbis still a separate registered backend, and you still can't express multiple workloads on one engine.Solution
Separate the two concepts:
postgresbackend, onemongodbbackend, etc.Directory layout
Per-dataset
workload.yamlis optional and shallow-merged on top of the global manifest. Use it only when one dataset needs to override a specific field (e.g., a different connection URL).Workload manifest schema
backendpostgres,mongodb, …)containerconnectionenvVarUPPER(workload_name) + "_URL"connectiondescriptioncolorextrasextrasshapeextrasdeclares extension- or workload-specific metadata to capture into benchmark results, on top of the generic metadata the driver always collects (Postgres GUCs likeshared_buffers, server version, etc.). For Postgres-based workloads, this is how extension-specific GUCs and version queries get recorded.Each
queriesentry is a single SQL statement returning one row with one column. The column name (AS …) becomes the key in the captured config map. The Go-side postgres driver already supports custom config queries viaSetExtraQueries(today hardcoded inbackends/paradedb/register.go); under the workload model these become declarative manifest content instead of Go code. Adding pgvector capture later is a YAML change, not a code change.Examples
workloads/paradedb.yaml:workloads/postgresfts.yaml:workloads/mongodb-atlas.yaml(future):Driver-level changes
backends/paradedb/deleted. ParadeDB becomes a workload (workloads/paradedb.yaml) on thepostgresbackend. The GUCs and version query that this package hardcodes today move into the workload manifest'sextrasblock.backends/shared/postgres/driver.goalready supportsSetExtraGUCs/SetExtraQueries. Wire the workload manifest'sextrasthrough these existing hooks at driver construction.backends/elasticsearch/andbackends/opensearch/stay as separate backends. Their differences (VersionInfoField, TLS skip behavior) are genuine driver-level differences, not just dataset content.Loader CLI changes (
cmd/loader/main.go)--workload <name>flag. Picks the dataset subdir (datasets/<dataset>/<workload>/) and resolves connection/container/env-var from the manifest.--workloadis omitted, defaults to the--backendvalue (preserves 1:1 ergonomics for simple cases likeelasticsearch,mongodb).--connectionflag → manifestenvVar→ manifestconnectionfield → error.k6 config changes (
backends.go)workloadbecomes a field in each backend block:When
workloadis omitted, defaults totype. At startup, the runtime loads the manifest and applies container/connection/extras to the driver.Dashboard changes
Color map keyed by alias (or workload), not backend type. Aliases default to workload names, so existing run JSON stays readable.
Migration
postgresftsbackend →postgresat the Go level. Independent of the workload model.--workloaddefaults to backend type when absent. Add manifest support,workloadfield in k6 config, plumbextrasinto the existingSetExtraGUCs/SetExtraQuerieshooks. ParadeDB stays as a backend in this PR.backends/paradedb/. Addworkloads/paradedb.yaml(with the GUCs/queries inextras) andworkloads/postgresfts.yaml. Update dataset directory naming as needed. Update docs.Acceptance criteria
workloads/directory exists with manifests for all current workloads.loader load --workload paradedb ./datasets/samplereads the manifest and uses the right container/connection/dir.workloadfield; omitting it falls back totype.backends/paradedb/is removed; paradedb runs as a workload on thepostgresbackend.extrasblock inworkloads/paradedb.yamlproduces the same captured GUCs/queries thatbackends/paradedb/register.goproduces today.workload.yaml(when present) overrides the global manifest field-by-field.backends.get("paradedb")continue to work unchanged.PARADEDB_URLandPOSTGRES_URLenv vars continue to work unchanged.Out of scope
Adding new workloads beyond what exists today. Once the framework lands, things like
postgres-pgvector,postgres-pgtrgm,mongodb-atlasvsmongodb-text,elasticsearch-knnbecome trivial follow-ups.