Skip to content

Add openGauss (row store and column store) - #1725

Open
alexey-milovidov wants to merge 3 commits into
mainfrom
add-opengauss
Open

Add openGauss (row store and column store)#1725
alexey-milovidov wants to merge 3 commits into
mainfrom
add-opengauss

Conversation

@alexey-milovidov

Copy link
Copy Markdown
Member

openGauss is Huawei's open-source (Mulan PSL v2) DBMS: a PostgreSQL 9.2.4 fork — the server still reports 9.2.4 — rewritten in C++, with a thread-per-session model, a vectorized executor and a column store alongside the row engine.

Two entries, because those two storage engines behave nothing alike on this workload:

  • opengauss — the row store a plain CREATE TABLE gives you.
  • opengauss-column — the same setup with ORIENTATION = COLUMN, which is what the vendor documentation prescribes for OLAP.

Installation

Native, no container. openGauss publishes binaries for openEuler and CentOS only, but the 6.0.5 LTS openEuler 20.03 server tarball (x86_64 and aarch64) is glibc 2.28 and runs unmodified on Ubuntu once two sonames are bridged in a private directory on LD_LIBRARY_PATH: libaio.so.1, which Ubuntu renamed to libaio.so.1t64 in a transition that is an ABI no-op on 64-bit, and libreadline.so.7, which only gsql links and only for line editing. Everything else non-glibc is bundled in the tarball. Docker images exist only for the 7.0.0-RC line, not for 6.0.x LTS.

The server refuses to run as root, so install creates the conventional omm user and the other scripts go through a wrapper that sources the openGauss environment. Since omm cannot traverse root's home on the benchmark VMs, create.sql and hits.tsv reach gsql on stdin rather than by path.

Three things a naive entry gets wrong

  1. DBCOMPATIBILITY = 'PG' is not optional. openGauss defaults to 'A' (Oracle) compatibility, where the empty string is NULL, and the load stops on the first row of hits.tsv with null value in column "referer" violates not-null constraint. Had the schema not been NOT NULL throughout the damage would have been silent instead: 94.4% of MobilePhoneModel, 86.8% of SearchPhrase, 19.0% of Referer and 14.9% of Title are empty strings in this dataset, and the 16 queries that filter on <> '' would have matched nothing.
  2. query_dop and max_connections are coupled. openGauss runs a query on one thread unless query_dop is raised above its default of 1 — it is worth 9x on Q34 at 100M rows — but every worker thread then takes a connection slot, so the driver's 10-connection throughput test exhausts the default max_connections = 200 with No free proc is available to create a new connection. install sizes both from the thread count, and max_process_memory (a hard per-instance ceiling that defaults to 12 GB regardless of machine, which would hold a c7a.metal-48xl to a twelfth of its RAM) from MemTotal.
  3. gs_ctl start does not detach. It leaves the server in the caller's process group, where anything that later signals that group takes the database down; ./start runs it under setsid.

Verification

Both entries were run end to end on the full dataset:

opengauss opengauss-column
rows loaded 99,997,497 99,997,497
./data-size 86.6 GB 31.3 GB (16 GB table)
load 2h22m 1h20m
43 queries all answered, no errors or timeouts all answered, no errors or timeouts

(Load times are from a busy shared machine and are not meant as results.)

All 43 queries are the postgresql entry's, unmodified — no rewrites, no substituted functions. They were compared query by query against clickhouse-local on a 1% slice, agreeing everywhere except where a LIMIT cuts through a run of tied sort keys and on AVG(UserID), where openGauss accumulates in numeric and is the one that is exactly right. The two openGauss engines were then compared against each other on the full 100M rows: 34 of 43 identical, the other 9 the same tie-breaking cases.

No results files — those need runs on the benchmark's own EC2 machines.

Two openGauss defects found on the way

Neither changes what the benchmark measures; both are written up in opengauss-column/README.md.

  • Q35 differs from Q34 only by a constant in the GROUP BY list and runs 12x slower. EXPLAIN VERBOSE gives the reason in one line — the parallel redistribution is keyed on the leading grouping column, so Q35 gets Distribute Key: (1) and hashes all 100M rows onto a single worker. query_dop = 48 makes Q35 slower than query_dop = 1; moving the constant to the end of the list restores Distribute Key: url and the runtime. The query is left exactly as ClickBench specifies it.
  • An aggregate FILTER clause over a column-store table kills the whole gaussdb instance — no log entry, no core, redo recovery on next start. 1000 rows reproduce it and the same statement on a row-store table is fine. No ClickBench query uses FILTER.

Both are unfiled: the openGauss tracker is on Gitee.

🤖 Generated with Claude Code

openGauss is Huawei's open-source (Mulan PSL v2) DBMS: a PostgreSQL 9.2.4 fork
— the server still reports 9.2.4 — rewritten in C++, with a thread-per-session
model, a vectorized executor and a column store bolted onto the row engine.
Two entries, because those two storage engines behave nothing alike here:
`opengauss` is the row store a plain CREATE TABLE gives you, and
`opengauss-column` is the same setup with ORIENTATION = COLUMN, which is what
the vendor documentation prescribes for OLAP.

It installs natively; no container is needed. openGauss publishes binaries for
openEuler and CentOS only, but the 6.0.5 LTS openEuler 20.03 server tarball
(x86_64 and aarch64) is glibc 2.28 and runs unmodified on Ubuntu once two
sonames are bridged in a private directory on LD_LIBRARY_PATH: libaio.so.1,
which Ubuntu renamed to libaio.so.1t64 in a transition that is an ABI no-op on
64-bit, and libreadline.so.7, which only gsql links and only for line editing.
Everything else non-glibc is bundled in the tarball. The server refuses to run
as root, so install creates the conventional omm user and the other scripts go
through a wrapper that sources the openGauss environment; since omm cannot
traverse root's home on the benchmark VMs, create.sql and hits.tsv reach gsql
on stdin rather than by path.

DBCOMPATIBILITY = 'PG' on CREATE DATABASE is the one thing that is not
optional. openGauss defaults to 'A' (Oracle) compatibility, where the empty
string *is* NULL, and the load stops on the first row of hits.tsv with `null
value in column "referer" violates not-null constraint`. Had the schema not
been NOT NULL throughout, the damage would have been silent instead: 94.4% of
MobilePhoneModel, 86.8% of SearchPhrase, 19.0% of Referer and 14.9% of Title
are empty strings in this dataset, and the 16 queries that filter on <> ''
would have matched nothing.

Two configuration knobs needed sizing that the postgresql entry has no
equivalent for. max_process_memory caps everything the instance allocates and
defaults to 12 GB regardless of machine, which would hold a c7a.metal-48xl to a
twelfth of its RAM; install uses 80%, floored at the 2 GB minimum. And
query_dop, which openGauss needs above its default of 1 to use more than one
thread — worth 9x on Q34 at 100M rows — makes every worker thread take a
connection slot, so the driver's 10-connection throughput test exhausts the
default max_connections = 200 with `No free proc is available to create a new
connection`; max_connections is therefore sized from query_dop. ./start also
runs gs_ctl under setsid: it otherwise leaves the server in the caller's
process group, where anything that signals that group takes the database down.

Both entries were run end to end on the full dataset. Each gets exactly
99,997,497 rows in and answers all 43 queries with no errors and no timeouts;
the row store occupies 86.6 GB, the column store 16 GB for the table and
31.3 GB for the data directory, and the column store loads in a little over
half the time. All 43 queries are the postgresql entry's, unmodified. They were
checked query by query against clickhouse-local on a 1% slice — agreeing
everywhere except where a LIMIT cuts a run of tied sort keys, and on AVG(UserID)
where openGauss is the one that is exactly right — and the two openGauss
engines were then compared against each other on the full 100M rows, agreeing
on 34 of 43 with the other 9 being the same tie-breaking cases.

Two openGauss defects found on the way are written up in
opengauss-column/README.md, neither of which changes what the benchmark
measures:

  - Q35 differs from Q34 only by a constant in the GROUP BY list, and runs
    12x slower. EXPLAIN VERBOSE gives the reason in one line: the parallel
    redistribution is keyed on the leading grouping column, so Q35 gets
    `Distribute Key: (1)` and hashes all 100M rows onto a single worker.
    query_dop = 48 makes Q35 slower than query_dop = 1; moving the constant to
    the end of the list restores `Distribute Key: url` and the runtime.

  - An aggregate FILTER clause over a column-store table kills the whole
    gaussdb instance — no log entry, no core, redo recovery on next start.
    1000 rows reproduce it, and the same statement on a row-store table is
    fine.

No results yet — those need runs on the benchmark's own EC2 machines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexey-milovidov
alexey-milovidov deployed to benchmark-approval September 1, 2026 09:10 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

The run of opengauss on c6a.4xlarge did not produce results.
The run of opengauss-column on c6a.4xlarge did not produce results.

Logs:

The first benchmark run of both entries failed, in two unrelated ways, and
neither could reproduce on a development box.

Spilling hash aggregations died with `could not create temporary file ...:
Too many open files`. openGauss derives max_safe_fds from the shell's
`ulimit -n`, which cloud-init leaves at 1024, and then caps it again with
max_files_per_process, whose default is 1000 — so the instance had 978 file
descriptors for everything, and a machine with a generous default ulimit (this
one had 524288) never notices. The `run` wrapper now raises the OS limit to the
1000000 openGauss's own installation guide asks for, falling back to the hard
limit, and `install` raises max_files_per_process to 100000; a fresh install
verified end to end now logs `max_safe_fds = 99978` instead of 978.

The column-store entry spent about 30 minutes in every cold cycle — 18 queries
in ten hours against 156 seconds of actual query time — while the row-store
entry, same driver and machine, spent about 100 seconds. The only difference
between the two configurations was the quarter of RAM the column entry gave
cstore_buffers, so it no longer does: the driver restarts the server and drops
the page cache before every query, so a large CU cache is never warm when it
matters, and 8 GB of it on a 32 GB machine is only a way to run out of memory.
The two install scripts are now identical apart from a comment.

Two defensive changes in the same area, since a cold cycle is invisible in the
log (the driver silences ./start and ./stop): gs_ctl now gets an explicit -t so
a stuck lifecycle operation cannot eat a cycle unbounded, and PGCONNECT_TIMEOUT
bounds each ./check probe, which a server still replaying WAL would otherwise
accept and then sit on.

The row-store entry has a third problem that is not a bug and cannot be
configured away, now measured and written up in its README: its cold scan runs
at about 110 MB/s regardless of the storage under it. On c6a.4xlarge that is
670-720 s for every one of the 43 queries against a ~72 GB table, where the
postgresql entry answers in ~258 s against a larger one. On NVMe capable of
gigabytes per second the same scan still only manages 107-123 MB/s, and
heap_bulk_read_size, the seqscan pre-read knob, does not move it — at
query_dop = 1 a large pre-read makes things worse, reading 25 GB to scan a
6.7 GB table. 43 queries x 3 runs x ~700 s is 25 hours, so on any machine whose
RAM cannot hold the table this entry cannot finish inside the benchmark's
10-hour cap. It should complete on the large-memory instances, where only the
cold run of each query comes off the disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexey-milovidov
alexey-milovidov deployed to benchmark-approval September 1, 2026 20:14 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Results for opengauss-column are ready for: c6a.4xlarge.
The result files are committed as d964a39.

Logs:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant