In-Memory-Datenbank nach dem Vorbild der SAP-HANA-Spaltenablage. Stack und Spezifikation.

GGS3-DB hält Tabellen spaltenweise im Arbeitsspeicher und spricht das PostgreSQL-Protokoll. Ausdrücke werden zu .NET-Code kompiliert, Auswertungen laufen parallel auf allen Kernen, und ein Commit ist erst nach fsync sichtbar.

In Entwicklung

Stand: Oktober 2026

Der Weg einer Abfrage

Client

psql · Npgsql

PostgreSQL-Protokoll

SCRAM-SHA-256 · Extended Query

SQL-Compiler

Parser · Ausdrucksbäume

Spaltenablage im RAM

parallel auf allen Kernen

Redo-Log

Group Commit · fsync

Auf einen Blick

Die Eckwerte von GGS3-DB, belegt durch den Code.

  • PostgreSQL-Protokoll

    psql und Npgsql verbinden sich ohne eigenen Treiber

    Protokoll 3.0

  • Spalten im RAM

    Wörterbuch-Kodierung für Text, parallele Scans auf allen Kernen

    Spaltenablage

  • Commit nach fsync

    Redo-Log mit Group Commit, Neustart aus Snapshot und Log

    Dauerhaftigkeit

So läuft eine Abfrage

Vom Verbindungsaufbau bis zur Antwort, lesend und schreibend.

  1. Verbinden

    Jede Verbindung bekommt einen eigenen Thread. Die Anmeldung läuft über SCRAM-SHA-256, wie bei PostgreSQL.

  2. Parsen

    Ein handgeschriebener Parser macht aus dem SQL einen Syntaxbaum, im Simple- oder Extended-Query-Protokoll.

  3. Kompilieren

    Der Binder prüft Typen und Rechte und kompiliert Ausdrücke über Ausdrucksbäume zu .NET-Code. Bedingungen wandern zu den Tabellen, Gleichheits-Joins werden Hash-Joins.

  4. Lesen

    Leser arbeiten auf einem unveränderlichen Stand und warten nie. Die Spalten werden in Blöcken parallel auf allen Kernen durchsucht.

  5. Schreiben

    Eine Transaktion zur Zeit hängt an die Spalten an. Der Commit landet im Redo-Log und ist erst nach fsync sichtbar; gleichzeitige Commits teilen sich ein fsync.

  6. Antworten

    Die Zeilen gehen im Text- oder Binärformat zurück, wie bei PostgreSQL.

Der Stack

Eine .NET-Anwendung ohne Pakete außerhalb der Basisbibliothek, in sechs Teilen.

  • Protokoll

    PostgreSQL-Protokoll 3.0 mit Simple und Extended Query, vorbereiteten Anweisungen und COPY in text, csv und binary.

  • SQL-Compiler

    Handgeschriebener Parser und Binder; Ausdrücke werden über System.Linq.Expressions zu Delegates kompiliert.

  • Ausführung

    Parallele Scans in Blöcken, Hash-Joins, nach Hash partitioniertes GROUP BY und ein Heap für Top-N.

  • Spaltenablage

    Ein Array je Spalte, Wörterbuch-Kodierung für Text, Min/Max je Block und sortierte Hash-Läufe für Schlüssel.

  • Dauerhaftigkeit

    Redo-Log mit CRC32C-Rahmen und Group Commit, Snapshots beim Checkpoint, Recovery aus Snapshot und Log.

  • Plattform

    C# auf .NET 10 mit Server-GC; als Container auf einem Chiseled-Image ohne Shell.

Gemessen

Ein Vergleich mit PostgreSQL 18 per pgbench und ein Lasttest mit 50 Millionen Zeilen. Die Zahlen stammen aus dem Repository; Rohdaten sind nicht veröffentlicht.

Messbedingungen

  • Docker Desktop auf einem M5 Max (18 Kerne, 64 GB), je Server 8 CPUs und 16 GB, fsync und synchronous_commit an.
  • 2 Millionen Bestellungen und 100 000 Kunden; pgbench mit vorbereiteten Anweisungen aus postgres:18-alpine, im Netzwerk des Servers.
  • PostgreSQL 18 getunt (shared_buffers 4 GB, work_mem 64 MB, parallele Worker), nach dem Laden VACUUM ANALYZE.
  • Beide Server abwechselnd in zwei Runden, der bessere Wert zählt.

Vorbehalte

  • Die Werte schwanken zwischen Läufen um 10 bis 40 %.
  • Für Top-N liegen die Daten in Einfügereihenfolge, was GGS3-DB entgegenkommt; PostgreSQL hat keinen Index auf created.
  • Angegeben sind Mittelwerte, keine Perzentile.
  • Bei vielen gleichzeitig schreibenden Clients liegt PostgreSQL vorn.

Vergleich mit PostgreSQL 18

Transaktionen pro Sekunde, GGS3-DB / PostgreSQL (Faktor)

Workload1 Client16 Clients64 Clients
WHERE id = $118 379 / 23 943 (0,77×)105 167 / 140 817 (0,75×)113 463 / 119 121 (0,95×)
count(*) WHERE customer_id = $11 128 / 64 (18×)1 495 / 135 (11×)1 474 / 145 (10×)
GROUP BY status111 / 22 (5,0×)107 / 42 (2,6×)109 / 42 (2,6×)
GROUP BY customer_id (100 000 Gruppen)24 / 2,5 (9,8×)32 / 7,6 (4,2×)30 / 7,1 (4,2×)
JOIN + GROUP BY country54 / 11 (4,7×)35 / 14 (2,5×)27 / 12 (2,3×)
ORDER BY created DESC LIMIT 20386 / 22 (17×)1 817 / 44 (41×)1 578 / 44 (36×)
INSERT, ein Commit pro Zeile9 908 / 9 658 (1,03×)45 894 / 44 622 (1,03×)63 340 / 87 495 (0,72×)
UPDATE … WHERE id = $19 868 / 8 676 (1,14×)47 276 / 41 116 (1,15×)78 051 / 83 525 (0,93×)
80 % Lesen, 20 % Update13 484 / 20 438 (0,66×)71 063 / 80 519 (0,88×)113 299 / 137 679 (0,82×)

Lasttest mit 50 Millionen Zeilen

Im Prozess auf einem M5 Max (18 Kerne, 64 GB).

COPY binary, 50 M Zeilen
11,3 s (4,4 M Zeilen/s)
GROUP BY über 1000 Gruppen
0,3 s
GROUP BY über eine Textspalte
0,3–0,6 s
gefilterter count
0,05 s
WHERE id = k, erster Zugriff (baut die Schlüssel-Läufe)
0,4 s
JOIN mit 1000-Zeilen-Tabelle plus GROUP BY
0,2–0,4 s
ORDER BY … LIMIT 10
0,3–0,4 s
UPDATE von 5 M Zeilen
0,6 s
Neustart (1,1 GB Snapshot, Index neu aufbauen)
1,7–1,9 s

Spalten im Arbeitsspeicher.

PostgreSQL-Protokoll ohne eigenen Treiber.

Sichtbar erst nach fsync.

Spezifikation

Speicher

Spaltenablage
ein Array je Spalte, Schreiben hängt nur an
Text
Wörterbuch-Kodierung, jede Zeile speichert nur einen int-Code
Weitere Kompression
keine
UPDATE
markiert die alte Zeile als gelöscht und hängt die neue Version an; der Checkpoint entfernt gelöschte Zeilen
Schlüssel
PRIMARY KEY und UNIQUE; die Suche nach einem Schlüssel läuft über sortierte Hash-Läufe je 16 384 Zeilen
Min/Max je Block
je 65 536 Zeilen, für ORDER BY … LIMIT
Sekundärindizes
nicht unterstützt
Datenmenge
alles im Arbeitsspeicher, höchstens rund 2,1 Milliarden Zeilen je Tabelle

Datentypen

Zahlen
smallint, integer, bigint, real, double precision, numeric(p,s) bis 28 Stellen
Text
text, varchar(n), UTF-8
Zeit
date, timestamp, timestamptz (UTC)
Wahrheitswerte
boolean
Nicht unterstützt
uuid, bytea, json, interval, Arrays, SERIAL

SQL

Tabellen
CREATE TABLE mit NOT NULL, PRIMARY KEY und UNIQUE; DROP TABLE
Schreiben
INSERT … VALUES | SELECT, UPDATE, DELETE, COPY … FROM STDIN in text, csv und binary
Abfragen
WHERE, INNER und LEFT JOIN, GROUP BY, HAVING, DISTINCT, ORDER BY, LIMIT/OFFSET
Aggregate
count, sum, avg, min, max, auch count(DISTINCT x)
Ausdrücke
CASE, CAST, IN, BETWEEN, LIKE/ILIKE, coalesce, date_trunc, extract und weitere Funktionen
Rollen und Rechte
CREATE, ALTER und DROP ROLE, GRANT und REVOKE je Tabelle, Besitzer
Nicht unterstützt
Unterabfragen, CTEs, UNION, Fensterfunktionen, RIGHT/FULL JOIN, ALTER TABLE, Views, Fremdschlüssel, RETURNING, ON CONFLICT, SAVEPOINT

Transaktionen

Modell
unveränderliche Versionen (Copy-on-Write) und eine Schreibsperre für die ganze Datenbank
Leser
arbeiten auf einem festen Stand und warten nie
Schreiber
eine Transaktion zur Zeit, andere warten bis zu 30 Sekunden auf die Sperre
Isolation
READ COMMITTED; REPEATABLE READ und SERIALIZABLE als Snapshot, ein Schreibversuch bricht ab, sobald seit dem Snapshot irgendein Commit kam
Sperren je Tabelle
statt einer Sperre für die ganze DatenbankGeplant

Dauerhaftigkeit

Redo-Log
Rahmen mit CRC32C-Prüfsumme, je Commit die ganze Transaktion
Commit
erst nach fsync sichtbar; gleichzeitige Commits teilen sich ein fsync (Group Commit)
Checkpoint
automatisch ab 1 GiB Log oder per CHECKPOINT; schreibt einen Snapshot ohne gelöschte Zeilen
Neustart
neuester Snapshot plus Log; eine unvollständige Transaktion am Ende wird verworfen
Geprüft
ein an jeder Byte-Position abgeschnittenes Log führt genau zum bestätigten Stand zurück

Protokoll und Clients

Protokoll
PostgreSQL-Protokoll 3.0, meldet sich als Version 16.0
Abfragen
Simple und Extended Query, vorbereitete Anweisungen, Text- und Binärformat
Plan-Cache
für vorbereitete INSERT, UPDATE und DELETE
Plan-Cache für SELECT
SELECT wird heute bei jeder Ausführung neu kompiliertGeplant
COPY
FROM STDIN in text, csv und binary
Getestete Clients
Npgsql (mit Server Compatibility Mode=NoTypeLoading), psql und pgbench
Nicht unterstützt
COPY TO, Abbrechen laufender Abfragen, LISTEN/NOTIFY, Systemkatalog (\d, DBeaver, pgAdmin)

Sicherheit

Anmeldung
SCRAM-SHA-256
Rechte
Rollen, Mitgliedschaften, Tabellenrechte auch für PUBLIC
Root-Passwort
aus einer Umgebungsvariable oder einer Datei, etwa einem Docker-Secret
Ohne Passwort
nimmt der Server nur Verbindungen über Loopback an
Container
Chiseled-Image ohne Shell, läuft nicht als root
Verschlüsselung der Verbindung
nicht unterstützt (kein TLS)

Betrieb

Start
eine .NET-Anwendung, per Kommandozeile oder Docker Compose
Einstellungen
Port, Adresse, Datenverzeichnis und Höchstzahl der Verbindungen
Verbindungen
ein Thread je Verbindung, standardmäßig bis zu 100
Healthcheck
im Docker-Image eingebaut, grün sobald die Recovery durch ist
Metriken
keine, nur Meldungen auf stdout und stderr
Backup
kein pg_dump und kein dokumentiertes Backup-Verfahren

Grenzen

Passt gut
Daten, die in den Arbeitsspeicher passen und vor allem ausgewertet werden: Filter ohne Index, GROUP BY, JOIN, Top-N
Besser mit PostgreSQL
viele gleichzeitig schreibende Clients, Daten größer als der Arbeitsspeicher, voller SQL-Umfang, TLS, Replikation, pg_dump

Lizenz

Lizenz
Apache 2.0
Abhängigkeiten
im Server keine Pakete außerhalb der .NET-Basisbibliothek
Tests
xunit.v3 und Npgsql

Stand

Umgesetzt
Spaltenablage, SQL-Teilmenge, Transaktionen, Redo-Log mit Recovery, Rollen und Rechte, PostgreSQL-Protokoll mit COPY, Docker-Image
Tests
58 automatisierte Tests in 9 Bereichen, darunter Persistenz, Transaktionen, Protokoll und Rechte
In Arbeit
Ereignisschleifen statt eines Threads je Verbindung, ein Hash-Index für Schlüssel und weitere OLTP-Optimierungen
Geplant
Plan-Cache für SELECT, Sperren je Tabelle

Häufige Fragen zu GGS3-DB

Kurz beantwortet, die Details stehen in der Spezifikation.

Ebenfalls auf GGS Network

Die übrigen Seiten, kurz erklärt.

  • Remote-Desktop

    Stack und Spezifikation des Remote-Desktops auf Basis von Moonlight und Sunshine.

  • Team

    Wer GGS Network entwirft, baut und betreibt.

  • Status

    Ob Website, API und Dienste gerade erreichbar sind, laufend gemessen.