Skip to content

Repository files navigation

GreptimeDB Development Environment

A Nix flake for running a local GreptimeDB cluster with process-compose.

Quick Start

All processes are disabled by default. Start what you need:

nix develop

Garage S3 Storage Only

process-compose up garage

GreptimeDB Standalone

process-compose up standalone
Protocol Address
HTTP http://127.0.0.1:11040
gRPC 127.0.0.1:11041
MySQL 127.0.0.1:11042
PostgreSQL 127.0.0.1:11043

These are the client-facing ports, shared by standalone, standalone-fs, and the distributed cluster's haproxy — they are never run at the same time, so the same client code works unchanged across every mode.

GreptimeDB Standalone (Local File Backend)

process-compose up standalone-fs

Single-node GreptimeDB using local disk instead of Garage S3. No garage/etcd dependency — fastest mode to start. Data lives under .greptimedb/standalone-fs/. Same connection details as standalone above.

Enterprise Active/Standby Standalone

process-compose up haproxy-standby

Two enterprise standalone instances form an active/standby pair sharing Garage S3 (main data store) and a Postgres table (shared metadata + leader election); each keeps its own dedicated WAL. Only the elected leader accepts writes; the follower rejects writes and serves read-refreshed queries. haproxy-standby routes the client ports to whichever node is currently leader.

  • Requires an enterprise greptime binary provided in place as ./greptime (or via GREPTIME_BIN) — the same path every other mode uses. The OSS binary cannot run this mode.
  • Election backend is Postgres (the shared postgres process on port 11080) — the enterprise active/standby election is built on the external RDS metadata store.
  • Clients use the same ports 11040-11043 as every other mode; traffic always reaches the active leader.
Protocol Address
HTTP http://127.0.0.1:11040
gRPC 127.0.0.1:11041
MySQL 127.0.0.1:11042
PostgreSQL 127.0.0.1:11043

Test failover with process-compose process stop standby-a (stop the leader); standby-b is elected and haproxy reroutes automatically. Inspect roles via curl http://127.0.0.1:11070/status/standalone/role (and :11074).

GreptimeDB Distributed Cluster

process-compose up haproxy

Clients connect to haproxy on the same ports as standalone (11040-11043) — haproxy load-balances the internal frontend instance(s), which are not exposed directly. So client code written for standalone works here unchanged.

Protocol Address
HTTP http://127.0.0.1:11040
gRPC 127.0.0.1:11041
MySQL 127.0.0.1:11042
PostgreSQL 127.0.0.1:11043

Place a greptime binary in the project root before starting. Process-compose runs on port 11099.

testbedctl

A utility script for common operations against the running cluster:

./testbedctl psql                           # PostgreSQL CLI
./testbedctl mysql                          # MySQL CLI
./testbedctl s3 ls                          # List S3 buckets
./testbedctl s3 ls s3://test-bucket/        # List objects in bucket
./testbedctl s3 ls s3://test-bucket/ --recursive  # List all objects
./testbedctl telemetrygen                   # Ingest OTel traces
./testbedctl telemetrygen down              # Stop trace ingestion
./testbedctl telemetrygen metrics up        # Ingest continuous OTel metrics (gauge/sum/histogram)
./testbedctl telemetrygen metrics down      # Stop metrics ingestion
./testbedctl metrics-partition              # Partition greptime_physical_table into 4 ranges on 'timebox'
./testbedctl flush <table>                  # Flush a table's memtable (admin flush_table)
./testbedctl compact <table> [type] [opts]  # Trigger compaction (admin compact_table); optional twcs/swcs + parallelism=N
./testbedctl gc <table> [--full]            # Trigger garbage collection (admin gc_table); --full = full file listing
./testbedctl gc-regions <id>... [--full]    # Trigger GC for specific regions (admin gc_regions); region ids are u64
./testbedctl purge <table> [-y]             # Permanently purge a DROPPED table's data (admin purge_table); -y skips prompt
./testbedctl clean                          # Remove .greptimedb
./testbedctl duckdb                         # DuckDB shell with the Iceberg REST catalog attached
./testbedctl pyiceberg [table] [-local]     # Query Iceberg tables via pyiceberg (-local = standalone-fs)
./testbedctl spark                          # Apache Spark SQL (local mode) on the Iceberg REST catalog
./testbedctl spark demo                     #   create a TIMESTAMP(6) table + run sample queries
./testbedctl spark -e "SELECT ..."          #   one-shot Spark SQL query
./testbedctl trino                           # Trino SQL on the Iceberg REST catalog (interactive)
./testbedctl trino demo                      #   create a TIMESTAMP(6) table + run sample queries
./testbedctl trino --execute "SELECT ..."    #   one-shot Trino SQL query
./testbedctl trino stop                      #   stop the Trino server container

Reading Iceberg data with Spark

testbedctl spark runs Apache Spark 4.1.3 + Iceberg 1.11.0 in local mode (single JVM, no master/worker) against GreptimeDB's Iceberg REST catalog (catalog name greptime, via Garage S3). On first run it pulls the Spark image and the Iceberg/AWS jars automatically (cached in spark/jars/, gitignored).

Two modes — interactive and predefined:

./testbedctl spark            # interactive spark-sql shell (Ctrl+D to exit)
./testbedctl spark demo       # create a TIMESTAMP(6) demo table + sample-query battery
./testbedctl spark test       # automated TIMESTAMP(6) compatibility suite (PASS/FAIL, exit 1 on failure)
./testbedctl spark -e "SQL"   # one-shot query
./testbedctl spark -f q.sql   # run a SQL script

Requirements & caveats (see spark/README if present, and issue.md):

  • Cluster must be up (process-compose up standalone or up haproxy) and the table flushed so Iceberg manifests exist.
  • Declare time-index columns as TIMESTAMP(6) so the parquet is microseconds (matching the Iceberg timestamptz schema); plain TIMESTAMP writes milliseconds and breaks time-range predicate pushdown.
  • Keep the default (vectorized) reader — GreptimeDB writes an internal __sequence UINT_64 column that breaks the non-vectorized reader on every table.
  • Unsupported GreptimeDB types (fail when projected): unsigned ints (* UNSIGNED) and DECIMAL. Everything else — bool, signed ints, float, double, string, binary, date, TIMESTAMP(6) — works for SELECT / WHERE / GROUP BY / ORDER BY / aggregations / joins / windows.

Spark compatibility suite (spark test)

./testbedctl spark test (source: scripts/spark-compat-test) is an automated, self-verifying regression suite: it builds a TIMESTAMP(6) fixture covering every Spark-supported type plus µs edge cases (epoch, 1900, 2262-04-11 max, .000001/.999999, adjacent-µs rows, NULLs, numeric extremes), a join table, and one table per known-bad type — then runs ~80 assertions with exact expected output (precision round-trips, time predicates, µs-granularity predicate pushdown (EXPLAIN), date/time functions, windows, joins, aggregations, NULL semantics, binary/date columns, casts, incremental append visibility, Iceberg .snapshots metadata, and time travel). It drops and recreates its own spark_ts6_* / spark_bad_* tables, so it is safe to re-run any time; use it after swapping in a new greptime binary. Raw Spark output lands in .spark/compat/{battery,append}.out for debugging.

Verified against GreptimeDB 26.05.2.0 — 78/78 checks pass. Known issues it asserts as current behavior (fail loudly if they change):

Issue Behavior
WHERE on an unsigned column silently returns wrong results (0 rows; predicate answered from manifest stats) — never filter on unsigned columns
TIMESTAMP(9) columns no longer crash: read back shifted 1000× (ns parquet read as µs), e.g. 2024-01-01 00:00:00.123456789+55969-09-28 00:02:03.456789
Iceberg snapshot history only the current snapshot is retained — each flush expires previous snapshots, so VERSION AS OF <older> fails (Cannot find snapshot with ID); time travel to history is unavailable

Also note: good columns on a table that also has an unsigned/decimal column remain readable (SELECT ts, count(*) work) — only projecting the bad column fails.

Reading Iceberg data with Trino

testbedctl trino runs Trino 471 (server on port 18080, single node) with its Iceberg connector pointed at the same REST catalog.

./testbedctl trino                          # interactive shell (trino CLI)
./testbedctl trino demo                     # demo table + sample-query battery
./testbedctl trino --execute "SELECT ..."   # one-shot query
./testbedctl trino stop                     # stop the server container

Trino caveat (fixed in GreptimeDB 26.05.2.0, 2026-08-15 build) — earlier builds wrote manifest split_offsets as row-group start offsets, making Trino silently return 0 rows; the fix landed and no rebuild is needed. Two remaining operational notes: use ts < CAST(TIMESTAMP '…' AS timestamp(6) WITH TIME ZONE) for time-predicate pushdown (tz-less literals don't prune), and testbedctl trino restart after testbedctl clean (regenerated S3 keys).

Trino type matrix (after rebuild): all Spark-supported types work the same; unsigned ints read without error but wrap (u32=4294967295 → -1, u64 max → -1) — better than Spark's hard error, but silently wrong above INT32/INT64 max; DECIMAL and TIMESTAMP(9) (nanos) fail with hard errors (same schema/physical mismatches as Spark).

Start Extra Processes

process-compose process start metasrv-1
process-compose process start frontend-1
process-compose process start flownode

Cleanup

process-compose down
./testbedctl clean

About

A nix flake environment for greptimedb development and local testing

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages