Skip to content

Add OceanBase (column store and row store) - #1726

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

Add OceanBase (column store and row store)#1726
alexey-milovidov wants to merge 3 commits into
mainfrom
add-oceanbase

Conversation

@alexey-milovidov

Copy link
Copy Markdown
Member

OceanBase is the shared-nothing, Paxos-replicated, multi-tenant DBMS Alibaba started in 2010 and open-sourced under Mulan PSL v2 in 2021: a MySQL-compatible SQL layer — SELECT VERSION() answers 5.7.25-OceanBase_CE-v5.0.1.0 — over an LSM-tree storage engine. Since 4.3 that engine can store a table by column instead of by row, and the two are different enough here to justify two entries: oceanbase is the column store, which is what the vendor's own OLAP parameter template selects, and oceanbase-row is the same setup with WITH COLUMN GROUP (all columns).

It installs natively; no container is needed. OceanBase publishes only el7/el8 RPMs, but the observer links nothing outside glibc and a libaio that ships in the oceanbase-ce-libs package, so rpm2cpio | cpio into /opt/oceanbase runs unmodified on Ubuntu — on x86_64 and aarch64 alike, so the c8g machines are covered too. Nothing is installed system-wide.

What was surprising

  • The whole cluster setup has to live in ./install. A freshly started observer serves no tenant at all — it answers Tenant not in this server until ALTER SYSTEM BOOTSTRAP has created the internal sys tenant, and user data then needs a tenant of its own, which needs a resource pool, which needs a unit config. All of that has to finish before the driver's first ./check.
  • ./load has to force a minor freeze of the meta tenants, or the entry produces no result at all. The direct load leaves ~100 MB of redo log on the meta tenant that every user tenant carries, and that stream's base_lsn stays at zero, so every ./start replays the whole thing. How much replay a tenant can buffer is bounded by its memtable, and a meta tenant's memtable is about 4% of the resource unit's memory — 410 MB on a 9 GB unit. The backlog does not fit, replay stalls with CLOG pending size in task queue exceeds limit, the observer never reaches start success, and ./check times out on all 43 queries. Measured on the unit whose meta tenant gets exactly that 410 MB: without the freeze the server had not finished starting after ten minutes, twice; with it, 27 seconds. TENANT = all covers the sys and user tenants — the meta tenants need all_meta.
  • Load parallelism has to be capped by memory, not just by cores. The vendor's rule is "up to the tenant's core count", but each direct-load worker holds a sort area, a macroblock writer and a 7 MB coroutine stack; at 90 workers against a 9 GB tenant the load reaches ~10 GB of data and dies with ERROR 4013: No memory or reach tenant memory limit, rolling everything back. install caps it at one worker per 512 MB of tenant memory, which on every machine this benchmark runs on is a no-op.
  • secure_file_priv can only be set over a Unix socket, never over TCP, so install uses -S /opt/oceanbase/run/sql.sock for that one statement.
  • du is useless for the data size. The observer preallocates its data file and its redo log pool at startup, so du reports the reservation — 159 GiB against 21.1 GiB of real content on the full dataset. ./data-size reads DATA_DISK_IN_USE + LOG_DISK_IN_USE from GV$OB_SERVERS instead: 11.9 GiB of columns plus 9.2 GiB of redo log.
  • VARCHAR, not TEXT, and the widths are not free. TEXT is a LOB type in OceanBase and the benchmark's hottest columns are URL, Title and Referer. VARCHAR needs a declared width, and OceanBase caps a row at 1.5 MB of declared width, which 28 VARCHAR(65535) columns exceed fivefold. The widths are at least 4× the longest value the dataset actually holds, measured in the loaded table.

Configuration

install sizes the instance the way obd, the vendor's deployer, does — memory_limit at 80% of RAM, system_memory from obd's step function, cpu_count at nproc - 2 — plus the olap parameter and system-variable template that ships inside the RPM itself (etc/default_parameter.json, etc/default_system_variable.json) and that obd and OCP offer as a dropdown. Two of those entries change results and not just speed: utf8mb4_bin makes LIKE and ORDER BY byte-exact, which is what the reference ClickHouse results do, and parallel_degree_policy = AUTO is what lets a query use more than one thread without a hint in the SQL. install refuses to run below 8 GB of RAM, the vendor's documented minimum.

Query results

queries.sql differs from clickhouse/queries.sql on two lines out of 43: Q29's REGEXP_REPLACE backreference has to be '$1' (OceanBase follows MySQL 8, where '\1' is the literal character 1, and left alone the query collapses every row into one group), and Q43 groups by minute, which the mysql entry's '%H:00:00' gets wrong.

Every query was compared against clickhouse-local on the same rows. 33 of 43 agree exactly; eight are LIMIT cutting through tied sort keys (verified — the sort-key multisets match, and in Q24 the ten WatchIDs are the same ten); Q4 differs because ClickHouse overflows AVG(UserID) in an Int64 while OceanBase returns the exact decimal mean; and Q6 differs by 2 in 107907 because utf8mb4_bin is a PAD SPACE collation and two phrase pairs differ only in a trailing space.

Verification

Run end to end on the full dataset: ./load gets exactly 99,997,497 rows in, the APPEND hint's online statistics land with them (num_rows = 99997497), all 43 queries return a result, ./stop./start./check → query works, and the driver's own bench_run_query and bench_concurrent_qps were run against the loaded table. The rowstore entry is validated at 1% scale.

The machine that ran it is a shared 96-core box, not a benchmark VM, so no timing here means anything — and at 96 cores against a 9 GB tenant, a tenth of the memory per core that any machine in this benchmark has, AUTO parallelism picks a degree the tenant cannot afford for Q19, Q32 and Q33. Pinning the degree to 13 — what a 16-core machine would choose — runs all three in 22.6 s, 74.6 s and 40.9 s.

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

OceanBase is the shared-nothing, Paxos-replicated, multi-tenant DBMS Alibaba
started in 2010 and open-sourced under Mulan PSL v2 in 2021: a MySQL-compatible
SQL layer — `SELECT VERSION()` answers `5.7.25-OceanBase_CE-v5.0.1.0` — over an
LSM-tree storage engine. Since 4.3 that engine can store a table by column
instead of by row, and the two are different enough here to justify two
entries: `oceanbase` is the column store, which is what the vendor's own OLAP
parameter template selects, and `oceanbase-row` is the same setup with
`WITH COLUMN GROUP (all columns)`.

It installs natively; no container is needed. OceanBase publishes only el7/el8
RPMs, but the observer links nothing outside glibc and a libaio that ships in
the `oceanbase-ce-libs` package, so `rpm2cpio | cpio` into /opt/oceanbase runs
unmodified on Ubuntu — on x86_64 and aarch64 alike, so the c8g machines are
covered too. Nothing is installed system-wide.

The whole one-time cluster setup lives in ./install rather than ./load, because
a freshly started observer serves no tenant at all: it answers "Tenant not in
this server" until ALTER SYSTEM BOOTSTRAP has created the internal sys tenant,
and user data then needs a tenant of its own, which needs a resource pool,
which needs a unit config. All of that has to be done before the driver's first
./check. The unit is sized from what GV$OB_SERVERS reports as unassigned rather
than from arithmetic on memory_limit, because the bootstrap has already given
the sys tenant a unit whose size depends on the version.

Configuration is obd's, the vendor deployer's: memory_limit at 80% of RAM,
system_memory from obd's step function, cpu_count at nproc-2, plus the `olap`
parameter and system-variable template that ships inside the RPM itself
(etc/default_parameter.json, etc/default_system_variable.json) and that obd and
OCP offer as a dropdown. Two of those template entries change results and not
just speed: utf8mb4_bin makes LIKE and ORDER BY byte-exact, which is what the
reference ClickHouse results do, and parallel_degree_policy = AUTO is what lets
a query use more than one thread without a hint in the SQL.

./load goes through LOAD DATA INFILE with the APPEND hint — OceanBase's bypass
(direct) load, which sorts by primary key and writes straight into major
SSTables, skipping the SQL layer, the transaction layer and the memtable, and
which also collects optimizer statistics on the way so no separate ANALYZE is
needed. Parallelism is the tenant's core count capped at one worker per 512 MB
of tenant memory: each direct-load worker holds a sort area, a macroblock
writer and a 7 MB coroutine stack, and at 90 workers against a 9 GB tenant the
load dies with "No memory or reach tenant memory limit" and rolls back. On the
benchmark's own machines the core count is the smaller of the two limits.

./load then forces a minor freeze of the user tenants *and* of the meta tenants
and waits for it, without which the entry produces no result at all. The direct
load leaves ~100 MB of redo log on the meta tenant that every user tenant
carries, and that stream's base_lsn -- its checkpoint -- stays at zero, so every
./start replays the whole thing; how much replay a tenant can buffer is bounded
by its memtable, and a meta tenant's memtable is about 4% of the resource unit's
memory, 410 MB on a 9 GB unit. The backlog does not fit, replay stalls with
"CLOG pending size in task queue exceeds limit", the observer never reaches
"start success", and ./check times out on all 43 queries. Measured on the unit
whose meta tenant gets exactly that 410 MB: without the freeze the server had
not finished starting after ten minutes, twice; with it, 27 seconds. TENANT =
all covers the sys and user tenants; the meta tenants need all_meta. For the
same reason in reverse, benchmark.sh raises BENCH_CHECK_TIMEOUT to 900 s -- the
observer's start is not instant even with nothing to replay, and a ./check that
times out aborts the whole run rather than one query.

./data-size reports DATA_DISK_IN_USE + LOG_DISK_IN_USE from GV$OB_SERVERS
rather than du of the store directory: the observer preallocates both its data
file and its redo log pool at startup, so du reports the reservation and would
say the same thing about an empty database as about a loaded one. On the full
dataset that is 21.1 GiB -- 11.9 GiB of columns plus 9.2 GiB of redo log --
against 159 GiB of du.

queries.sql differs from clickhouse/queries.sql on two lines out of 43. Q29's
REGEXP_REPLACE backreference has to be '$1' — OceanBase follows MySQL 8, where
'\1' is the literal character 1, and left alone the query collapses every row
into a single group. Q43 groups by minute, which the mysql entry's '%H:00:00'
gets wrong. Every query was compared against clickhouse-local on the same rows:
33 agree exactly, eight are LIMIT cutting through tied sort keys (verified: the
sort-key multisets match, and in Q24 the ten WatchIDs are the same ten), Q4
differs because ClickHouse overflows AVG(UserID) in an Int64 while OceanBase
returns the exact decimal mean, and Q6 differs by 2 in 107907 because
utf8mb4_bin is a PAD SPACE collation and two phrase pairs differ only in a
trailing space.

The string columns are VARCHAR rather than TEXT — TEXT is a LOB type in
OceanBase and the benchmark's hottest columns are URL, Title and Referer — with
widths at least four times the longest value the dataset actually holds, since
OceanBase caps a row at 1.5 MB of declared width and 28 VARCHAR(65535) columns
exceed that fivefold.

Verified end to end on the full dataset: ./load gets exactly 99,997,497 rows in,
the APPEND hint's online statistics land with them, all 43 queries return a
result, stop/start/check/query works, and the driver's own bench_run_query and
bench_concurrent_qps were run against the loaded table. The rowstore entry is
validated at 1% scale. 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 13:47 — with GitHub Actions Active
@alexey-milovidov

Copy link
Copy Markdown
Member Author

What was surprising

This is laughable. How not to build a database 🤣

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

The run of oceanbase on c6a.4xlarge did not produce results.
The run of oceanbase-row on c6a.4xlarge did not produce results.

Logs:

@alexey-milovidov

alexey-milovidov commented Sep 1, 2026

Copy link
Copy Markdown
Member Author
[2026-09-01 19:25:19.226034] INFO  [LIB] pidfile_test (utility.cpp:1113) [2439][observer][T0][Y0-0000000000000000-0-0] [lt=0] fid file doesn't exist(pidfile=run/observer.pid)
ERROR 4015 (HY000) at line 1: System error

This is pathetic.

INFO [LIB] - double whitespace
[Y0-0000000000000000-0-0] - what's that shit?
[lt=0] - what's those?
fid - what's "fid"?
exist(pidfile=run/observer.pid) - missing space before (
ERROR 4015 (HY000) at line 1: System error - system error 🤣

@alexey-milovidov

Copy link
Copy Markdown
Member Author

It must be a very serious enterprise database. Judging by error messages.

The c6a.4xlarge run died at ALTER SYSTEM BOOTSTRAP with a bare "ERROR 4015
(HY000): System error" and nothing else in the log, so there was nothing to act
on. Rerunning ./install locally with that machine's exact derived configuration
-- memory_limit 25102M, system_memory 6144M, cpu_count 14, a 162 GB data file
and a 24.5 GB log disk -- bootstraps fine on this box, so whatever it is does
not follow from the numbers.

Three changes, in order of usefulness:

- On failure, dump the observer's own log: the lines mentioning bootstrap,
  CHECK_SERVER_EMPTY, WDIAG/EDIAG/ERROR, plus the tail of observer.log and of
  observer.log.wf. The next run will say what the server actually objected to
  instead of leaving us to guess.
- Give the bootstrap up to three attempts. Retrying is not just a matter of
  re-running the statement: the observer decides whether it may be
  bootstrapped by looking for its own leftovers, and those are spread around
  $OB_HOME rather than confined to store/ -- etc2/, etc3/, wallet/, audit/ and
  two generated files in etc/. Leave any of them behind and the retry is
  refused with "Server is not empty but has never been bootstrapped ...
  has_data_version_file=TRUE", which is exactly what happened when a first
  attempt at this cleared only store/. reset_observer_state clears all of
  them, and re-bootstrapping from that state was verified to work.
- Wait 30 s after root@sys first answers before bootstrapping. The SQL port
  comes up before the rest of the server has settled and the bootstrap's first
  act is a round of RPCs to every address in the rs_list; an instant start was
  reproduced here by putting the store on tmpfs, and while that did bootstrap
  cleanly, firing the statement into a server that answered a second ago is
  not worth the risk when the wait is free.

Also fixes the shutdown wait in the retry path to use the pid file: the
observer is launched as ./bin/observer from $OB_HOME, so pgrep on an absolute
path never matches it.

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

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

The run of oceanbase on c6a.4xlarge did not produce results.

Logs:

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Results for oceanbase-row are ready for: c6a.4xlarge.
The result files are committed as bc63329.

Logs:

@alexey-milovidov alexey-milovidov added the machine:all PR benchmark on every machine type label Sep 2, 2026
@alexey-milovidov
alexey-milovidov deployed to benchmark-approval September 2, 2026 06:31 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The run of oceanbase on c6a.large did not produce results.
The run of oceanbase on c6a.xlarge did not produce results.
The run of oceanbase on t3a.small did not produce results.
The run of oceanbase-row on c6a.large did not produce results.
The run of oceanbase-row on c6a.xlarge did not produce results.
The run of oceanbase-row on t3a.small did not produce results.

Logs:

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

Labels

machine:all PR benchmark on every machine type

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant