An in-memory database modelled on the SAP HANA column store. Stack and specification.
GGS3-DB keeps tables column by column in main memory and speaks the PostgreSQL protocol. Expressions are compiled to .NET code, analytical queries run in parallel on every core, and a commit becomes visible only after fsync.
In development
As of October 2026
Client
psql · Npgsql
PostgreSQL protocol
SCRAM-SHA-256 · extended query
SQL compiler
parser · expression trees
Column store in RAM
parallel on every core
Redo log
group commit · fsync
At a glance
The key figures of GGS3-DB, backed by the code.
PostgreSQL protocol
psql and Npgsql connect without a driver of their own
Protocol 3.0
Columns in RAM
Dictionary encoding for text, parallel scans on every core
Column store
Commit after fsync
Redo log with group commit, restart from snapshot and log
Durability
How a query runs
From connecting to the answer, for reads and for writes.
Connect
Connect
Every connection gets a thread of its own. Sign-in uses SCRAM-SHA-256, as with PostgreSQL.
Parse
Parse
A hand-written parser turns the SQL into a syntax tree, over the simple or the extended query protocol.
Compile
Compile
The binder checks types and rights and compiles expressions to .NET code through expression trees. Conditions move to the tables, equality joins become hash joins.
Read
Read
Readers work on an immutable state and never wait. Columns are scanned in blocks, in parallel on every core.
Write
Write
One transaction at a time appends to the columns. The commit goes into the redo log and becomes visible only after fsync; concurrent commits share one fsync.
Reply
Reply
Rows go back in text or binary format, as with PostgreSQL.
The stack
One .NET application with no packages beyond the base library, in six parts.
Protocol
PostgreSQL protocol 3.0 with simple and extended query, prepared statements and COPY in text, csv and binary.
SQL compiler
Hand-written parser and binder; expressions are compiled to delegates through System.Linq.Expressions.
Execution
Parallel scans in blocks, hash joins, hash-partitioned GROUP BY and a heap for top-N.
Column store
One array per column, dictionary encoding for text, min/max per block and sorted hash runs for keys.
Durability
Redo log with CRC32C frames and group commit, snapshots at checkpoint, recovery from snapshot and log.
Platform
C# on .NET 10 with server GC; as a container on a chiseled image without a shell.
Measured
A comparison with PostgreSQL 18 using pgbench, and a load test with 50 million rows. The figures come from the repository; raw data is not published.
Conditions
- Docker Desktop on an M5 Max (18 cores, 64 GB), 8 CPUs and 16 GB per server, fsync and synchronous_commit on.
- 2 million orders and 100,000 customers; pgbench with prepared statements from postgres:18-alpine, in the server's network.
- PostgreSQL 18 tuned (shared_buffers 4 GB, work_mem 64 MB, parallel workers), VACUUM ANALYZE after loading.
- Both servers alternately in two rounds; the better value counts.
Caveats
- Values vary between runs by 10 to 40 %.
- For top-N the data is in insert order, which favours GGS3-DB; PostgreSQL has no index on created.
- Figures are averages, not percentiles.
- With many clients writing at the same time, PostgreSQL is ahead.
Comparison with PostgreSQL 18
Transactions per second, GGS3-DB / PostgreSQL (factor)
| Workload | 1 client | 16 clients | 64 clients |
|---|---|---|---|
| WHERE id = $1 | 18,379 / 23,943 (0.77×) | 105,167 / 140,817 (0.75×) | 113,463 / 119,121 (0.95×) |
| count(*) WHERE customer_id = $1 | 1,128 / 64 (18×) | 1,495 / 135 (11×) | 1,474 / 145 (10×) |
| GROUP BY status | 111 / 22 (5.0×) | 107 / 42 (2.6×) | 109 / 42 (2.6×) |
| GROUP BY customer_id (100,000 groups) | 24 / 2.5 (9.8×) | 32 / 7.6 (4.2×) | 30 / 7.1 (4.2×) |
| JOIN + GROUP BY country | 54 / 11 (4.7×) | 35 / 14 (2.5×) | 27 / 12 (2.3×) |
| ORDER BY created DESC LIMIT 20 | 386 / 22 (17×) | 1,817 / 44 (41×) | 1,578 / 44 (36×) |
| INSERT, one commit per row | 9,908 / 9,658 (1.03×) | 45,894 / 44,622 (1.03×) | 63,340 / 87,495 (0.72×) |
| UPDATE … WHERE id = $1 | 9,868 / 8,676 (1.14×) | 47,276 / 41,116 (1.15×) | 78,051 / 83,525 (0.93×) |
| 80 % reads, 20 % updates | 13,484 / 20,438 (0.66×) | 71,063 / 80,519 (0.88×) | 113,299 / 137,679 (0.82×) |
Load test with 50 million rows
In process on an M5 Max (18 cores, 64 GB).
- COPY binary, 50 M rows
- 11.3 s (4.4 M rows/s)
- GROUP BY over 1000 groups
- 0.3 s
- GROUP BY over a text column
- 0.3–0.6 s
- filtered count
- 0.05 s
- WHERE id = k, first access (builds the key runs)
- 0.4 s
- JOIN with a 1000-row table plus GROUP BY
- 0.2–0.4 s
- ORDER BY … LIMIT 10
- 0.3–0.4 s
- UPDATE of 5 M rows
- 0.6 s
- Restart (1.1 GB snapshot, index rebuilt)
- 1.7–1.9 s
Columns in memory.
The PostgreSQL protocol, no driver of its own.
Visible only after fsync.
Specification
Storage
- Column store
- one array per column; writes only append
- Text
- dictionary encoding, each row stores just an int code
- Further compression
- none
- UPDATE
- marks the old row deleted and appends the new version; the checkpoint removes deleted rows
- Keys
- PRIMARY KEY and UNIQUE; a key lookup goes through sorted hash runs of 16,384 rows each
- Min/max per block
- per 65,536 rows, for ORDER BY … LIMIT
- Secondary indexes
- not supported
- Data volume
- everything in main memory, at most about 2.1 billion rows per table
Data types
- Numbers
- smallint, integer, bigint, real, double precision, numeric(p,s) up to 28 digits
- Text
- text, varchar(n), UTF-8
- Time
- date, timestamp, timestamptz (UTC)
- Booleans
- boolean
- Not supported
- uuid, bytea, json, interval, arrays, SERIAL
SQL
- Tables
- CREATE TABLE with NOT NULL, PRIMARY KEY and UNIQUE; DROP TABLE
- Writes
- INSERT … VALUES | SELECT, UPDATE, DELETE, COPY … FROM STDIN in text, csv and binary
- Queries
- WHERE, INNER and LEFT JOIN, GROUP BY, HAVING, DISTINCT, ORDER BY, LIMIT/OFFSET
- Aggregates
- count, sum, avg, min, max, including count(DISTINCT x)
- Expressions
- CASE, CAST, IN, BETWEEN, LIKE/ILIKE, coalesce, date_trunc, extract and more functions
- Roles and rights
- CREATE, ALTER and DROP ROLE, GRANT and REVOKE per table, owners
- Not supported
- subqueries, CTEs, UNION, window functions, RIGHT/FULL JOIN, ALTER TABLE, views, foreign keys, RETURNING, ON CONFLICT, SAVEPOINT
Transactions
- Model
- immutable versions (copy-on-write) and one write lock for the whole database
- Readers
- work on a fixed state and never wait
- Writers
- one transaction at a time; others wait up to 30 seconds for the lock
- Isolation
- READ COMMITTED; REPEATABLE READ and SERIALIZABLE as a snapshot, and a write attempt aborts once any commit happened since the snapshot
- Locks per table
- instead of one lock for the whole databasePlanned
Durability
- Redo log
- frames with a CRC32C checksum, the whole transaction per commit
- Commit
- visible only after fsync; concurrent commits share one fsync (group commit)
- Checkpoint
- automatic from 1 GiB of log or via CHECKPOINT; writes a snapshot without deleted rows
- Restart
- newest snapshot plus log; an unfinished transaction at the end is dropped
- Tested
- a log cut at every byte offset recovers exactly the committed state
Protocol and clients
- Protocol
- PostgreSQL protocol 3.0, reports itself as version 16.0
- Queries
- simple and extended query, prepared statements, text and binary format
- Plan cache
- for prepared INSERT, UPDATE and DELETE
- Plan cache for SELECT
- today SELECT is compiled again on every executionPlanned
- COPY
- FROM STDIN in text, csv and binary
- Tested clients
- Npgsql (with Server Compatibility Mode=NoTypeLoading), psql and pgbench
- Not supported
- COPY TO, cancelling running queries, LISTEN/NOTIFY, the system catalog (\d, DBeaver, pgAdmin)
Security
- Sign-in
- SCRAM-SHA-256
- Rights
- roles, memberships, table privileges including PUBLIC
- Root password
- from an environment variable or a file, such as a Docker secret
- Without a password
- the server accepts connections over loopback only
- Container
- chiseled image without a shell, does not run as root
- Connection encryption
- not supported (no TLS)
Operations
- Start
- a .NET application, from the command line or with Docker Compose
- Settings
- port, address, data directory and maximum number of connections
- Connections
- one thread per connection, up to 100 by default
- Health check
- built into the Docker image, green once recovery is done
- Metrics
- none, only messages on stdout and stderr
- Backup
- no pg_dump and no documented backup procedure
Limits
- A good fit
- data that fits in main memory and is mostly analysed: filters without an index, GROUP BY, JOIN, top-N
- Better with PostgreSQL
- many clients writing at once, data larger than main memory, full SQL, TLS, replication, pg_dump
Licence
- Licence
- Apache 2.0
- Dependencies
- no packages in the server beyond the .NET base library
- Tests
- xunit.v3 and Npgsql
Status
- Built
- column store, SQL subset, transactions, redo log with recovery, roles and rights, PostgreSQL protocol with COPY, Docker image
- Tests
- 58 automated tests in 9 areas, among them persistence, transactions, protocol and rights
- In progress
- event loops instead of one thread per connection, a hash index for keys and further OLTP optimisations
- Planned
- plan cache for SELECT, locks per table
Questions about GGS3-DB
Answered briefly; the details are in the specification.
Also on GGS Network
The other pages, in short.
Remote desktop
Stack and specification of the remote desktop built on Moonlight and Sunshine.
Team
Who designs, builds and runs GGS Network.
Status
Whether the website, API and services are reachable right now, measured continuously.