From 0a8fc80591235f0d05263056f916da8a495d2881 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:11:19 +0530 Subject: [PATCH 01/15] fix(bench): force HyperGraphDB's log after each commit under sync durability HyperGraphDB 1.4 begins every JE transaction with WRITE_NO_SYNC, which overrides the COMMIT_SYNC the adapter set on the environment. In sync mode its commits were never forced: single-update commits took about 0.1 ms while one forced write on the test machine takes about 3.8 ms. The adapter now calls Environment.flushLog(true) after every commit in sync mode. --- .../src/main/java/io/hstore/bench/HyperGraphDbStore.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/main/java/io/hstore/bench/HyperGraphDbStore.java b/benchmarks/src/main/java/io/hstore/bench/HyperGraphDbStore.java index 7774608..edc88d9 100644 --- a/benchmarks/src/main/java/io/hstore/bench/HyperGraphDbStore.java +++ b/benchmarks/src/main/java/io/hstore/bench/HyperGraphDbStore.java @@ -39,10 +39,12 @@ final class HyperGraphDbStore implements Store { private long readBeforeReopen; private final long cacheBytes; + private final boolean sync; HyperGraphDbStore(Path directory, boolean sync, long cacheBytes) { this.directory = directory; this.cacheBytes = cacheBytes; + this.sync = sync; configuration.setTransactional(true); EnvironmentConfig environment = ((BJEStorageImplementation) configuration.getStoreImplementation()).getConfiguration().getEnvironmentConfig(); if (sync) { @@ -76,7 +78,11 @@ public Path directory() { } private T transact(Callable work) { - return graph.getTransactionManager().transact(work); + T result = graph.getTransactionManager().transact(work); + if (sync) { + environment().flushLog(true); + } + return result; } private T read(Callable work) { From 99b25db13dde104958dbe0667aed04a37bc62e15 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:11:19 +0530 Subject: [PATCH 02/15] feat(bench): print the size and skew of the generated dataset DatasetStats prints node, hyperedge and incidence counts, the cardinality distribution and the incidence-set size distribution at scales 1 and 4, for the dataset table in the benchmark report. --- .../java/io/hstore/bench/DatasetStats.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 benchmarks/src/main/java/io/hstore/bench/DatasetStats.java diff --git a/benchmarks/src/main/java/io/hstore/bench/DatasetStats.java b/benchmarks/src/main/java/io/hstore/bench/DatasetStats.java new file mode 100644 index 0000000..bdefa1d --- /dev/null +++ b/benchmarks/src/main/java/io/hstore/bench/DatasetStats.java @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Metacog Labs +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 + +package io.hstore.bench; + +import java.util.Arrays; +import java.util.stream.IntStream; + +public final class DatasetStats { + + private static final long SEED = 20_260_904L; + private static final int INLINE_POSTINGS = 48; + + private DatasetStats() { + } + + public static void main(String[] args) { + IntStream.of(1, 4).forEach(DatasetStats::print); + } + + private static void print(int scale) { + Dataset dataset = Dataset.generate(scale, SEED); + int[] degree = new int[dataset.nodes()]; + int[] cardinality = Arrays.stream(dataset.edges()).mapToInt(edge -> edge.length).sorted().toArray(); + Arrays.stream(dataset.edges()).flatMapToInt(Arrays::stream).forEach(member -> degree[member]++); + long incidences = IntStream.of(cardinality).asLongStream().sum(); + int[] sorted = degree.clone(); + Arrays.sort(sorted); + IO.println("scale %d: %,d nodes, %,d hyperedges, %,d incidences".formatted(scale, dataset.nodes(), cardinality.length, incidences)); + IO.println(" cardinality mean %.2f, p99 %d, max %d".formatted(incidences / (double) cardinality.length, + at(cardinality, 0.99), cardinality[cardinality.length - 1])); + IO.println(" incidence set mean %.2f, p50 %d, p90 %d, p99 %d, p99.9 %d, max %d".formatted(incidences / (double) degree.length, + at(sorted, 0.5), at(sorted, 0.9), at(sorted, 0.99), at(sorted, 0.999), sorted[sorted.length - 1])); + IO.println(" nodes in no hyperedge %,d, incidence sets above %d entries %,d".formatted( + IntStream.of(degree).filter(d -> d == 0).count(), INLINE_POSTINGS, IntStream.of(degree).filter(d -> d > INLINE_POSTINGS).count())); + } + + private static int at(int[] sorted, double quantile) { + return sorted[(int) (sorted.length * quantile)]; + } +} From 4d1de1599948b7df4ccb59231045a2de1efc4e02 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:11:19 +0530 Subject: [PATCH 03/15] docs(bench): publish results from main with equal 64 MiB caches Replaces the tables with runs of commit 459b8be at scale 1 (async and sync) and scale 4, both caches at 64 MiB. The analysis now covers where HyperGraphDB leads (member enumeration once the data outgrows HStore's node cache), the results that are too close to call, and what HStore spends. The setup explains the sync fix for HyperGraphDB and notes that sync results published before it measured HyperGraphDB without syncing. --- docs/benchmarks.md | 271 +++++++++++++++++++++++++++++---------------- 1 file changed, 173 insertions(+), 98 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 441aff0..0bcc672 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -23,11 +23,18 @@ flowchart LR | Data model used | node type `Entity` (indexed by key, property `name`); `SET_EDGE` type `Relation` | `String` atoms; `HGPlainLink` links | | Cache | node cache of half the heap, or `CACHE_MB` | JE cache at 30% of the heap (HyperGraphDB default), or `CACHE_MB`, plus the HyperGraphDB atom cache | | `async` durability | `durability = async` | HyperGraphDB default: JE `WRITE_NO_SYNC` | -| `sync` durability | `durability = sync`, `wal_mode = references`, group commit | JE `COMMIT_SYNC` | +| `sync` durability | `durability = sync`, `wal_mode = references`, group commit | JE `COMMIT_SYNC`, and `Environment.flushLog(true)` after every commit (see below) | | History | `history_limit = 64` generations retained for time travel (default) | none | -Each run used a 4 GiB heap and `-XX:+UseParallelGC` on JDK 25 (GraalVM, HotSpot JIT) on macOS 26.5.1 with an -Apple silicon `aarch64` CPU and 10 cores. +Each run used a 4 GiB heap and `-XX:+UseParallelGC` on Oracle GraalVM for JDK 25.0.4 (HotSpot JIT), on an Apple +M5 (4 performance and 6 efficiency cores) with 24 GiB of memory and an internal SSD, running macOS 26.5.1. + +**Synchronous commits in HyperGraphDB.** HyperGraphDB 1.4 begins every JE transaction with an explicit +`WRITE_NO_SYNC` durability, which overrides the environment's `COMMIT_SYNC`. Setting the environment alone +leaves its commits unsynced: single-update commits took about 0.1 ms, while one forced write on the machine +above takes about 3.8 ms. In `sync` mode the adapter therefore calls `Environment.flushLog(true)` after every +commit, which writes and forces JE's log as `COMMIT_SYNC` would. Results for `sync` published before this +change measured HyperGraphDB without any sync. **Dataset.** At scale *s*: 50,000·*s* nodes and 100,000·*s* hyperedges. Cardinality follows a geometric tail starting at 2 (continue with probability 0.62, capped at 32), for a mean of 3.6. Members are drawn as @@ -159,121 +166,190 @@ configured (see *Setup*). ## Results +These results come from HStore at commit `459b8be`, on the machine described in *Setup*, with both caches set to +64 MiB (`CACHE_MB=64`) and `THREADS=8`. Scale 1 ran 5 times per store in each durability mode, and scale 4 ran 3 +times. Each cell is the median, with the slowest and fastest run in brackets. The machine was running other +work: sampled before each run, the one-minute load average was between 4.4 and 16.7 on 10 cores. Where the +bracketed ranges of the two stores overlap, treat the ratio as a tendency rather than a measured difference. + +The [benchmark report](benchmark-report.pdf) has the full method, every table for all three configurations +(latency, bytes written, heap and GC), the value of every run, and the discussion. Its raw results, and the +script that builds its tables, are in [`benchmarks/report`](../benchmarks/report). + Ratios above 1 favour HStore. Throughput ratios divide HStore by HyperGraphDB; latency and size ratios divide -HyperGraphDB by HStore. Laptop runs vary by up to ±30% from one run to the next, so read the tables for -consistent multiples rather than single digits. +HyperGraphDB by HStore. ### Scale 1, `async` durability -Dataset: 50,000 nodes, 100,000 hyperedges, 362,757 incidences. +Dataset: 50,000 nodes, 100,000 hyperedges, 362,757 incidences; durability async; 8 threads. -| Workload | HStore | HyperGraphDB | Ratio | Results agree | +| Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes` — insert nodes in transactions of 1,000 | 12,717 ops/s | 10,451 ops/s | **1.22×** | yes | -| `ingest.edges` — insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 9,610 ops/s | 16,684 ops/s | **0.58×** | yes | -| `read.incidence` — enumerate the incidence set of a node | 189,969 ops/s | 48,237 ops/s | **3.94×** | yes | -| `read.members` — enumerate the members of a hyperedge | 313,600 ops/s | 853,555 ops/s | **0.37×** | yes | -| `read.comembership` — count hyperedges containing both atoms of a pair | 72,541 ops/s | 9,134 ops/s | **7.94×** | yes | -| `read.incidence.parallel` — incidence sets from 10 threads, 100,000 probes each | 585,719 ops/s | 881,973 ops/s | **0.66×** | yes | -| `write.update` — replace a node value in transactions of 1,000 | 9,821 ops/s | 5,114 ops/s | **1.92×** | yes | -| `large.ingest` — create one hyperedge with 50,000 members | 93,884 ops/s | 40,387 ops/s | **2.32×** | yes | -| `large.scan` — enumerate all members of the large hyperedge (x20) | 2,392,915 ops/s | 81,701,289 ops/s | **0.03×** | yes | -| `large.probe` — test whether a node belongs to the large hyperedge | 594,904 ops/s | 200,944 ops/s | **2.96×** | yes | -| `reopen` — close and reopen the database, including recovery | 961 ms | 1,514 ms | **1.58×** | yes | -| `read.incidence.cold` — incidence sets immediately after reopening | 137,791 ops/s | 35,009 ops/s | **3.94×** | yes | -| `disk` — bytes on disk after a clean shutdown | 243.5 MiB | 252.9 MiB | **1.04×** | yes | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 45,124 ops/s (32,919 to 66,106) | 24,930 ops/s (11,229 to 33,032) | **1.81×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 28,198 ops/s (22,543 to 37,283) | 22,500 ops/s (11,053 to 35,992) | **1.25×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 7,472,143 ops/s (3,997,182 to 7,630,311) | 739,484 ops/s (221,944 to 927,800) | **10.10×** | yes | +| `read.members`: enumerate the members of a hyperedge | 3,694,075 ops/s (1,529,065 to 5,202,587) | 1,702,172 ops/s (482,769 to 2,220,779) | **2.17×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 447,789 ops/s (127,231 to 651,937) | 32,538 ops/s (18,486 to 54,813) | **13.76×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 301,232 ops/s (240,766 to 347,622) | 74,579 ops/s (18,598 to 130,603) | **4.04×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 24,439,172 ops/s (17,134,244 to 34,798,935) | 1,691,919 ops/s (637,312 to 2,175,513) | **14.44×** | yes | +| `latency.read`: one incidence lookup per read transaction | 2,809,235 ops/s (1,970,735 to 3,462,394) | 269,377 ops/s (106,860 to 337,642) | **10.43×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 78,091 ops/s (53,208 to 82,882) | 5,111 ops/s (2,905 to 8,739) | **15.28×** | yes | +| `latency.commit`: one node update per write transaction | 7,865 ops/s (5,340 to 13,622) | 5,798 ops/s (4,560 to 9,591) | **1.36×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,872,082 ops/s (1,782,768 to 2,529,519) | 761,958 ops/s (316,836 to 1,021,256) | **2.46×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (996 to 1,000) | 1,000 ops/s (795 to 1,000) | **1.00×** | n/a | +| `large.ingest`: create one hyperedge with 50,000 members | 439,642 ops/s (261,929 to 480,878) | 35,654 ops/s (13,771 to 54,078) | **12.33×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 2,259,599,768 ops/s (1,700,065,878 to 3,729,893,079) | 610,672,274 ops/s (308,735,077 to 1,170,263,579) | **3.70×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 15,623,882 ops/s (2,535,875 to 19,290,897) | 771,891 ops/s (251,952 to 1,073,301) | **20.24×** | yes | +| `reopen`: close and reopen the database, including recovery | 121 ms (116 to 373) | 1,369 ms (940 to 2,088) | **11.31×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 2,761,318 ops/s (277,983 to 3,026,066) | 41,898 ops/s (15,486 to 55,249) | **65.91×** | yes | +| `disk`: bytes on disk after a clean shutdown | 91.2 MiB (91.1 to 93.2) | 432.2 MiB (374.2 to 535.2) | **4.74×** | yes | +| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 16,291 ops/s (6,055 to 20,524) | 2,986 ops/s (1,565 to 6,388) | **5.46×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 24,365 ops/s (14,469 to 33,208) | 8,119 ops/s (5,676 to 15,599) | **3.00×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 6,888,001 ops/s (2,626,571 to 7,491,198) | 1,107,018 ops/s (785,085 to 2,403,497) | **6.22×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 119.8 MiB (115.9 to 129.7) | 293.3 MiB (261.2 to 348.9) | **2.45×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 90.8 MiB (90.8 to 90.8) | 273.5 MiB (204.0 to 290.7) | **3.01×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 128 ms (107 to 356) | 132 ms (82 to 157) | **1.03×** | n/a | + +Latency of single operations, median of each percentile across runs: + +| Workload | Percentile | HStore | HyperGraphDB | Ratio | +|---|---|---:|---:|---:| +| `latency.read` | p50 | 0.3 µs | 1.6 µs | **4.76×** | +| `latency.read` | p99 | 1.2 µs | 26.3 µs | **22.53×** | +| `latency.read` | p99.9 | 5.2 µs | 207.0 µs | **40.05×** | +| `latency.read` | max | 57.8 µs | 7.29 ms | **126.09×** | +| `latency.commit` | p50 | 85.1 µs | 91.5 µs | **1.07×** | +| `latency.commit` | p99 | 429.6 µs | 1.08 ms | **2.52×** | +| `latency.commit` | p99.9 | 1.62 ms | 5.02 ms | **3.09×** | +| `latency.commit` | max | 8.79 ms | 25.56 ms | **2.91×** | +| `mixed.read` | p50 | 1.6 µs | 2.9 µs | **1.77×** | +| `mixed.read` | p99 | 12.7 µs | 73.6 µs | **5.81×** | +| `mixed.read` | p99.9 | 57.4 µs | 1.03 ms | **17.93×** | +| `mixed.read` | max | 51.62 ms | 87.42 ms | **1.69×** | +| `mixed.write` | p50 | 3.09 ms | 4.85 ms | **1.57×** | +| `mixed.write` | p99 | 16.70 ms | 80.12 ms | **4.80×** | +| `mixed.write` | p99.9 | 46.96 ms | 110.43 ms | **2.35×** | +| `mixed.write` | max | 46.96 ms | 110.43 ms | **2.35×** | +| `compaction.baseline` | p50 | 5.06 ms | 4.87 ms | **0.96×** | +| `compaction.baseline` | p99 | 9.63 ms | 27.99 ms | **2.91×** | +| `compaction.baseline` | p99.9 | 10.60 ms | 52.86 ms | **4.99×** | +| `compaction.baseline` | max | 10.60 ms | 52.86 ms | **4.99×** | +| `compaction` | p50 | 5.15 ms | 5.11 ms | **0.99×** | +| `compaction` | p99 | 9.39 ms | 13.32 ms | **1.42×** | +| `compaction` | p99.9 | 16.03 ms | 33.70 ms | **2.10×** | +| `compaction` | max | 16.03 ms | 33.70 ms | **2.10×** | ### Scale 1, `sync` durability -| Workload | HStore | HyperGraphDB | Ratio | Results agree | +Dataset: 50,000 nodes, 100,000 hyperedges, 362,757 incidences; durability sync; 8 threads. + +| Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes` — insert nodes in transactions of 1,000 | 15,748 ops/s | 11,211 ops/s | **1.40×** | yes | -| `ingest.edges` — insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 12,349 ops/s | 9,142 ops/s | **1.35×** | yes | -| `read.incidence` — enumerate the incidence set of a node | 339,666 ops/s | 31,321 ops/s | **10.84×** | yes | -| `read.members` — enumerate the members of a hyperedge | 557,841 ops/s | 803,557 ops/s | **0.69×** | yes | -| `read.comembership` — count hyperedges containing both atoms of a pair | 114,055 ops/s | 8,595 ops/s | **13.27×** | yes | -| `read.incidence.parallel` — incidence sets from 10 threads, 100,000 probes each | 912,412 ops/s | 682,443 ops/s | **1.34×** | yes | -| `write.update` — replace a node value in transactions of 1,000 | 12,867 ops/s | 4,844 ops/s | **2.66×** | yes | -| `large.ingest` — create one hyperedge with 50,000 members | 156,773 ops/s | 35,232 ops/s | **4.45×** | yes | -| `large.scan` — enumerate all members of the large hyperedge (x20) | 8,374,222 ops/s | 87,843,227 ops/s | **0.10×** | yes | -| `large.probe` — test whether a node belongs to the large hyperedge | 1,063,080 ops/s | 214,721 ops/s | **4.95×** | yes | -| `reopen` — close and reopen the database, including recovery | 445 ms | 1,472 ms | **3.31×** | yes | -| `read.incidence.cold` — incidence sets immediately after reopening | 289,371 ops/s | 38,394 ops/s | **7.54×** | yes | -| `disk` — bytes on disk after a clean shutdown | 243.5 MiB | 300.3 MiB | **1.23×** | yes | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 29,660 ops/s (22,199 to 31,465) | 12,627 ops/s (9,260 to 15,596) | **2.35×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 19,884 ops/s (5,121 to 25,199) | 16,377 ops/s (11,336 to 19,379) | **1.21×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 3,662,400 ops/s (722,841 to 4,402,272) | 384,637 ops/s (202,508 to 541,974) | **9.52×** | yes | +| `read.members`: enumerate the members of a hyperedge | 2,553,373 ops/s (1,283,576 to 2,708,706) | 1,176,098 ops/s (605,219 to 1,606,077) | **2.17×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 268,955 ops/s (73,128 to 359,769) | 9,264 ops/s (6,602 to 22,363) | **29.03×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 174,383 ops/s (112,165 to 239,400) | 38,159 ops/s (14,753 to 62,344) | **4.57×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 7,889,212 ops/s (3,865,167 to 17,032,178) | 942,062 ops/s (626,685 to 1,698,089) | **8.37×** | yes | +| `latency.read`: one incidence lookup per read transaction | 1,807,100 ops/s (601,842 to 2,270,977) | 222,231 ops/s (170,393 to 251,137) | **8.13×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 40,288 ops/s (6,028 to 50,806) | 3,600 ops/s (2,741 to 4,556) | **11.19×** | yes | +| `latency.commit`: one node update per write transaction | 250 ops/s (238 to 255) | 203 ops/s (160 to 216) | **1.23×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,583,174 ops/s (1,248,493 to 1,633,918) | 623,680 ops/s (528,806 to 731,169) | **2.54×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (999 to 1,000) | 973 ops/s (937 to 1,000) | **1.03×** | n/a | +| `large.ingest`: create one hyperedge with 50,000 members | 264,836 ops/s (100,131 to 348,720) | 28,912 ops/s (18,611 to 59,305) | **9.16×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 1,793,205,884 ops/s (1,362,030,591 to 2,533,074,993) | 401,183,491 ops/s (209,074,988 to 495,719,770) | **4.47×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 9,596,123 ops/s (7,711,041 to 10,334,359) | 867,374 ops/s (123,150 to 1,185,144) | **11.06×** | yes | +| `reopen`: close and reopen the database, including recovery | 165 ms (146 to 166) | 1,274 ms (1,016 to 2,417) | **7.73×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 1,920,287 ops/s (1,498,046 to 2,097,403) | 35,738 ops/s (19,627 to 47,057) | **53.73×** | yes | +| `disk`: bytes on disk after a clean shutdown | 91.2 MiB (90.7 to 92.7) | 350.9 MiB (341.1 to 429.2) | **3.85×** | yes | +| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 10,529 ops/s (7,705 to 14,811) | 2,326 ops/s (2,045 to 3,494) | **4.53×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 21,722 ops/s (7,640 to 26,595) | 6,392 ops/s (4,632 to 8,894) | **3.40×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 4,178,629 ops/s (3,906,256 to 4,731,898) | 832,358 ops/s (239,476 to 1,283,097) | **5.02×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 123.6 MiB (121.6 to 128.4) | 278.1 MiB (259.0 to 359.5) | **2.25×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 90.8 MiB (90.8 to 90.8) | 235.4 MiB (230.3 to 268.1) | **2.59×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 191 ms (156 to 1,646) | 139 ms (123 to 171) | **0.73×** | n/a | ### Scale 4, `async` durability -Dataset: 200,000 nodes, 400,000 hyperedges, 1,453,427 incidences. +Dataset: 200,000 nodes, 400,000 hyperedges, 1,453,427 incidences; durability async; 8 threads. -| Workload | HStore | HyperGraphDB | Ratio | Results agree | +| Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes` — insert nodes in transactions of 1,000 | 11,013 ops/s | 13,093 ops/s | **0.84×** | yes | -| `ingest.edges` — insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 3,063 ops/s | 3,803 ops/s | **0.81×** | yes | -| `read.incidence` — enumerate the incidence set of a node | 180,086 ops/s | 8,166 ops/s | **22.05×** | yes | -| `read.members` — enumerate the members of a hyperedge | 134,192 ops/s | 888,461 ops/s | **0.15×** | yes | -| `read.comembership` — count hyperedges containing both atoms of a pair | 64,263 ops/s | 2,735 ops/s | **23.50×** | yes | -| `read.incidence.parallel` — incidence sets from 10 threads, 100,000 probes each | 339,512 ops/s | 736,535 ops/s | **0.46×** | yes | -| `write.update` — replace a node value in transactions of 1,000 | 7,015 ops/s | 4,010 ops/s | **1.75×** | yes | -| `large.ingest` — create one hyperedge with 200,000 members | 40,146 ops/s | 15,985 ops/s | **2.51×** | yes | -| `large.scan` — enumerate all members of the large hyperedge (x20) | 2,416,821 ops/s | 78,756,824 ops/s | **0.03×** | yes | -| `large.probe` — test whether a node belongs to the large hyperedge | 841,554 ops/s | 113,450 ops/s | **7.42×** | yes | -| `reopen` — close and reopen the database, including recovery | 2,035 ms | 9,304 ms | **4.57×** | yes | -| `read.incidence.cold` — incidence sets immediately after reopening | 63,822 ops/s | 8,299 ops/s | **7.69×** | yes | -| `disk` — bytes on disk after a clean shutdown | 2361.9 MiB | 1265.5 MiB | **0.54×** | yes | - -Rerunning scale 4 with `history_limit = 1` gives the following disk row, which shows that retained history is -not what drives HStore's footprint: - -| `disk` — bytes on disk after a clean shutdown | 2115.5 MiB | 1265.5 MiB | **0.60×** | yes | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 27,056 ops/s (23,106 to 54,381) | 21,380 ops/s (18,088 to 39,804) | **1.27×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 11,696 ops/s (8,388 to 29,131) | 1,903 ops/s (1,370 to 2,876) | **6.15×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 2,658,726 ops/s (721,371 to 3,508,844) | 1,005,595 ops/s (466,163 to 1,020,410) | **2.64×** | yes | +| `read.members`: enumerate the members of a hyperedge | 218,369 ops/s (164,400 to 441,827) | 1,719,353 ops/s (1,087,218 to 2,715,851) | **0.13×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 390,285 ops/s (245,706 to 685,369) | 45,277 ops/s (2,502 to 64,982) | **8.62×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 15,008 ops/s (8,947 to 22,517) | 78,094 ops/s (21,417 to 98,666) | **0.19×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 5,180,283 ops/s (4,054,032 to 24,383,863) | 1,784,381 ops/s (1,044,917 to 1,855,868) | **2.90×** | yes | +| `latency.read`: one incidence lookup per read transaction | 1,151,517 ops/s (1,025,897 to 2,794,408) | 364,075 ops/s (120,193 to 368,730) | **3.16×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 20,793 ops/s (11,713 to 64,932) | 1,240 ops/s (1,046 to 2,486) | **16.77×** | yes | +| `latency.commit`: one node update per write transaction | 5,210 ops/s (1,503 to 12,178) | 2,017 ops/s (1,384 to 2,540) | **2.58×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,762,368 ops/s (1,350,450 to 2,098,567) | 883,741 ops/s (535,366 to 1,019,176) | **1.99×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (1,000 to 1,000) | 1,000 ops/s (897 to 1,000) | **1.00×** | n/a | +| `large.ingest`: create one hyperedge with 200,000 members | 212,654 ops/s (148,736 to 632,034) | 12,629 ops/s (7,597 to 13,984) | **16.84×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 1,598,891,425 ops/s (933,830,793 to 2,831,060,900) | 564,337,071 ops/s (89,028,350 to 642,985,381) | **2.83×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 6,819,112 ops/s (2,350,931 to 17,020,914) | 991,137 ops/s (952,611 to 1,137,921) | **6.88×** | yes | +| `reopen`: close and reopen the database, including recovery | 238 ms (127 to 313) | 1,210 ms (914 to 1,569) | **5.09×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 346,015 ops/s (286,676 to 1,825,463) | 11,832 ops/s (6,048 to 15,298) | **29.24×** | yes | +| `disk`: bytes on disk after a clean shutdown | 165.1 MiB (163.2 to 165.2) | 2366.0 MiB (2233.5 to 2377.7) | **14.33×** | yes | +| `churn.delete`: delete 80,000 hyperedges (20%) in transactions of 1,000 | 2,090 ops/s (1,534 to 5,016) | 1,487 ops/s (885 to 1,857) | **1.41×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 2,828 ops/s (2,449 to 7,564) | 2,684 ops/s (1,730 to 3,572) | **1.05×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 3,450,581 ops/s (2,866,935 to 6,637,792) | 777,091 ops/s (767,716 to 1,148,418) | **4.44×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 1140.8 MiB (1140.8 to 1142.8) | 1875.4 MiB (1869.6 to 1945.4) | **1.64×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 162.6 MiB (162.6 to 162.7) | 1177.7 MiB (1168.7 to 1216.8) | **7.24×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 203 ms (177 to 403) | 545 ms (362 to 1,214) | **2.69×** | n/a | ## Analysis **Where HStore leads, and why.** -* **Incidence enumeration (4–22×) and co-membership (8–24×).** An atom's incidence set is a counted B+tree keyed - by hyperedge id. Enumeration is therefore a sequential cursor walk over a few packed nodes, and +* **Incidence enumeration (2.6–10×), co-membership (8.6–29×) and cold reads after a reopen (29–66×).** An + atom's incidence set is one entry of the reverse index: a short list stored inline for most atoms, and a + counted B+tree for hubs. Enumeration hands each hyperedge id to the caller without allocating, and co-membership is a leapfrog intersection that skips whole subtrees by their summaries - ([persistent-tree.md](storage/persistent-tree.md)). HyperGraphDB stores incidence sets as BDB duplicate - sets: it opens a cursor for each lookup, and its `hg.and` query plans a general join. The gap widens with - scale because hubs grow and HStore's cost depends on the smaller side of the intersection. -* **Membership probes on giant hyperedges (3–7×).** A member lookup is a single descent of the hyperedge's - membership tree. HyperGraphDB answers it through the node's incidence set. -* **Updates (1.8–2.7×) and giant-edge ingest (2.3–4.5×).** A batch becomes one copy-on-write path per touched - tree and a single root swap at commit, and large edges are bulk-built bottom-up (`BulkBuilder`). -* **Reopen (1.6–4.6×) and cold reads (4–8×).** A clean shutdown leaves a checkpointed catalog, so opening a - database reads one catalog image and replays an empty WAL suffix. Cold reads benefit from packed 64-byte - extents: one 4 KiB read usually fetches a whole small node ([pages.md](storage/pages.md)). + ([persistent-tree.md](storage/persistent-tree.md)). HyperGraphDB opens a JE cursor on a duplicate set for + each lookup, and its `hg.and` query plans a general join. +* **Updates (11–17×) and bytes written.** A commit logs only the keys it changed, a few hundred bytes, and a + checkpoint writes each changed node once for all the commits since the previous one + ([wal-and-recovery.md](transactions/wal-and-recovery.md)). A JE commit appends a new version of every changed + record and of the B+tree nodes above it. With `async` durability, a single-update commit writes 649 B in HStore against about 9 KiB in + HyperGraphDB. +* **Giant hyperedges (ingest 9–17×, probes 6.9–20×).** Large edges are bulk-built bottom-up, and a membership + probe is one descent of the edge's membership tree. +* **Disk (3.9–14× smaller after load).** Ids are 8-byte varint gaps instead of 16-byte handles, node images are + packed into 64-byte units, small member trees and incidence sets live inside their parent record, and the WAL + is truncated at every checkpoint. +* **Tail latency.** HStore's p99 and p99.9 are lower in every latency workload. Readers pin an immutable + generation and never wait for writers, checkpoints or compaction. **Where HyperGraphDB leads, and why.** -* **Member enumeration (`read.members`, `large.scan`).** `graph.get(link)` returns an `HGPlainLink` from the - atom cache, which holds a Java array of target handles, so a scan is an array loop. HStore walks a B+tree - cursor, decodes incidence columns, resolves role sets and builds a `Member` record for each member. That - record carries roles, weight, validity, position and qualifier. The cost buys per-member metadata and - snapshot-consistent reads, which a plain link cannot express. The benchmark now reads ids through - `Reader.forEachMemberId`, which builds neither `Member` records nor tree entries; the numbers below predate that. - Small hyperedges now keep their members inline in their catalog record, so the extra page per edge is gone - too (see *Inline member trees* below); the tables above predate that as well. -* **Edge ingest at small batch sizes (about 0.6–0.8× in async mode).** Each commit rewrites the incident-tree - paths of every touched member, which is copy-on-write write amplification. JE appends log records and - defers the work to its cleaner. In `sync` mode group commit amortises the fsync and HStore leads (1.35×). - Since storage format 7 a commit no longer writes those paths: it logs the keys it changed, and a checkpoint - writes each changed node once for all the commits since the previous one. These numbers predate that. -* **Parallel incidence reads at scale 4 (0.46×).** Every probe batch opens a snapshot, which pins a - generation in a shared skip-list map. At 10 threads this coordination, plus node-cache admission under - churn (the hot set exceeded the 1,048,576-node cache these runs used), costs more than JE's latch-free cache hits. At scale 1 - the results are mixed: 0.66× async and 1.34× sync. -* **Disk at scale 4 (0.54×).** Live data is comparable: about 1 GB of live node images in HStore against - 1.27 GB in JE. The difference is garbage that has not been reclaimed yet. Both engines are log-structured - with a 50% minimum-utilisation cleaner: HStore's `compaction_live_ratio = 0.5`, JE's - `je.cleaner.minUtilization = 50`. A sealed HStore segment is only compacted once fewer than half of its - images are live, and the background pass runs after every `checkpoint_wal_mb` of writes - ([maintenance.md](storage/maintenance.md)). Raising `compaction_live_ratio` trades write amplification for - space. -* **Crash recovery since storage format 7 (`recover`, not in the tables above).** Recovery now replays each - commit's changed keys onto the trees of the last checkpoint, which takes more CPU than pointing the roots at - pages that were already written. On a busy laptop at scale 1, the median went from 90 ms on format 6 to - 171 ms, against 123 ms for HyperGraphDB. That hasn't been rerun on an idle machine yet. +* **Member enumeration once the data outgrows the cache (`read.members` 0.13× and `read.twohop` 0.19× at + scale 4).** HyperGraphDB's atom cache keeps each link's targets as a Java array, and that cache isn't limited + by `CACHE_MB`. HStore reads a hyperedge's members from its catalog record, and at scale 4 the catalog no longer + fits in a 64 MiB node cache: each lookup reads about 780 bytes from the segment files and allocates about + 9 KiB decoding pages, against 50 bytes for HyperGraphDB. At scale 1 HStore is ahead (2.2×, ranges + overlapping). + +**Where the two are close.** + +* **Single-update commits under `sync` durability (1.2×).** Both stores pay about 4 ms for the forced write + of each commit on this machine. HStore's p99.9 is 4× lower. JE reports about 210 KiB written per commit + in this mode, against 9 KiB with `async`; the extra bytes come with the log flush the adapter adds (see + *Setup*), and we haven't established what JE writes, so don't read the `sync` bytes-written figures for + single commits as a comparison. +* **Bulk ingest at scale 1 (1.2–2.4×)** and **deletes at scale 4 (1.05–1.4×)**. All but one of these ratios + have overlapping ranges. +* **Crash recovery.** Neither store lost an acknowledged commit in any run. Recovery times overlap in every + configuration. + +**What HStore spends.** It retains more heap after ingest (189 MiB against 119 MiB at scale 1), because the +retained heap includes up to `write_buffer_mb` of nodes no checkpoint has written yet. After a large delete, its +files grow until the retained history stops referencing the replaced pages: at scale 4 they reached 1.1 GiB +before compaction brought them back to 163 MiB. Its slowest single commit at scale 4 was slower than +HyperGraphDB's, even though its p99 and p99.9 were lower. ### Inline member trees @@ -310,10 +386,9 @@ when it is read instead of being kept in the cached leaf, about 64 bytes per loo ``` benchmarks/install-hypergraphdb.sh # clone, compile and install HyperGraphDB 99485a1 and JE 5.0.73 -SCALE=1 DURABILITY=async benchmarks/run.sh -SCALE=1 DURABILITY=sync benchmarks/run.sh -SCALE=4 DURABILITY=async benchmarks/run.sh -SCALE=4 DURABILITY=async HISTORY=1 benchmarks/run.sh +SCALE=1 DURABILITY=async CACHE_MB=64 THREADS=8 RUNS=5 benchmarks/run.sh +SCALE=1 DURABILITY=sync CACHE_MB=64 THREADS=8 RUNS=5 benchmarks/run.sh +SCALE=4 DURABILITY=async CACHE_MB=64 THREADS=8 RUNS=3 benchmarks/run.sh ``` `run.sh` builds the `benchmarks` module (`-Pbenchmarks`) and runs each store in its own JVM, `RUNS` times From 5c35a525902d1b57761309b32a41deba1ea74e26 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:11:19 +0530 Subject: [PATCH 04/15] docs: add a benchmark report comparing HStore and HyperGraphDB The report describes both systems, the dataset, workloads and method, presents the results for three configurations, discusses them, and lists the threats to validity. Appendices hold every table, every run and the steps to reproduce. gen.py builds all tables, figures and quoted numbers from the raw results in benchmarks/report/results. --- .gitignore | 2 + benchmarks/report/gen.py | 606 ++++++++++++++++++ benchmarks/report/preamble.tex | 49 ++ benchmarks/report/report.tex | 40 ++ benchmarks/report/results/progress.log | 57 ++ .../report/results/s1-async/hstore-1.json | 1 + .../report/results/s1-async/hstore-2.json | 1 + .../report/results/s1-async/hstore-3.json | 1 + .../report/results/s1-async/hstore-4.json | 1 + .../report/results/s1-async/hstore-5.json | 1 + .../results/s1-async/hypergraphdb-1.json | 1 + .../results/s1-async/hypergraphdb-2.json | 1 + .../results/s1-async/hypergraphdb-3.json | 1 + .../results/s1-async/hypergraphdb-4.json | 1 + .../results/s1-async/hypergraphdb-5.json | 1 + .../report/results/s1-sync/hstore-1.json | 1 + .../report/results/s1-sync/hstore-2.json | 1 + .../report/results/s1-sync/hstore-3.json | 1 + .../report/results/s1-sync/hstore-4.json | 1 + .../report/results/s1-sync/hstore-5.json | 1 + .../results/s1-sync/hypergraphdb-1.json | 1 + .../results/s1-sync/hypergraphdb-2.json | 1 + .../results/s1-sync/hypergraphdb-3.json | 1 + .../results/s1-sync/hypergraphdb-4.json | 1 + .../results/s1-sync/hypergraphdb-5.json | 1 + .../report/results/s4-async/hstore-1.json | 1 + .../report/results/s4-async/hstore-2.json | 1 + .../report/results/s4-async/hstore-3.json | 1 + .../results/s4-async/hypergraphdb-1.json | 1 + .../results/s4-async/hypergraphdb-2.json | 1 + .../results/s4-async/hypergraphdb-3.json | 1 + benchmarks/report/sections/abstract.tex | 1 + benchmarks/report/sections/appendix.tex | 105 +++ benchmarks/report/sections/conclusion.tex | 5 + benchmarks/report/sections/discussion.tex | 18 + benchmarks/report/sections/findings.tex | 8 + benchmarks/report/sections/introduction.tex | 12 + benchmarks/report/sections/method.tex | 100 +++ .../report/sections/results-configs.tex | 12 + benchmarks/report/sections/results-sync.tex | 10 + benchmarks/report/sections/results-text.tex | 76 +++ benchmarks/report/sections/results.tex | 27 + benchmarks/report/sections/systems.tex | 92 +++ benchmarks/report/sections/validity.tex | 16 + docs/benchmark-report.pdf | Bin 0 -> 137863 bytes 45 files changed, 1262 insertions(+) create mode 100644 benchmarks/report/gen.py create mode 100644 benchmarks/report/preamble.tex create mode 100644 benchmarks/report/report.tex create mode 100644 benchmarks/report/results/progress.log create mode 100644 benchmarks/report/results/s1-async/hstore-1.json create mode 100644 benchmarks/report/results/s1-async/hstore-2.json create mode 100644 benchmarks/report/results/s1-async/hstore-3.json create mode 100644 benchmarks/report/results/s1-async/hstore-4.json create mode 100644 benchmarks/report/results/s1-async/hstore-5.json create mode 100644 benchmarks/report/results/s1-async/hypergraphdb-1.json create mode 100644 benchmarks/report/results/s1-async/hypergraphdb-2.json create mode 100644 benchmarks/report/results/s1-async/hypergraphdb-3.json create mode 100644 benchmarks/report/results/s1-async/hypergraphdb-4.json create mode 100644 benchmarks/report/results/s1-async/hypergraphdb-5.json create mode 100644 benchmarks/report/results/s1-sync/hstore-1.json create mode 100644 benchmarks/report/results/s1-sync/hstore-2.json create mode 100644 benchmarks/report/results/s1-sync/hstore-3.json create mode 100644 benchmarks/report/results/s1-sync/hstore-4.json create mode 100644 benchmarks/report/results/s1-sync/hstore-5.json create mode 100644 benchmarks/report/results/s1-sync/hypergraphdb-1.json create mode 100644 benchmarks/report/results/s1-sync/hypergraphdb-2.json create mode 100644 benchmarks/report/results/s1-sync/hypergraphdb-3.json create mode 100644 benchmarks/report/results/s1-sync/hypergraphdb-4.json create mode 100644 benchmarks/report/results/s1-sync/hypergraphdb-5.json create mode 100644 benchmarks/report/results/s4-async/hstore-1.json create mode 100644 benchmarks/report/results/s4-async/hstore-2.json create mode 100644 benchmarks/report/results/s4-async/hstore-3.json create mode 100644 benchmarks/report/results/s4-async/hypergraphdb-1.json create mode 100644 benchmarks/report/results/s4-async/hypergraphdb-2.json create mode 100644 benchmarks/report/results/s4-async/hypergraphdb-3.json create mode 100644 benchmarks/report/sections/abstract.tex create mode 100644 benchmarks/report/sections/appendix.tex create mode 100644 benchmarks/report/sections/conclusion.tex create mode 100644 benchmarks/report/sections/discussion.tex create mode 100644 benchmarks/report/sections/findings.tex create mode 100644 benchmarks/report/sections/introduction.tex create mode 100644 benchmarks/report/sections/method.tex create mode 100644 benchmarks/report/sections/results-configs.tex create mode 100644 benchmarks/report/sections/results-sync.tex create mode 100644 benchmarks/report/sections/results-text.tex create mode 100644 benchmarks/report/sections/results.tex create mode 100644 benchmarks/report/sections/systems.tex create mode 100644 benchmarks/report/sections/validity.tex create mode 100644 docs/benchmark-report.pdf diff --git a/.gitignore b/.gitignore index 497a38e..e133570 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ benchmarks/results/ Thumbs.db .mvn/wrapper/maven-wrapper.jar +benchmarks/report/gen/ +benchmarks/report/report.pdf diff --git a/benchmarks/report/gen.py b/benchmarks/report/gen.py new file mode 100644 index 0000000..63a993b --- /dev/null +++ b/benchmarks/report/gen.py @@ -0,0 +1,606 @@ +#!/usr/bin/env python3 +import json +import glob +import os +import statistics +import sys + +BENCH = sys.argv[1] +OUT = sys.argv[2] +os.makedirs(OUT, exist_ok=True) + +CONFIGS = { + "s1-async": "Scale 1, asynchronous commits", + "s1-sync": "Scale 1, synchronous commits", + "s4-async": "Scale 4, asynchronous commits", +} +ENGINES = ("hstore", "hypergraphdb") +KEYS = {"s1-async": "A", "s1-sync": "B", "s4-async": "C"} +PCT = {"p50": "med", "p99": "ptwo", "p99.9": "pthree", "max": "max"} + +THROUGHPUT = [ + ("ingest.nodes", "Insert nodes (1,000 per txn)"), + ("ingest.edges", "Insert hyperedges (1,000 per txn)"), + ("write.update", "Update node values (1,000 per txn)"), + ("latency.commit", "Single-update transactions"), + ("churn.delete", "Delete hyperedges (1,000 per txn)"), + ("churn.remove", "Remove one member (1,000 per txn)"), + ("large.ingest", "Build one giant hyperedge"), + ("read.incidence", "Incidence set of a node"), + ("read.members", "Members of a hyperedge"), + ("read.comembership", "Co-membership count of a pair"), + ("read.twohop", "Two-hop neighbourhood"), + ("read.incidence.parallel", "Incidence sets, 8 threads"), + ("latency.read", "One lookup per read txn"), + ("read.incidence.cold", "Incidence sets after reopen"), + ("read.incidence.churned", "Incidence sets after deletes"), + ("large.scan", "Scan the giant hyperedge"), + ("large.probe", "Membership probe, giant hyperedge"), + ("mixed.read", "Reads beside a 1,000 updates/s writer"), +] +TIMES = [("reopen", "Clean reopen"), ("recover", "Open after SIGKILL")] +SIZES = [("disk", "After load"), ("disk.churned", "After deletes"), ("disk.compacted", "After compaction")] +LATENCY = [("latency.read", "Read transaction"), ("latency.commit", "Commit"), + ("mixed.read", "Read beside writer"), ("mixed.write", "Commit beside readers"), + ("compaction.baseline", "Commit, no compaction"), ("compaction", "Commit during compaction")] +WRITES = ["ingest.nodes", "ingest.edges", "write.update", "latency.commit", "mixed.write", + "large.ingest", "churn.delete", "churn.remove"] +READS_IO = ["read.incidence.cold"] + + +def load(cfg): + runs = {} + for engine in ENGINES: + files = sorted(glob.glob(f"{BENCH}/{cfg}/{engine}-*.json")) + runs[engine] = [json.load(open(f)) for f in files] + return runs + + +def results(run): + return {r["workload"]: r for r in run["results"]} + + +def series(runs, workload, metric): + values = [] + for run in runs: + r = results(run).get(workload) + if r is not None: + v = metric(r) + if v is not None: + values.append(v) + return values + + +def ops(r): + return r["operations"] / (r["millis"] / 1000.0) + + +def med(values): + return statistics.median(values) if values else None + + +def fmt_rate(v): + if v >= 1e9: + return f"{v / 1e9:.2f}\\,G" + if v >= 1e6: + return f"{v / 1e6:.2f}\\,M" + if v >= 1e3: + return f"{v / 1e3:.1f}\\,k" + return f"{v:.0f}" + + +def fmt_int(v): + return f"{v:,.0f}".replace(",", "{,}") + + +def fmt_us(v): + if v >= 1000: + return f"{v / 1000:.2f}\\,ms" + if v >= 10: + return f"{v:.0f}\\,\\textmu s" + return f"{v:.1f}\\,\\textmu s" + + +def fmt_bytes(v): + if v < 0: + return "$-$" + fmt_bytes(-v) + if v >= 1 << 30: + return f"{v / (1 << 30):.2f}\\,GiB" + if v >= 1 << 20: + return f"{v / (1 << 20):.1f}\\,MiB" + if v >= 1 << 10: + return f"{v / (1 << 10):.1f}\\,KiB" + return f"{v:.0f}\\,B" + + +def fmt_ratio(v): + if v is None: + return "--" + if v >= 100: + return f"{v:.0f}$\\times$" + if v >= 10: + return f"{v:.1f}$\\times$" + return f"{v:.2f}$\\times$" + + +def agree(runs, workload): + sums = set() + for engine in ENGINES: + for run in runs[engine]: + r = results(run).get(workload) + if r is None: + return "--" + if r.get("checked") is False: + return "n/a" + sums.add(r["checksum"]) + return "yes" if len(sums) == 1 else "\\textbf{no}" + + +def rng(values, f): + return f"{f(min(values))}--{f(max(values))}" + + +macros = {} + + +def macro(name, value): + macros[name] = value + + +def main_table(cfg, runs): + rows = [] + for workload, label in THROUGHPUT: + h = series(runs["hstore"], workload, ops) + g = series(runs["hypergraphdb"], workload, ops) + if not h or not g: + continue + ratio = med(h) / med(g) + rows.append((workload, label, fmt_rate(med(h)), fmt_rate(med(g)), ratio, agree(runs, workload))) + for workload, label in TIMES: + h = series(runs["hstore"], workload, lambda r: r["millis"]) + g = series(runs["hypergraphdb"], workload, lambda r: r["millis"]) + if not h or not g: + continue + rows.append((workload, label, fmt_int(med(h)) + "\\,ms", fmt_int(med(g)) + "\\,ms", med(g) / med(h), agree(runs, workload))) + for workload, label in SIZES: + h = series(runs["hstore"], workload, lambda r: r["operations"]) + g = series(runs["hypergraphdb"], workload, lambda r: r["operations"]) + if not h or not g: + continue + rows.append((workload, "Disk, " + label.lower(), fmt_bytes(med(h)), fmt_bytes(med(g)), med(g) / med(h), "n/a")) + return rows + + +def write_main(cfg, rows): + key = KEYS[cfg] + with open(f"{OUT}/main-{cfg}.tex", "w") as f: + f.write("\\begin{tabular}{@{}llrrrc@{}}\n\\toprule\n") + f.write("Workload & Operation & HStore & HyperGraphDB & Ratio & Agree \\\\\n\\midrule\n") + section = None + for workload, label, h, g, ratio, ok in rows: + kind = "write" if workload in ("ingest.nodes", "ingest.edges", "write.update", "latency.commit", "churn.delete", "churn.remove", "large.ingest") \ + else "time" if workload in ("reopen", "recover") else "size" if workload.startswith("disk") else "read" + if section is not None and kind != section: + f.write("\\midrule\n") + section = kind + win = "\\textbf{" + fmt_ratio(ratio) + "}" if ratio >= 1 else fmt_ratio(ratio) + f.write(f"\\texttt{{{workload}}} & {label} & {h} & {g} & {win} & {ok} \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + for workload, label, h, g, ratio, ok in rows: + macro(f"ratio{key}{workload.replace('.', '')}", fmt_ratio(ratio)) + macro(f"h{key}{workload.replace('.', '')}", h) + macro(f"g{key}{workload.replace('.', '')}", g) + + +def write_ratio_figure(cfg, rows): + with open(f"{OUT}/ratios-{cfg}.tex", "w") as f: + names = [r[0] for r in rows] + f.write("\\def\\ratiosymbols{" + ",".join(names) + "}\n") + f.write("\\def\\ratiocoords{" + " ".join(f"({r[4]:.4f},{r[0]})" for r in rows) + "}\n") + + +def latency_table(cfg, runs): + key = KEYS[cfg] + with open(f"{OUT}/latency-{cfg}.tex", "w") as f: + f.write("\\begin{tabular}{@{}llrrrrrrrr@{}}\n\\toprule\n") + f.write(" & & \\multicolumn{4}{c}{HStore} & \\multicolumn{4}{c}{HyperGraphDB} \\\\\n") + f.write("\\cmidrule(lr){3-6}\\cmidrule(l){7-10}\n") + f.write("Workload & Operation & p50 & p99 & p99.9 & max & p50 & p99 & p99.9 & max \\\\\n\\midrule\n") + for workload, label in LATENCY: + cells = [] + for engine in ENGINES: + for p in ("p50", "p99", "p99.9", "max"): + v = series(runs[engine], workload, lambda r, p=p: r.get("latency", {}).get(p)) + cells.append(fmt_us(med(v)) if v else "--") + if v: + macro(f"lat{key}{engine.replace('hypergraphdb', 'hgdb')}{workload.replace('.', '')}{PCT[p]}", fmt_us(med(v))) + if all(c == "--" for c in cells): + continue + f.write(f"\\texttt{{{workload}}} & {label} & " + " & ".join(cells) + " \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + with open(f"{OUT}/cdf-{cfg}.tex", "w") as f: + for workload in ("latency.read", "latency.commit", "mixed.write"): + for engine in ENGINES: + pts = [] + for i, p in enumerate(("p50", "p99", "p99.9", "max")): + v = series(runs[engine], workload, lambda r, p=p: r.get("latency", {}).get(p)) + if v: + pts.append(f"({i},{med(v):.4f})") + name = f"lat{workload.replace('.', '')}{engine}" + f.write(f"\\def\\{name.replace('hypergraphdb', 'hgdb')}{{{' '.join(pts)}}}\n") + + +def resources_table(cfg, runs): + key = KEYS[cfg] + rows = [] + for workload in WRITES: + cells = [] + for engine in ENGINES: + per = series(runs[engine], workload, lambda r: r["bytesWritten"] / r["operations"] if r.get("bytesWritten") is not None else None) + amp = series(runs[engine], workload, lambda r: r["bytesWritten"] / r["logicalWritten"] if r.get("logicalWritten") else None) + cells.append((med(per), med(amp))) + if cells[0][0] is None or cells[1][0] is None: + continue + rows.append((workload, cells)) + macro(f"bpo{key}hstore{workload.replace('.', '')}", fmt_bytes(cells[0][0])) + macro(f"bpo{key}hgdb{workload.replace('.', '')}", fmt_bytes(cells[1][0])) + with open(f"{OUT}/writes-{cfg}.tex", "w") as f: + f.write("\\begin{tabular}{@{}lrrrrr@{}}\n\\toprule\n") + f.write(" & \\multicolumn{2}{c}{Bytes written per operation} & \\multicolumn{2}{c}{Write amplification} & \\\\\n") + f.write("\\cmidrule(lr){2-3}\\cmidrule(lr){4-5}\n") + f.write("Workload & HStore & HyperGraphDB & HStore & HyperGraphDB & Ratio \\\\\n\\midrule\n") + for workload, cells in rows: + (hb, ha), (gb, ga) = cells + f.write(f"\\texttt{{{workload}}} & {fmt_bytes(hb)} & {fmt_bytes(gb)} & {ha:.1f}$\\times$ & {ga:.1f}$\\times$ & \\textbf{{{fmt_ratio(gb / hb)}}} \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + with open(f"{OUT}/writes-fig-{cfg}.tex", "w") as f: + f.write("\\def\\writesymbols{" + ",".join(w for w, _ in rows) + "}\n") + f.write("\\def\\writeh{" + " ".join(f"({w},{c[0][0]:.1f})" for w, c in rows) + "}\n") + f.write("\\def\\writeg{" + " ".join(f"({w},{c[1][0]:.1f})" for w, c in rows) + "}\n") + + with open(f"{OUT}/memory-{cfg}.tex", "w") as f: + f.write("\\begin{tabular}{@{}lrrrr@{}}\n\\toprule\n") + f.write(" & \\multicolumn{2}{c}{Heap allocated per operation} & \\multicolumn{2}{c}{GC pause (ms)} \\\\\n") + f.write("\\cmidrule(lr){2-3}\\cmidrule(l){4-5}\n") + f.write("Workload & HStore & HyperGraphDB & HStore & HyperGraphDB \\\\\n\\midrule\n") + for workload, _ in THROUGHPUT + TIMES: + a = [series(runs[e], workload, lambda r: r["allocated"] / r["operations"] if r.get("allocated") is not None else None) for e in ENGINES] + g = [series(runs[e], workload, lambda r: r.get("gcMillis")) for e in ENGINES] + if not a[0] or not a[1]: + continue + f.write(f"\\texttt{{{workload}}} & {fmt_bytes(med(a[0]))} & {fmt_bytes(med(a[1]))} & {med(g[0]):.0f} & {med(g[1]):.0f} \\\\\n") + macro(f"alloc{key}hstore{workload.replace('.', '')}", fmt_bytes(med(a[0]))) + macro(f"alloc{key}hgdb{workload.replace('.', '')}", fmt_bytes(med(a[1]))) + f.write("\\bottomrule\n\\end{tabular}\n") + + retained = [med([run["retainedHeap"] for run in runs[e]]) for e in ENGINES] + total = [med([run["bytesWritten"] for run in runs[e]]) for e in ENGINES] + macro(f"retained{key}hstore", fmt_bytes(retained[0])) + macro(f"retained{key}hgdb", fmt_bytes(retained[1])) + macro(f"totalwritten{key}hstore", fmt_bytes(total[0])) + macro(f"totalwritten{key}hgdb", fmt_bytes(total[1])) + for e, short in zip(ENGINES, ("hstore", "hgdb")): + comp = series(runs[e], "compaction", lambda r: r.get("compactionMillis")) + recl = series(runs[e], "compaction", lambda r: r.get("reclaimed")) + lost = series(runs[e], "recover", lambda r: r.get("lost")) + ack = series(runs[e], "recover", lambda r: r.get("acknowledged")) + if comp: + macro(f"compms{key}{short}", fmt_int(med(comp)) + "\\,ms") + if recl: + macro(f"reclaimed{key}{short}", fmt_bytes(med(recl))) + if lost: + macro(f"lost{key}{short}", f"{max(lost):.0f}") + macro(f"ack{key}{short}", fmt_int(med(ack))) + mix = series(runs[e], "mixed.write", ops) + if mix: + macro(f"mixmin{key}{short}", fmt_int(min(mix))) + cold = series(runs[e], "read.incidence.cold", lambda r: r["bytesRead"] / r["operations"] if r.get("bytesRead") is not None else None) + if cold: + macro(f"coldread{key}{short}", fmt_bytes(med(cold))) + + +def runs_table(cfg, runs): + with open(f"{OUT}/runs-{cfg}.tex", "w") as f: + n = max(len(runs[e]) for e in ENGINES) + f.write("\\begin{longtable}{@{}ll" + "r" * n + "@{}}\n\\toprule\n") + f.write("Workload & Engine & " + " & ".join(f"Run {i + 1}" for i in range(n)) + " \\\\\n\\midrule\n\\endhead\n") + for workload, _ in THROUGHPUT + TIMES + SIZES: + for e, name in zip(ENGINES, ("HStore", "HyperGraphDB")): + cells = [] + for run in runs[e]: + r = results(run).get(workload) + if r is None: + cells.append("--") + elif workload in dict(TIMES): + cells.append(f"{r['millis']:.0f}\\,ms") + elif workload.startswith("disk"): + cells.append(fmt_bytes(r["operations"])) + else: + cells.append(fmt_rate(ops(r))) + cells += [""] * (n - len(cells)) + label = f"\\texttt{{{workload}}}" if e == "hstore" else "" + f.write(f"{label} & {name} & " + " & ".join(cells) + " \\\\\n") + f.write("\\addlinespace[2pt]\n") + f.write("\\bottomrule\n\\end{longtable}\n") + + +def ranges_table(cfg, runs): + with open(f"{OUT}/ranges-{cfg}.tex", "w") as f: + f.write("\\begin{tabular}{@{}lrrrr@{}}\n\\toprule\n") + f.write(" & \\multicolumn{2}{c}{HStore} & \\multicolumn{2}{c}{HyperGraphDB} \\\\\n") + f.write("\\cmidrule(lr){2-3}\\cmidrule(l){4-5}\n") + f.write("Workload & min--max & CV & min--max & CV \\\\\n\\midrule\n") + for workload, _ in THROUGHPUT: + cells = [] + for e in ENGINES: + v = series(runs[e], workload, ops) + if not v: + cells += ["--", "--"] + continue + cv = statistics.pstdev(v) / statistics.mean(v) * 100 if len(v) > 1 else 0 + cells += [rng(v, fmt_rate), f"{cv:.0f}\\%"] + f.write(f"\\texttt{{{workload}}} & " + " & ".join(cells) + " \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + + +def environment(cfg, runs): + key = KEYS[cfg] + h = runs["hstore"][0] + macro(f"nodes{key}", fmt_int(h["nodes"])) + macro(f"edges{key}", fmt_int(h["edges"])) + macro(f"incidences{key}", fmt_int(h["incidences"])) + macro(f"runs{key}", str(min(len(runs[e]) for e in ENGINES))) + + + +def metric_for(workload): + if workload in dict(TIMES): + return (lambda r: r["millis"]), True + if workload.startswith("disk"): + return (lambda r: r["operations"]), True + return ops, False + + +def summary(all_runs): + cfgs = [c for c in CONFIGS if c in all_runs] + names = {"s1-async": "S1 async", "s1-sync": "S1 sync", "s4-async": "S4 async"} + with open(f"{OUT}/summary.tex", "w") as f: + f.write("\\begin{tabular}{@{}l" + "r" * len(cfgs) + "@{}}\n\\toprule\n") + f.write("Workload & " + " & ".join(names[c] for c in cfgs) + " \\\\\n\\midrule\n") + groups = [THROUGHPUT, TIMES, SIZES] + for gi, group in enumerate(groups): + if gi: + f.write("\\midrule\n") + for workload, _ in group: + cells = [] + for c in cfgs: + runs = all_runs[c] + metric, lower = metric_for(workload) + h = series(runs["hstore"], workload, metric) + g = series(runs["hypergraphdb"], workload, metric) + if not h or not g: + cells.append("--") + continue + ratio = med(g) / med(h) if lower else med(h) / med(g) + disjoint = (max(h) < min(g) or min(h) > max(g)) + text = fmt_ratio(ratio) + if ratio >= 1: + text = "\\textbf{" + text + "}" + if not disjoint: + text += "$^{\\dagger}$" + cells.append(text) + macros[f"sum{KEYS[c]}{workload.replace('.', '')}"] = fmt_ratio(ratio) + macros[f"dis{KEYS[c]}{workload.replace('.', '')}"] = "yes" if disjoint else "no" + f.write(f"\\texttt{{{workload}}} & " + " & ".join(cells) + " \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + with open(f"{OUT}/summary-fig.tex", "w") as f: + rows = [w for w, _ in THROUGHPUT + TIMES + SIZES] + f.write("\\def\\sumsymbols{" + ",".join(rows) + "}\n") + for c in cfgs: + pts = [] + for workload in rows: + runs = all_runs[c] + metric, lower = metric_for(workload) + h = series(runs["hstore"], workload, metric) + g = series(runs["hypergraphdb"], workload, metric) + if h and g: + ratio = med(g) / med(h) if lower else med(h) / med(g) + pts.append(f"({ratio:.4f},{workload})") + f.write(f"\\def\\sum{KEYS[c]}{{{' '.join(pts)}}}\n") + +available = [] +all_runs = {} +for cfg in CONFIGS: + runs = load(cfg) + if not runs["hstore"] or not runs["hypergraphdb"]: + continue + available.append(cfg) + all_runs[cfg] = runs + rows = main_table(cfg, runs) + write_main(cfg, rows) + write_ratio_figure(cfg, rows) + latency_table(cfg, runs) + resources_table(cfg, runs) + runs_table(cfg, runs) + ranges_table(cfg, runs) + environment(cfg, runs) + +summary(all_runs) + +loads = [] +if os.path.exists(f"{BENCH}/progress.log"): + import re + for line in open(f"{BENCH}/progress.log"): + m = re.search(r"load \{ ([0-9.]+)", line) + if m: + loads.append(float(m.group(1))) +if loads: + macros["loadmin"] = f"{min(loads):.1f}" + macros["loadmax"] = f"{max(loads):.1f}" + macros["loadmed"] = f"{statistics.median(loads):.1f}" + +with open(f"{OUT}/macros.tex", "w") as f: + for name, value in sorted(macros.items()): + safe = "".join(c for c in name if c.isalpha()) + f.write(f"\\expandafter\\def\\csname {safe}\\endcsname{{{value}}}\n") +print("configs:", ", ".join(available), "macros:", len(macros)) + + +SHORT = { + "ingest.nodes": "ingest.nodes", "ingest.edges": "ingest.edges", "write.update": "write.update", + "latency.commit": "latency.commit", "churn.delete": "churn.delete", "churn.remove": "churn.remove", + "large.ingest": "large.ingest", "read.incidence": "read.incidence", "read.members": "read.members", + "read.comembership": "read.comemb.", "read.twohop": "read.twohop", "read.incidence.parallel": "read.inc.parallel", + "latency.read": "latency.read", "read.incidence.cold": "read.inc.cold", "read.incidence.churned": "read.inc.churned", + "large.scan": "large.scan", "large.probe": "large.probe", "mixed.read": "mixed.read", "reopen": "reopen", + "recover": "recover", "disk": "disk", "disk.churned": "disk.churned", "disk.compacted": "disk.compacted", +} + + +def ratio_of(runs, workload): + metric, lower = metric_for(workload) + h = series(runs["hstore"], workload, metric) + g = series(runs["hypergraphdb"], workload, metric) + if not h or not g: + return None + return med(g) / med(h) if lower else med(h) / med(g) + + +def fig_ratios(): + cfgs = [c for c in CONFIGS if c in all_runs] + rows = [w for w, _ in THROUGHPUT + TIMES + SIZES] + labels = ",".join(SHORT[w] for w in rows) + styles = {"s1-async": "fill=hs,draw=hs", "s1-sync": "fill=hs!45,draw=hs!45", "s4-async": "fill=hg,draw=hg!80!black"} + legend = {"s1-async": "S1 async", "s1-sync": "S1 sync", "s4-async": "S4 async"} + with open(f"{OUT}/fig-ratios.tex", "w") as f: + f.write("\\begin{tikzpicture}\n\\begin{axis}[report, ybar=0pt, bar width=2.6pt, width=\\textwidth, height=5.6cm,\n") + f.write(f" ymode=log, log origin=infty, ymin=0.25, ymax=200, symbolic x coords={{{labels}}}, xtick=data,\n") + f.write(" x tick label style={rotate=50, anchor=east, font=\\scriptsize\\ttfamily}, enlarge x limits=0.02,\n") + f.write(" ylabel={HStore advantage ($\\times$, log)}, ymajorgrids, yticklabel style={/pgf/number format/fixed},\n") + f.write(" ytick={0.5,1,2,5,10,20,50,100}, yticklabels={0.5,1,2,5,10,20,50,100},\n") + f.write(" legend style={at={(0.5,1.02)}, anchor=south, legend columns=-1, /tikz/every even column/.append style={column sep=6pt}}]\n") + for c in cfgs: + pts = [] + for w in rows: + r = ratio_of(all_runs[c], w) + if r is not None: + pts.append(f"({SHORT[w]},{r:.4f})") + f.write(f"\\addplot[{styles[c]}] coordinates {{{' '.join(pts)}}};\n\\addlegendentry{{{legend[c]}}}\n") + f.write("\\addplot[rule, densely dashed, sharp plot, update limits=false] coordinates {(ingest.nodes,1) (disk.compacted,1)};\n") + f.write("\\end{axis}\n\\end{tikzpicture}\n") + + +def fig_latency(): + panels = [("s1-async", "latency.commit", "Commit, async"), ("s1-sync", "latency.commit", "Commit, sync"), + ("s1-async", "latency.read", "Read transaction"), ("s1-async", "mixed.write", "Commit beside 7 readers")] + with open(f"{OUT}/fig-latency.tex", "w") as f: + for i, (c, w, title) in enumerate(panels): + if c not in all_runs: + continue + f.write("\\begin{tikzpicture}\n\\begin{axis}[report, width=0.27\\textwidth, height=3.9cm, ymode=log,\n") + f.write(" xtick={0,1,2,3}, xticklabels={p50,p99,p99.9,max}, xmin=-0.3, xmax=3.3, ymajorgrids,\n") + f.write(f" title={{\\footnotesize {title}}}, title style={{yshift=-4pt}}") + f.write(", ylabel={latency (\\textmu s)}" if i == 0 else "") + f.write(", legend style={at={(0.03,0.97)}, anchor=north west}" if i == 0 else "") + f.write("]\n") + for e, style, name in (("hstore", "hs, mark=*, mark size=1.4pt, thick", "HStore"), + ("hypergraphdb", "hg!70!black, mark=square*, mark size=1.3pt, thick", "HyperGraphDB")): + pts = [] + for k, p in enumerate(("p50", "p99", "p99.9", "max")): + v = series(all_runs[c][e], w, lambda r, p=p: r.get("latency", {}).get(p)) + if v: + pts.append(f"({k},{med(v):.3f})") + f.write(f"\\addplot[{style}] coordinates {{{' '.join(pts)}}};\n") + if i == 0: + f.write(f"\\addlegendentry{{{name}}}\n") + f.write("\\end{axis}\n\\end{tikzpicture}%\n") + if i < len(panels) - 1: + f.write("\\hfill\n") + + +def fig_writes(): + c = "s1-async" + if c not in all_runs: + return + rows = [w for w in WRITES if series(all_runs[c]["hstore"], w, lambda r: r.get("bytesWritten"))] + with open(f"{OUT}/fig-writes.tex", "w") as f: + f.write("\\begin{tikzpicture}\n\\begin{axis}[report, ybar=0pt, bar width=4pt, width=\\columnwidth, height=4.6cm,\n") + f.write(f" ymode=log, log origin=infty, symbolic x coords={{{','.join(rows)}}}, xtick=data,\n") + f.write(" x tick label style={rotate=40, anchor=east, font=\\scriptsize\\ttfamily}, enlarge x limits=0.08,\n") + f.write(" ylabel={bytes written per op}, ymajorgrids, legend style={at={(0.02,0.98)}, anchor=north west}]\n") + for e, style, name in (("hstore", "fill=hs,draw=hs", "HStore"), ("hypergraphdb", "fill=hg,draw=hg!80!black", "HyperGraphDB")): + pts = [] + for w in rows: + v = series(all_runs[c][e], w, lambda r: r["bytesWritten"] / r["operations"] if r.get("bytesWritten") is not None else None) + pts.append(f"({w},{med(v):.1f})") + f.write(f"\\addplot[{style}] coordinates {{{' '.join(pts)}}};\n\\addlegendentry{{{name}}}\n") + f.write("\\end{axis}\n\\end{tikzpicture}\n") + + +def fig_disk(): + cfgs = [c for c in ("s1-async", "s4-async") if c in all_runs] + with open(f"{OUT}/fig-disk.tex", "w") as f: + for i, c in enumerate(cfgs): + f.write("\\begin{tikzpicture}\n\\begin{axis}[report, ybar=0pt, bar width=7pt, width=0.55\\columnwidth, height=4.2cm,\n") + f.write(" symbolic x coords={load,deletes,compacted}, xtick=data, enlarge x limits=0.25, ymin=0, ymajorgrids,\n") + f.write(f" title={{\\footnotesize {'Scale 1' if c == 's1-async' else 'Scale 4'}}}, title style={{yshift=-4pt}}") + f.write(", ylabel={MiB on disk}" if i == 0 else "") + f.write(", legend style={at={(0.98,0.98)}, anchor=north east}" if i == 0 else "") + f.write("]\n") + for e, style, name in (("hstore", "fill=hs,draw=hs", "HStore"), ("hypergraphdb", "fill=hg,draw=hg!80!black", "HyperGraphDB")): + pts = [] + for w, label in (("disk", "load"), ("disk.churned", "deletes"), ("disk.compacted", "compacted")): + v = series(all_runs[c][e], w, lambda r: r["operations"]) + if v: + pts.append(f"({label},{med(v) / (1 << 20):.1f})") + f.write(f"\\addplot[{style}] coordinates {{{' '.join(pts)}}};\n") + if i == 0: + f.write(f"\\addlegendentry{{{name}}}\n") + f.write("\\end{axis}\n\\end{tikzpicture}%\n") + if i < len(cfgs) - 1: + f.write("\\hfill\n") + + +fig_ratios() +fig_latency() +fig_writes() +fig_disk() + + +def resources_summary(): + cfgs = [c for c in CONFIGS if c in all_runs] + names = {"s1-async": "S1 async", "s1-sync": "S1 sync", "s4-async": "S4 async"} + with open(f"{OUT}/resources.tex", "w") as f: + f.write("\\begin{tabular}{@{}l" + "rr" * len(cfgs) + "@{}}\n\\toprule\n") + f.write(" & " + " & ".join(f"\\multicolumn{{2}}{{c}}{{{names[c]}}}" for c in cfgs) + " \\\\\n") + f.write("".join(f"\\cmidrule(lr){{{2 + 2 * i}-{3 + 2 * i}}}" for i in range(len(cfgs))) + "\n") + f.write("Measure" + " & HS & HG" * len(cfgs) + " \\\\\n\\midrule\n") + + def row(label, fn, fmt): + cells = [] + for c in cfgs: + for e in ENGINES: + v = [x for x in (fn(run) for run in all_runs[c][e]) if x is not None] + cells.append(fmt(med(v)) if v else "--") + f.write(label + " & " + " & ".join(cells) + " \\\\\n") + + def wl(name, field): + return lambda run: results(run).get(name, {}).get(field) + + row("Retained heap after ingest", lambda run: run.get("retainedHeap"), fmt_bytes) + row("Bytes written, whole run", lambda run: run.get("bytesWritten"), fmt_bytes) + row("GC pause, whole run (ms)", lambda run: sum(r.get("gcMillis", 0) for r in run["results"]), lambda v: fmt_int(v)) + row("Reclamation time (ms)", wl("compaction", "compactionMillis"), lambda v: fmt_int(v)) + row("Space reclaimed", wl("compaction", "reclaimed"), fmt_bytes) + row("Acknowledged before kill", wl("recover", "acknowledged"), lambda v: fmt_int(v)) + lost = lambda run: results(run).get("recover", {}).get("lost") + cells = [] + for c in cfgs: + for e in ENGINES: + v = [x for x in (lost(run) for run in all_runs[c][e]) if x is not None] + cells.append(fmt_int(max(v)) if v else "--") + f.write("Acknowledged lost (max) & " + " & ".join(cells) + " \\\\\n") + f.write("\\bottomrule\n\\end{tabular}\n") + + +resources_summary() diff --git a/benchmarks/report/preamble.tex b/benchmarks/report/preamble.tex new file mode 100644 index 0000000..8610ec3 --- /dev/null +++ b/benchmarks/report/preamble.tex @@ -0,0 +1,49 @@ +\usepackage[letterpaper,margin=0.75in,columnsep=0.25in]{geometry} +\usepackage{libertinus} +\usepackage{microtype} +\usepackage{titlesec} +\titlespacing*{\paragraph}{0pt}{5pt plus 2pt minus 1pt}{0.6em} +\titleformat{\paragraph}[runin]{\normalfont\bfseries}{}{0pt}{} +\usepackage{booktabs} +\usepackage{longtable} +\usepackage{tabularx} +\usepackage{array} +\usepackage{multirow} +\usepackage{xcolor} +\usepackage{caption} +\usepackage{subcaption} +\usepackage{enumitem} +\usepackage{amsmath} +\usepackage{pgfplots} +\usepackage{tikz} +\usetikzlibrary{positioning,arrows.meta,fit,backgrounds,calc} +\pgfplotsset{compat=1.18} +\usepackage[hidelinks]{hyperref} +\usepackage{cleveref} + +\definecolor{hs}{HTML}{1F4E79} +\definecolor{hg}{HTML}{B4B4B4} +\definecolor{rule}{HTML}{555555} + +\captionsetup{font=small,labelfont=bf,skip=4pt} +\setlist{nosep,leftmargin=1.2em} +\setlength{\tabcolsep}{4pt} +\renewcommand{\arraystretch}{1.08} +\frenchspacing + +\newcommand{\wl}[1]{\texttt{#1}} +\newcommand{\M}[1]{\csname #1\endcsname} +\newcommand{\hstore}{HStore} +\newcommand{\hgdb}{HyperGraphDB} + +\pgfplotsset{ + report/.style={ + font=\footnotesize, + tick label style={font=\scriptsize}, + label style={font=\footnotesize}, + legend style={font=\scriptsize,draw=none,fill=none}, + axis line style={rule}, + tick style={rule}, + major grid style={gray!25}, + }, +} diff --git a/benchmarks/report/report.tex b/benchmarks/report/report.tex new file mode 100644 index 0000000..b99b507 --- /dev/null +++ b/benchmarks/report/report.tex @@ -0,0 +1,40 @@ +\documentclass[10pt,twocolumn]{article} +\input{preamble} +\input{gen/macros} +\input{gen/summary-fig} + +\title{\Large\bfseries HStore and HyperGraphDB:\\ a storage-level benchmark of two persistent hypergraph engines} +\author{Metacog Labs\\[2pt]\normalsize HStore 0.1.0, commit \texttt{459b8be}} +\date{October 2026} + +\begin{document} +\maketitle + +\begin{abstract} +\input{sections/abstract} +\end{abstract} + +\input{sections/introduction} +\input{sections/systems} +\input{sections/method} +\input{sections/results} +\input{sections/discussion} +\input{sections/validity} +\input{sections/conclusion} + +\bibliographystyle{plain} +\begin{thebibliography}{9}\small +\bibitem{hgdb} B.~Iordanov. HyperGraphDB: a generalized graph database. In \emph{Web-Age Information Management (WAIM) Workshops}, LNCS 6185, pages 25--36. Springer, 2010. +\bibitem{je} Oracle. \emph{Berkeley DB Java Edition Architecture}. Oracle white paper, 2006. +\bibitem{leapfrog} T.~L. Veldhuizen. Leapfrog triejoin: a simple, worst-case optimal join algorithm. In \emph{Proc.\ ICDT}, pages 96--106, 2014. +\bibitem{lmdb} H.~Chu. MDB: a memory-mapped database and backend for OpenLDAP. In \emph{Proc.\ LDAPCon}, 2011. +\bibitem{aries} C.~Mohan, D.~Haderle, B.~Lindsay, H.~Pirahesh, P.~Schwarz. ARIES: a transaction recovery method supporting fine-granularity locking and partial rollbacks using write-ahead logging. \emph{ACM TODS} 17(1):94--162, 1992. +\bibitem{lfs} M.~Rosenblum, J.~K. Ousterhout. The design and implementation of a log-structured file system. \emph{ACM TOCS} 10(1):26--52, 1992. +\end{thebibliography} + +\clearpage +\onecolumn +\appendix +\input{sections/appendix} + +\end{document} diff --git a/benchmarks/report/results/progress.log b/benchmarks/report/results/progress.log new file mode 100644 index 0000000..6dc3133 --- /dev/null +++ b/benchmarks/report/results/progress.log @@ -0,0 +1,57 @@ +s1-async hstore run 1 start 13:45:31 load { 6.56 6.15 5.48 } +s1-async hstore run 1 exit 0 took 56s +s1-async hypergraphdb run 1 start 13:46:27 load { 6.82 6.30 5.58 } +s1-async hypergraphdb run 1 exit 0 took 72s +s1-async hstore run 2 start 13:47:39 load { 7.09 6.58 5.75 } +s1-async hstore run 2 exit 0 took 57s +s1-async hypergraphdb run 2 start 13:48:36 load { 8.27 7.14 6.02 } +s1-async hypergraphdb run 2 exit 0 took 90s +s1-async hstore run 3 start 13:50:06 load { 10.63 8.52 6.68 } +s1-async hstore run 3 exit 0 took 65s +s1-async hypergraphdb run 3 start 13:51:11 load { 8.31 8.34 6.74 } +s1-async hypergraphdb run 3 exit 0 took 115s +s1-async hstore run 4 start 13:53:06 load { 12.56 10.43 7.79 } +s1-async hstore run 4 exit 0 took 58s +s1-async hypergraphdb run 4 start 13:54:04 load { 7.35 9.32 7.56 } +s1-async hypergraphdb run 4 exit 0 took 85s +s1-async hstore run 5 start 13:55:29 load { 11.56 10.06 8.00 } +s1-async hstore run 5 exit 0 took 57s +s1-async hypergraphdb run 5 start 13:56:26 load { 8.61 9.55 7.94 } +s1-async hypergraphdb run 5 exit 0 took 86s +s1-async report done +s4-async hstore run 1 start 14:13:02 load { 8.95 8.88 8.85 } +s4-async hstore run 1 exit 0 took 170s +s4-async hypergraphdb run 1 start 14:15:52 load { 11.15 10.49 9.54 } +s4-async hypergraphdb run 1 exit 0 took 2000s +s4-async hstore run 2 start 14:49:12 load { 10.38 13.12 13.22 } +s4-async hstore run 2 exit 0 took 163s +s4-async hypergraphdb run 2 start 14:51:55 load { 8.82 11.80 12.68 } +s4-async hypergraphdb run 2 exit 0 took 1341s +s4-async hstore run 3 start 15:14:16 load { 6.71 8.33 9.84 } +s4-async hstore run 3 exit 0 took 89s +s4-async hypergraphdb run 3 start 15:15:45 load { 4.40 7.20 9.26 } +s4-async hypergraphdb run 3 exit 0 took 1626s +s4-async report done +ALL DONE +s1-sync hstore run 1 start 15:43:19 load { 8.80 17.45 15.33 } +s1-sync hstore run 1 exit 0 took 87s +s1-sync hypergraphdb run 1 start 15:44:46 load { 16.71 16.65 15.17 } +s1-sync hypergraphdb run 1 exit 0 took 128s +s1-sync hstore run 2 start 15:46:54 load { 13.48 15.05 14.70 } +s1-sync hstore run 2 exit 0 took 102s +s1-sync hypergraphdb run 2 start 15:48:36 load { 12.43 15.05 14.80 } +s1-sync hypergraphdb run 2 exit 0 took 136s +s1-sync hstore run 3 start 15:50:52 load { 14.54 15.45 15.02 } +s1-sync hstore run 3 exit 0 took 80s +s1-sync hypergraphdb run 3 start 15:52:12 load { 9.84 13.60 14.34 } +s1-sync hypergraphdb run 3 exit 0 took 113s +s1-sync hstore run 4 start 15:54:05 load { 8.01 11.98 13.62 } +s1-sync hstore run 4 exit 0 took 81s +s1-sync hypergraphdb run 4 start 15:55:26 load { 6.58 10.63 12.95 } +s1-sync hypergraphdb run 4 exit 0 took 120s +s1-sync hstore run 5 start 15:57:26 load { 9.35 10.26 12.50 } +s1-sync hstore run 5 exit 0 took 80s +s1-sync hypergraphdb run 5 start 15:58:46 load { 6.86 9.29 11.93 } +s1-sync hypergraphdb run 5 exit 0 took 133s +s1-sync report done +RERUN DONE diff --git a/benchmarks/report/results/s1-async/hstore-1.json b/benchmarks/report/results/s1-async/hstore-1.json new file mode 100644 index 0000000..868be9c --- /dev/null +++ b/benchmarks/report/results/s1-async/hstore-1.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4063756288,"retainedHeap":198440360,"bytesWritten":104663286,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1110.59275,"checksum":50000,"gcCount":1,"gcMillis":15,"allocated":999467040,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2934.5035,"checksum":362757,"gcCount":3,"gcMillis":91,"allocated":3797123584,"bytesWritten":37988472,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.105625,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":132032,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":35.78425,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029624,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":30.677792,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":78692608,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":35.613166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729544,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":46.690125,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1362464},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":28.88175,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26904392,"latency":{"p50":0.209,"p99":1.167,"p99.9":5.167,"max":34.333}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":248.694083,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":453815560,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":367.043291,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":384471168,"latency":{"p50":51.167,"p99":220.0,"p99.9":375.417,"max":3087.583},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":25295439,"millis":10000.096583,"checksum":0,"gcCount":2,"gcMillis":20,"allocated":1770223712,"latency":{"p50":1.5,"p99":11.166,"p99.9":41.166,"max":50947.292},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.096583,"checksum":0,"latency":{"p50":2377.416,"p99":12467.541,"p99.9":46956.208,"max":46956.208},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":123.239917,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":165889560,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.362084,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185880,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.183792,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16048,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":116.423542,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26924008},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":36.35375,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26598080,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95658930,"millis":1000.0,"checksum":95658930},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":977.084292,"checksum":20000,"gcCount":1,"gcMillis":40,"allocated":970820536,"bytesWritten":14633585,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":842.283042,"checksum":20000,"gcCount":1,"gcMillis":69,"allocated":576755456,"bytesWritten":13094827,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":14.518,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139936,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":125084385,"millis":1000.0,"checksum":125084385},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9980.762833,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85376576,"latency":{"p50":3234.208,"p99":5642.583,"p99.9":8913.167,"max":8913.167},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.198458,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81644440,"latency":{"p50":3066.125,"p99":7852.875,"p99.9":33621.583,"max":33621.583},"checked":false,"bytesWritten":10301605,"logicalWritten":16000,"compactionMillis":144,"reclaimed":31714458},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95197702,"millis":1000.0,"checksum":95197702},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":128.143208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":123668096,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-2.json b/benchmarks/report/results/s1-async/hstore-2.json new file mode 100644 index 0000000..0c341eb --- /dev/null +++ b/benchmarks/report/results/s1-async/hstore-2.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":3995598848,"retainedHeap":197260856,"bytesWritten":101110329,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":756.365667,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":990208600,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2682.222,"checksum":362757,"gcCount":3,"gcMillis":97,"allocated":3671635576,"bytesWritten":37990098,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.383042,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134360,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":19.221208,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029432,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":34.6085,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":78692488,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":41.534167,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729904,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":35.300125,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1314496},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":45.575541,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29315392,"latency":{"p50":0.333,"p99":2.0,"p99.9":18.5,"max":805.083}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":375.880042,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":441976848,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":635.719708,"checksum":5000,"gcCount":1,"gcMillis":11,"allocated":396433248,"latency":{"p50":85.084,"p99":429.625,"p99.9":1622.708,"max":14678.5},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":22423724,"millis":10000.23475,"checksum":0,"gcCount":2,"gcMillis":38,"allocated":2016530984,"latency":{"p50":1.625,"p99":11.875,"p99.9":57.416,"max":51617.583},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.23475,"checksum":0,"latency":{"p50":2443.209,"p99":23564.542,"p99.9":47717.0,"max":47717.0},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":113.728875,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":102521152,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.466458,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186960,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.455083,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15736,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":119.029708,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26926216},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":36.214584,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26584888,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95661962,"millis":1000.0,"checksum":95661962},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":974.477625,"checksum":20000,"gcCount":1,"gcMillis":47,"allocated":922298896,"bytesWritten":14598133,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":602.26525,"checksum":20000,"gcCount":1,"gcMillis":73,"allocated":495480008,"bytesWritten":9583710,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":38.072459,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":125640,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":121520706,"millis":1000.0,"checksum":121520706},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9980.727458,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84942160,"latency":{"p50":3400.25,"p99":9814.291,"p99.9":14704.375,"max":14704.375},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9980.950125,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81384768,"latency":{"p50":3069.708,"p99":5722.208,"p99.9":7893.583,"max":7893.583},"checked":false,"bytesWritten":10293591,"logicalWritten":16000,"compactionMillis":131,"reclaimed":28158938},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95189543,"millis":1000.0,"checksum":95189543},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":163.017458,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121208816,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-3.json b/benchmarks/report/results/s1-async/hstore-3.json new file mode 100644 index 0000000..3903e60 --- /dev/null +++ b/benchmarks/report/results/s1-async/hstore-3.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4095737856,"retainedHeap":197986800,"bytesWritten":115546988,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1053.86975,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":987869088,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4429.275083,"checksum":362757,"gcCount":3,"gcMillis":123,"allocated":3691878408,"bytesWritten":40040087,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":25.017625,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":120496,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":24.439875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4018544,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":157.194666,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612536,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":33.197042,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3730240,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":32.734333,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1651592},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":50.7425,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":23692928,"latency":{"p50":0.375,"p99":2.709,"p99.9":27.75,"max":166.834}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":357.3,"checksum":20000,"gcCount":1,"gcMillis":6,"allocated":419038416,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":936.245292,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":389565280,"latency":{"p50":146.166,"p99":881.583,"p99.9":4865.833,"max":8791.416},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":18796611,"millis":10040.486084,"checksum":0,"gcCount":3,"gcMillis":120,"allocated":2109244600,"latency":{"p50":1.041,"p99":12.666,"p99.9":232.042,"max":82056.916},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10040.486084,"checksum":0,"latency":{"p50":3601.584,"p99":62396.125,"p99.9":75346.459,"max":75346.459},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":190.891167,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103018120,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":10.395958,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186144,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":39.434125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":2432240,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":373.048959,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":28532144},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":359.733791,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26597768,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97726824,"millis":1000.0,"checksum":97726824},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3303.012208,"checksum":20000,"gcCount":1,"gcMillis":125,"allocated":1039731824,"bytesWritten":23350772,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1382.23125,"checksum":20000,"gcCount":1,"gcMillis":109,"allocated":495051496,"bytesWritten":13203797,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":29.9955,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139384,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":136052276,"millis":1000.0,"checksum":136052276},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.352083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84424736,"latency":{"p50":5325.0,"p99":53032.542,"p99.9":92712.458,"max":92712.458},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9982.936,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80885240,"latency":{"p50":5412.0,"p99":34581.959,"p99.9":80797.584,"max":80797.584},"checked":false,"bytesWritten":10307535,"logicalWritten":16000,"compactionMillis":253,"reclaimed":42677082},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95202978,"millis":1000.0,"checksum":95202978},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":356.205583,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121119712,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-4.json b/benchmarks/report/results/s1-async/hstore-4.json new file mode 100644 index 0000000..85f6342 --- /dev/null +++ b/benchmarks/report/results/s1-async/hstore-4.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4089970688,"retainedHeap":200245080,"bytesWritten":109641607,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1108.050708,"checksum":50000,"gcCount":1,"gcMillis":15,"allocated":985598200,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4435.9505,"checksum":362757,"gcCount":3,"gcMillis":99,"allocated":3860986248,"bytesWritten":37955181,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.108334,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":131960,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":27.070375,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":16120,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":50.317875,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612512,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":29.462125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3730048,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":22.989209,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1580856},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":31.755125,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26915264,"latency":{"p50":0.291,"p99":0.583,"p99.9":1.375,"max":22.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":241.306667,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":432211440,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":477.44425,"checksum":5000,"gcCount":1,"gcMillis":5,"allocated":381770392,"latency":{"p50":68.042,"p99":260.375,"p99.9":360.958,"max":4704.625},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":18645214,"millis":10000.100209,"checksum":0,"gcCount":2,"gcMillis":25,"allocated":2258659360,"latency":{"p50":2.042,"p99":14.583,"p99.9":56.75,"max":35207.75},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.100209,"checksum":0,"latency":{"p50":3093.834,"p99":16699.5,"p99.9":44883.542,"max":44883.542},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":110.352542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":166203976,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":8.851125,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176640,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.086125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16120,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":137.524833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26928576},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":35.694458,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26596688,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95626418,"millis":1000.0,"checksum":95626418},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1983.6315,"checksum":20000,"gcCount":1,"gcMillis":94,"allocated":1078600728,"bytesWritten":23168100,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":748.663667,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":544472168,"bytesWritten":9592470,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":13.349,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139936,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":130133248,"millis":1000.0,"checksum":130133248},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.382667,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84995992,"latency":{"p50":5151.5,"p99":9627.042,"p99.9":10007.917,"max":10007.917},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.801833,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81324416,"latency":{"p50":5154.792,"p99":9392.5,"p99.9":16033.542,"max":16033.542},"checked":false,"bytesWritten":10281059,"logicalWritten":16000,"compactionMillis":121,"reclaimed":36784474},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95176549,"millis":1000.0,"checksum":95176549},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":112.418083,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":120433272,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-5.json b/benchmarks/report/results/s1-async/hstore-5.json new file mode 100644 index 0000000..28fb3bc --- /dev/null +++ b/benchmarks/report/results/s1-async/hstore-5.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094164992,"retainedHeap":200531568,"bytesWritten":105137547,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1518.893958,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":985770024,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3546.368917,"checksum":362757,"gcCount":3,"gcMillis":77,"allocated":3706344024,"bytesWritten":37893618,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":16.430833,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134192,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":65.399458,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":18760,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":44.663916,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":84040936,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":28.766875,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729928,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":30.294625,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1650528},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":35.596875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26911304,"latency":{"p50":0.333,"p99":0.583,"p99.9":1.792,"max":57.792}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":256.111792,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":421594592,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":865.030208,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":408607112,"latency":{"p50":150.291,"p99":469.0,"p99.9":3506.25,"max":21544.75},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":17827972,"millis":10000.165958,"checksum":0,"gcCount":2,"gcMillis":43,"allocated":2467485280,"latency":{"p50":2.083,"p99":16.625,"p99.9":63.792,"max":63347.042},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.165958,"checksum":0,"latency":{"p50":3234.958,"p99":11541.542,"p99.9":30306.5,"max":30306.5},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":103.976542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103415608,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":11.76425,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176376,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.400458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15664,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":120.964958,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26953384},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":33.046209,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26597984,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95564338,"millis":1000.0,"checksum":95564338},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1227.695584,"checksum":20000,"gcCount":1,"gcMillis":40,"allocated":1075214984,"bytesWritten":18705415,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":820.833834,"checksum":20000,"gcCount":1,"gcMillis":34,"allocated":634479424,"bytesWritten":9607370,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":13.673375,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139408,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":125586081,"millis":1000.0,"checksum":125586081},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.770583,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85422328,"latency":{"p50":5059.666,"p99":9601.666,"p99.9":10602.958,"max":10602.958},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.094083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81504920,"latency":{"p50":5352.75,"p99":9473.5,"p99.9":9900.917,"max":9900.917},"checked":false,"bytesWritten":10286347,"logicalWritten":16000,"compactionMillis":135,"reclaimed":32232282},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95181574,"millis":1000.0,"checksum":95181574},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":106.864125,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":118872736,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-1.json b/benchmarks/report/results/s1-async/hypergraphdb-1.json new file mode 100644 index 0000000..187a27c --- /dev/null +++ b/benchmarks/report/results/s1-async/hypergraphdb-1.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4198498304,"retainedHeap":124392728,"bytesWritten":3234823471,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1513.681,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":932255920,"bytesWritten":53141197,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2778.429583,"checksum":362757,"gcCount":3,"gcMillis":58,"allocated":3019900976,"bytesWritten":240007394,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":135.2295,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9949680,"bytesRead":23181792,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":46.060333,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":8794237,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":364.878916,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":160164320,"bytesRead":88678933,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":76.568125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417480,"bytesRead":44603919,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":367.729292,"checksum":5852224,"gcCount":1,"gcMillis":7,"allocated":717458144},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":335.150208,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":446144992,"latency":{"p50":1.5,"p99":16.042,"p99.9":223.333,"max":6152.834}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":2288.69475,"checksum":20000,"gcCount":3,"gcMillis":62,"allocated":2924873256,"bytesWritten":128570323,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":521.339541,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":766743616,"latency":{"p50":47.917,"p99":737.583,"p99.9":7922.916,"max":25559.959},"bytesWritten":36413008,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10212917,"millis":10000.349417,"checksum":0,"gcCount":21,"gcMillis":230,"allocated":25420805624,"latency":{"p50":3.042,"p99":52.792,"p99.9":595.458,"max":87423.5},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.349417,"checksum":0,"latency":{"p50":3349.667,"p99":80123.667,"p99.9":97707.958,"max":97707.958},"checked":false,"bytesWritten":604649487,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":925.804083,"checksum":50000,"gcCount":2,"gcMillis":23,"allocated":1620325720,"bytesWritten":92232677,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":17.090167,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":7669449,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":129.551917,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10119240,"bytesRead":41327494,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1368.52825,"checksum":1,"gcCount":2,"gcMillis":10,"allocated":2931026872},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1809.977458,"checksum":831528,"gcCount":2,"gcMillis":70,"allocated":594967960,"bytesRead":445314319,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":392387800,"millis":1000.0,"checksum":392387800},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3130.773625,"checksum":20000,"gcCount":5,"gcMillis":106,"allocated":5426157848,"bytesWritten":220849834,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1282.161875,"checksum":20000,"gcCount":3,"gcMillis":30,"allocated":3215441736,"bytesWritten":145907712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":41.606041,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9753792,"bytesRead":14659339,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":284950082,"millis":1000.0,"checksum":284950082},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.897292,"checksum":0,"gcCount":3,"gcMillis":170,"allocated":3898173400,"latency":{"p50":3171.167,"p99":27988.417,"p99.9":67654.208,"max":67654.208},"checked":false,"bytesWritten":230752466,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.537541,"checksum":0,"gcCount":4,"gcMillis":60,"allocated":4202197208,"latency":{"p50":3251.458,"p99":6651.541,"p99.9":28435.708,"max":28435.708},"checked":false,"bytesWritten":253277189,"logicalWritten":16000,"compactionMillis":40,"reclaimed":-24535345},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":213885415,"millis":1000.0,"checksum":213885415},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":81.578875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83435656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-2.json b/benchmarks/report/results/s1-async/hypergraphdb-2.json new file mode 100644 index 0000000..61d82f9 --- /dev/null +++ b/benchmarks/report/results/s1-async/hypergraphdb-2.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4210032640,"retainedHeap":124633288,"bytesWritten":3161349189,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":2005.627542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":925782544,"bytesWritten":52909220,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3798.647041,"checksum":362757,"gcCount":2,"gcMillis":58,"allocated":2987198488,"bytesWritten":236525956,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":109.109084,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9863384,"bytesRead":15186575,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":58.748458,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":13480269,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":436.989083,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":157969832,"bytesRead":99888201,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":134.086792,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418248,"bytesRead":36838574,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":438.069833,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":424480128},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":296.171542,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":458090088,"latency":{"p50":1.459,"p99":19.666,"p99.9":69.583,"max":914.042}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3912.796542,"checksum":20000,"gcCount":5,"gcMillis":50,"allocated":5083525064,"bytesWritten":212817273,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":862.323042,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":1035299024,"latency":{"p50":91.459,"p99":1082.834,"p99.9":5997.167,"max":40446.542},"bytesWritten":50194913,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":4311312,"millis":10001.007875,"checksum":0,"gcCount":9,"gcMillis":264,"allocated":9911623144,"latency":{"p50":2.666,"p99":93.0,"p99.9":2331.458,"max":169146.167},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10001.007875,"checksum":0,"latency":{"p50":102162.959,"p99":862188.167,"p99.9":898486.75,"max":898486.75},"checked":false,"bytesWritten":193918603,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1800.184667,"checksum":50000,"gcCount":3,"gcMillis":42,"allocated":2826642632,"bytesWritten":126270976,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":64.780459,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":469128,"bytesRead":20078093,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":124.262583,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9946080,"bytesRead":36530604,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1491.12475,"checksum":1,"gcCount":1,"gcMillis":10,"allocated":1528400848},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":6457.439834,"checksum":831528,"gcCount":3,"gcMillis":317,"allocated":711168688,"bytesRead":723010333,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":453242306,"millis":1000.0,"checksum":453242306},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6617.6005,"checksum":20000,"gcCount":6,"gcMillis":87,"allocated":7834882992,"bytesWritten":301605846,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2463.311125,"checksum":20000,"gcCount":3,"gcMillis":35,"allocated":3613826032,"bytesWritten":158495248,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":90.332792,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9886368,"bytesRead":21291900,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":365838705,"millis":1000.0,"checksum":365838705},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9981.265875,"checksum":0,"gcCount":6,"gcMillis":81,"allocated":7849799024,"latency":{"p50":2988.334,"p99":28888.0,"p99.9":52857.209,"max":52857.209},"checked":false,"bytesWritten":457171628,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.1375,"checksum":0,"gcCount":7,"gcMillis":222,"allocated":8883107128,"latency":{"p50":3155.459,"p99":32542.209,"p99.9":72048.5,"max":72048.5},"checked":false,"bytesWritten":532017704,"logicalWritten":16000,"compactionMillis":71,"reclaimed":23471106},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":304780122,"millis":1000.0,"checksum":304780122},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":142.6095,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":79450672,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-3.json b/benchmarks/report/results/s1-async/hypergraphdb-3.json new file mode 100644 index 0000000..c7ed181 --- /dev/null +++ b/benchmarks/report/results/s1-async/hypergraphdb-3.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4179099648,"retainedHeap":124163288,"bytesWritten":2768140389,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":4452.646209,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":913590144,"bytesWritten":53301644,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":9047.272292,"checksum":362757,"gcCount":3,"gcMillis":201,"allocated":2883991920,"bytesWritten":231265231,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":450.563209,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10087368,"bytesRead":8452457,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":207.138416,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":15266130,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":673.274667,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":165134592,"bytesRead":76508977,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":537.70375,"checksum":281832,"gcCount":1,"gcMillis":92,"allocated":18418408,"bytesRead":34991976,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":1255.271834,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":318539360},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":935.807,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":382003992,"latency":{"p50":2.041,"p99":114.208,"p99.9":1029.459,"max":16317.334}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":6885.081708,"checksum":20000,"gcCount":4,"gcMillis":155,"allocated":4032032168,"bytesWritten":184567009,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":1096.415416,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":1256273440,"latency":{"p50":138.084,"p99":1296.792,"p99.9":3386.625,"max":11593.583},"bytesWritten":60537564,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":3171949,"millis":10011.327375,"checksum":0,"gcCount":7,"gcMillis":225,"allocated":6956336736,"latency":{"p50":2.5,"p99":106.0,"p99.9":4107.667,"max":129864.292},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":7960,"millis":10011.327375,"checksum":0,"latency":{"p50":1416594.459,"p99":2368270.75,"p99.9":2375131.292,"max":2375131.292},"checked":false,"bytesWritten":125417600,"logicalWritten":254720},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":3630.943875,"checksum":50000,"gcCount":3,"gcMillis":92,"allocated":3431022744,"bytesWritten":148639128,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":56.835833,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":10563083,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":252.378875,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10282104,"bytesRead":35301899,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":2087.711459,"checksum":1,"gcCount":1,"gcMillis":96,"allocated":579319112},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":5863.692875,"checksum":831528,"gcCount":3,"gcMillis":187,"allocated":686034552,"bytesRead":740226346,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":561174783,"millis":1000.0,"checksum":561174783},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":12782.528791,"checksum":20000,"gcCount":8,"gcMillis":417,"allocated":9797202384,"bytesWritten":359806395,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3523.472125,"checksum":20000,"gcCount":3,"gcMillis":110,"allocated":3886121304,"bytesWritten":166512442,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":85.662125,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9805432,"bytesRead":17076284,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":273864518,"millis":1000.0,"checksum":273864518},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.39125,"checksum":0,"gcCount":7,"gcMillis":62,"allocated":7706728592,"latency":{"p50":4869.75,"p99":12091.5,"p99.9":17663.125,"max":17663.125},"checked":false,"bytesWritten":450856937,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.095667,"checksum":0,"gcCount":6,"gcMillis":75,"allocated":6883786200,"latency":{"p50":5114.292,"p99":10098.959,"p99.9":33702.375,"max":33702.375},"checked":false,"bytesWritten":397002316,"logicalWritten":16000,"compactionMillis":50,"reclaimed":102897546},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":302659677,"millis":1000.0,"checksum":302659677},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":105.132333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83293656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-4.json b/benchmarks/report/results/s1-async/hypergraphdb-4.json new file mode 100644 index 0000000..4fcf8fc --- /dev/null +++ b/benchmarks/report/results/s1-async/hypergraphdb-4.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4147118080,"retainedHeap":124149040,"bytesWritten":3502731577,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1559.083333,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":932219816,"bytesWritten":53252654,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4999.62525,"checksum":362757,"gcCount":3,"gcMillis":93,"allocated":2976431072,"bytesWritten":233744915,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":107.781875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9865272,"bytesRead":14778537,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":45.02925,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":2885788,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1081.91125,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":157978656,"bytesRead":73154978,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":229.792791,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418536,"bytesRead":26026248,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":579.332083,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":514102376},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":371.226916,"checksum":731528,"gcCount":1,"gcMillis":7,"allocated":442489296,"latency":{"p50":1.584,"p99":26.542,"p99.9":206.959,"max":7286.917}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3072.328667,"checksum":20000,"gcCount":3,"gcMillis":24,"allocated":3817791152,"bytesWritten":164758291,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":930.579959,"checksum":5000,"gcCount":1,"gcMillis":16,"allocated":701834656,"latency":{"p50":86.708,"p99":1785.166,"p99.9":5015.084,"max":29427.0},"bytesWritten":32951380,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7919073,"millis":10000.202375,"checksum":0,"gcCount":17,"gcMillis":193,"allocated":19778582712,"latency":{"p50":3.334,"p99":73.583,"p99.9":867.833,"max":43150.416},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.202375,"checksum":0,"latency":{"p50":4734.667,"p99":57323.625,"p99.9":77639.542,"max":77639.542},"checked":false,"bytesWritten":434278266,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1402.369917,"checksum":50000,"gcCount":1,"gcMillis":26,"allocated":1296386672,"bytesWritten":73012744,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":32.750791,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":5798223,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":93.1705,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9812512,"bytesRead":27101870,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1111.3515,"checksum":1,"gcCount":1,"gcMillis":7,"allocated":1523540288},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1851.4795,"checksum":831528,"gcCount":2,"gcMillis":41,"allocated":662172784,"bytesRead":549603950,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":429678903,"millis":1000.0,"checksum":429678903},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6858.336708,"checksum":20000,"gcCount":7,"gcMillis":135,"allocated":7965550424,"bytesWritten":314831679,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2222.34975,"checksum":20000,"gcCount":2,"gcMillis":19,"allocated":3442632936,"bytesWritten":150759895,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":127.37475,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9920520,"bytesRead":18432947,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":307510627,"millis":1000.0,"checksum":307510627},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9998.707958,"checksum":0,"gcCount":6,"gcMillis":40,"allocated":8494310944,"latency":{"p50":5174.625,"p99":10503.75,"p99.9":18631.542,"max":18631.542},"checked":false,"bytesWritten":506939419,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.853416,"checksum":0,"gcCount":9,"gcMillis":116,"allocated":10748703144,"latency":{"p50":5184.791,"p99":13317.666,"p99.9":22195.541,"max":22195.541},"checked":false,"bytesWritten":648467584,"logicalWritten":16000,"compactionMillis":53,"reclaimed":-45586996},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":286815553,"millis":1000.0,"checksum":286815553},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":157.172166,"checksum":1,"gcCount":1,"gcMillis":43,"allocated":81903144,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-5.json b/benchmarks/report/results/s1-async/hypergraphdb-5.json new file mode 100644 index 0000000..01ac637 --- /dev/null +++ b/benchmarks/report/results/s1-async/hypergraphdb-5.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4159176704,"retainedHeap":124325120,"bytesWritten":3179241547,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3376.1705,"checksum":50000,"gcCount":1,"gcMillis":23,"allocated":923708992,"bytesWritten":53039261,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4444.402709,"checksum":362757,"gcCount":2,"gcMillis":58,"allocated":2947683952,"bytesWritten":233414583,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":141.570916,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9907480,"bytesRead":16176148,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":161.184875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":9523814,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":614.668084,"checksum":20287,"gcCount":1,"gcMillis":80,"allocated":158068928,"bytesRead":90283627,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":130.780416,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18419904,"bytesRead":21007066,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":472.835958,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":632197392},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":382.333333,"checksum":731528,"gcCount":1,"gcMillis":8,"allocated":485734800,"latency":{"p50":1.666,"p99":26.291,"p99.9":181.667,"max":8425.291}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":5370.347625,"checksum":20000,"gcCount":4,"gcMillis":96,"allocated":4435258208,"bytesWritten":193403929,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":730.458208,"checksum":5000,"gcCount":1,"gcMillis":11,"allocated":986632336,"latency":{"p50":96.083,"p99":874.583,"p99.9":3184.042,"max":12076.459},"bytesWritten":46471493,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7619950,"millis":10000.488375,"checksum":0,"gcCount":16,"gcMillis":212,"allocated":18115100160,"latency":{"p50":2.875,"p99":63.666,"p99.9":1029.417,"max":57008.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.488375,"checksum":0,"latency":{"p50":4848.75,"p99":61049.792,"p99.9":110425.709,"max":110425.709},"checked":false,"bytesWritten":391536008,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":924.582209,"checksum":50000,"gcCount":1,"gcMillis":14,"allocated":1229361456,"bytesWritten":69219988,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":27.414125,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6862866,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":396.900458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10220208,"bytesRead":54541760,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":939.744292,"checksum":1,"gcCount":1,"gcMillis":20,"allocated":1038603648},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2386.74825,"checksum":831528,"gcCount":2,"gcMillis":47,"allocated":668935176,"bytesRead":559065944,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":464298411,"millis":1000.0,"checksum":464298411},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6697.696708,"checksum":20000,"gcCount":6,"gcMillis":83,"allocated":7738269704,"bytesWritten":299135623,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2466.233792,"checksum":20000,"gcCount":3,"gcMillis":48,"allocated":3391427968,"bytesWritten":149771955,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":92.983542,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9750344,"bytesRead":26409149,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":353599977,"millis":1000.0,"checksum":353599977},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.196958,"checksum":0,"gcCount":8,"gcMillis":154,"allocated":9980967552,"latency":{"p50":5330.75,"p99":38144.583,"p99.9":110508.708,"max":110508.708},"checked":false,"bytesWritten":588484574,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.304,"checksum":0,"gcCount":4,"gcMillis":189,"allocated":4751092064,"latency":{"p50":5217.833,"p99":56751.916,"p99.9":135370.0,"max":135370.0},"checked":false,"bytesWritten":286600784,"logicalWritten":16000,"compactionMillis":29,"reclaimed":13352971},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":270310331,"millis":1000.0,"checksum":270310331},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":131.530083,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":77147216,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-1.json b/benchmarks/report/results/s1-sync/hstore-1.json new file mode 100644 index 0000000..e62ca36 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hstore-1.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4097835008,"retainedHeap":188799144,"bytesWritten":113076016,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1685.761791,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":969623504,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4335.848959,"checksum":362757,"gcCount":3,"gcMillis":82,"allocated":3686535512,"bytesWritten":37424796,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":22.879458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":120784,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":37.364792,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029384,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":55.591167,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612800,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":70.229792,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729832,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":88.816375,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1527168},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":49.804917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29313944,"latency":{"p50":0.458,"p99":0.792,"p99.9":1.125,"max":39.458}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":468.6185,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":432704208,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":20969.67125,"checksum":5000,"gcCount":1,"gcMillis":10,"allocated":659274392,"latency":{"p50":4006.209,"p99":7827.625,"p99.9":16855.916,"max":43443.5},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15860952,"millis":10005.659458,"checksum":0,"gcCount":2,"gcMillis":76,"allocated":1563769760,"latency":{"p50":1.791,"p99":17.416,"p99.9":131.125,"max":57834.791},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10005.659458,"checksum":0,"latency":{"p50":6282.958,"p99":93358.667,"p99.9":114175.042,"max":114175.042},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":499.3435,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103008216,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":11.153209,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185784,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":11.064625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15856,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":165.413167,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26964864},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":52.109834,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26578264,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95090633,"millis":1000.0,"checksum":95090633},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2595.688292,"checksum":20000,"gcCount":2,"gcMillis":105,"allocated":1160337208,"bytesWritten":23317289,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":920.718417,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":608965448,"bytesWritten":13369260,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":24.888292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":125688,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":133548190,"millis":1000.0,"checksum":133548190},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.509792,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84783952,"latency":{"p50":6793.459,"p99":167889.417,"p99.9":194173.542,"max":194173.542},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.989375,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80346408,"latency":{"p50":7400.084,"p99":45918.792,"p99.9":87426.75,"max":87426.75},"checked":false,"bytesWritten":10319874,"logicalWritten":16000,"compactionMillis":274,"reclaimed":40160026},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95215939,"millis":1000.0,"checksum":95215939},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":1646.139583,"checksum":1,"gcCount":1,"gcMillis":96,"allocated":123989536,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-2.json b/benchmarks/report/results/s1-sync/hstore-2.json new file mode 100644 index 0000000..db42979 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hstore-2.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094689280,"retainedHeap":187697944,"bytesWritten":114140476,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":2252.37275,"checksum":50000,"gcCount":1,"gcMillis":32,"allocated":984738248,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":19527.900584,"checksum":362757,"gcCount":3,"gcMillis":430,"allocated":3676133056,"bytesWritten":39197247,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":138.342958,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":147888,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":72.694791,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4032192,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":273.494917,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612584,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":89.15475,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729880,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":206.976834,"checksum":5852224,"gcCount":1,"gcMillis":4,"allocated":31203928},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":166.1565,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26918048,"latency":{"p50":1.125,"p99":3.709,"p99.9":108.208,"max":1451.166}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3317.954417,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":420396288,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":20141.207292,"checksum":5000,"gcCount":1,"gcMillis":34,"allocated":610679208,"latency":{"p50":3993.958,"p99":6699.5,"p99.9":10366.125,"max":34350.875},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":12485054,"millis":10000.095875,"checksum":0,"gcCount":2,"gcMillis":131,"allocated":1934722448,"latency":{"p50":1.667,"p99":20.708,"p99.9":374.625,"max":110962.459},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.095875,"checksum":0,"latency":{"p50":38808.084,"p99":692672.584,"p99.9":725905.042,"max":725905.042},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":144.143584,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":106482096,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":13.248583,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176208,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":12.968417,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":14420728,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":166.417375,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26082544},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":66.753625,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26599824,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96875176,"millis":1000.0,"checksum":96875176},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1350.391458,"checksum":20000,"gcCount":1,"gcMillis":27,"allocated":1013180728,"bytesWritten":18851261,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2617.760875,"checksum":20000,"gcCount":1,"gcMillis":42,"allocated":613834000,"bytesWritten":17154051,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":22.030875,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139264,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":134641844,"millis":1000.0,"checksum":134641844},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.830959,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84555304,"latency":{"p50":6594.0,"p99":9681.792,"p99.9":12790.042,"max":12790.042},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.826625,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81026408,"latency":{"p50":6654.75,"p99":10008.333,"p99.9":12608.5,"max":12608.5},"checked":false,"bytesWritten":10293120,"logicalWritten":16000,"compactionMillis":173,"reclaimed":41281178},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95188450,"millis":1000.0,"checksum":95188450},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":155.749208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":119037768,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-3.json b/benchmarks/report/results/s1-sync/hstore-3.json new file mode 100644 index 0000000..be33e29 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hstore-3.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4084727808,"retainedHeap":202348264,"bytesWritten":109128526,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1609.993625,"checksum":50000,"gcCount":1,"gcMillis":11,"allocated":983709184,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3968.36825,"checksum":362757,"gcCount":3,"gcMillis":80,"allocated":3704112728,"bytesWritten":38007036,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":35.642584,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":4129872,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":36.918,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029600,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":84.113791,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612632,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":41.771167,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729928,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":46.969917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":33374256},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":55.337292,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26912288,"latency":{"p50":0.459,"p99":1.125,"p99.9":2.958,"max":46.625}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":590.236542,"checksum":20000,"gcCount":1,"gcMillis":21,"allocated":423237552,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19990.880083,"checksum":5000,"gcCount":1,"gcMillis":15,"allocated":657944792,"latency":{"p50":3993.208,"p99":6025.959,"p99.9":8836.375,"max":23176.791},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15831963,"millis":10000.138292,"checksum":0,"gcCount":1,"gcMillis":17,"allocated":1702337872,"latency":{"p50":2.167,"p99":18.75,"p99.9":83.5,"max":41337.5},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.138292,"checksum":0,"latency":{"p50":5243.833,"p99":185143.083,"p99.9":200318.792,"max":200318.792},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":188.796292,"checksum":50000,"gcCount":1,"gcMillis":38,"allocated":102554336,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":7.895542,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186408,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":10.420875,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15712,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":155.088084,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26946696},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":52.075542,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26605176,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95678706,"millis":1000.0,"checksum":95678706},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1361.068958,"checksum":20000,"gcCount":1,"gcMillis":39,"allocated":1108232176,"bytesWritten":18827829,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1188.353792,"checksum":20000,"gcCount":1,"gcMillis":58,"allocated":617488944,"bytesWritten":13354916,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":25.599958,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139480,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":129598080,"millis":1000.0,"checksum":129598080},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.319292,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85245032,"latency":{"p50":6452.292,"p99":8543.583,"p99.9":12619.833,"max":12619.833},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.649959,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81309672,"latency":{"p50":6615.584,"p99":9630.917,"p99.9":11795.459,"max":11795.459},"checked":false,"bytesWritten":10293948,"logicalWritten":16000,"compactionMillis":174,"reclaimed":36236698},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95189157,"millis":1000.0,"checksum":95189157},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":211.654875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121309320,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-4.json b/benchmarks/report/results/s1-sync/hstore-4.json new file mode 100644 index 0000000..b0156b0 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hstore-4.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4097310720,"retainedHeap":190186912,"bytesWritten":107090393,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1816.809042,"checksum":50000,"gcCount":1,"gcMillis":14,"allocated":977119888,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5935.150875,"checksum":362757,"gcCount":3,"gcMillis":312,"allocated":3734784552,"bytesWritten":39553833,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":27.3045,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":145416,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":39.163875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029648,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":74.361958,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612608,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":48.767625,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":101.404292,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":31945288},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":55.866166,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26902160,"latency":{"p50":0.458,"p99":1.208,"p99.9":3.333,"max":49.541}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":496.422708,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":434984184,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19778.965792,"checksum":5000,"gcCount":1,"gcMillis":14,"allocated":661424720,"latency":{"p50":3987.083,"p99":5964.417,"p99.9":10669.125,"max":15880.209},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":16339501,"millis":10000.196417,"checksum":0,"gcCount":2,"gcMillis":66,"allocated":2072087328,"latency":{"p50":2.125,"p99":19.458,"p99.9":58.292,"max":65105.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.196417,"checksum":0,"latency":{"p50":5465.084,"p99":53406.459,"p99.9":79125.459,"max":79125.459},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":196.462958,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":105237944,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":9.582667,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":198584,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":10.386208,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16048,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":145.675042,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26932224},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":47.678,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26599968,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97235752,"millis":1000.0,"checksum":97235752},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1899.511459,"checksum":20000,"gcCount":1,"gcMillis":63,"allocated":1056943632,"bytesWritten":19001205,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":889.410417,"checksum":20000,"gcCount":1,"gcMillis":32,"allocated":592110472,"bytesWritten":9593712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":21.133167,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139528,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":127546486,"millis":1000.0,"checksum":127546486},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.773917,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85287784,"latency":{"p50":6009.292,"p99":9048.084,"p99.9":27899.209,"max":27899.209},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9988.194209,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81664856,"latency":{"p50":6084.917,"p99":9233.542,"p99.9":11269.167,"max":11269.167},"checked":false,"bytesWritten":10296846,"logicalWritten":16000,"compactionMillis":178,"reclaimed":34181786},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95192484,"millis":1000.0,"checksum":95192484},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":156.7,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":120390992,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-5.json b/benchmarks/report/results/s1-sync/hstore-5.json new file mode 100644 index 0000000..89363f2 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hstore-5.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094689280,"retainedHeap":194565360,"bytesWritten":109086299,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1589.057958,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":987956336,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5029.174416,"checksum":362757,"gcCount":3,"gcMillis":136,"allocated":3653788048,"bytesWritten":37391021,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":22.715542,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":118384,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":77.907333,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029648,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":59.107625,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":84040912,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":57.344875,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":103.247167,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":30516600},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":44.033917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29314552,"latency":{"p50":0.375,"p99":0.75,"p99.9":1.291,"max":64.291}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":393.656709,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":424344504,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19570.959,"checksum":5000,"gcCount":1,"gcMillis":17,"allocated":659141824,"latency":{"p50":3987.709,"p99":5314.375,"p99.9":8022.083,"max":20905.166},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15064843,"millis":10000.116875,"checksum":0,"gcCount":2,"gcMillis":156,"allocated":2007090792,"latency":{"p50":1.875,"p99":18.375,"p99.9":154.583,"max":85901.917},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.116875,"checksum":0,"latency":{"p50":6782.916,"p99":352760.416,"p99.9":380877.208,"max":380877.208},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":143.381375,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":104145136,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":14.683958,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186336,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":9.676459,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16480,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":164.833667,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26949144},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":48.270959,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":30582896,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95056585,"millis":1000.0,"checksum":95056585},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2015.0605,"checksum":20000,"gcCount":1,"gcMillis":77,"allocated":1127074040,"bytesWritten":23159923,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":752.009333,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":550679384,"bytesWritten":9593712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":23.931292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":4131520,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":129555766,"millis":1000.0,"checksum":129555766},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.91275,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84446208,"latency":{"p50":6688.792,"p99":8058.667,"p99.9":9903.458,"max":9903.458},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.957084,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80637592,"latency":{"p50":6762.459,"p99":21285.042,"p99.9":41092.709,"max":41092.709},"checked":false,"bytesWritten":10296846,"logicalWritten":16000,"compactionMillis":188,"reclaimed":36191066},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95192484,"millis":1000.0,"checksum":95192484},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":190.936667,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":118746368,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-1.json b/benchmarks/report/results/s1-sync/hypergraphdb-1.json new file mode 100644 index 0000000..8e73ce7 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hypergraphdb-1.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4178051072,"retainedHeap":124149304,"bytesWritten":3721992323,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":4127.234459,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":912910584,"bytesWritten":53091245,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":8821.297625,"checksum":362757,"gcCount":3,"gcMillis":99,"allocated":2847746912,"bytesWritten":227888947,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":493.808041,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10684864,"bytesRead":17398199,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":63.76525,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":10669897,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":2158.935041,"checksum":20287,"gcCount":1,"gcMillis":120,"allocated":167434432,"bytesRead":132535742,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":160.400417,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417104,"bytesRead":21139619,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":792.564834,"checksum":5852224,"gcCount":1,"gcMillis":23,"allocated":436286784},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":399.47825,"checksum":731528,"gcCount":1,"gcMillis":9,"allocated":412433664,"latency":{"p50":1.708,"p99":30.167,"p99.9":190.958,"max":8866.875}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":6225.191584,"checksum":20000,"gcCount":5,"gcMillis":80,"allocated":5860493288,"bytesWritten":244623265,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":24668.409041,"checksum":5000,"gcCount":19,"gcMillis":261,"allocated":21760953008,"latency":{"p50":4074.417,"p99":13843.208,"p99.9":43537.958,"max":108990.292},"bytesWritten":1075985379,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":6679351,"millis":10006.044958,"checksum":0,"gcCount":11,"gcMillis":174,"allocated":14606706320,"latency":{"p50":3.292,"p99":74.291,"p99.9":1278.958,"max":86781.083},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9980,"millis":10006.044958,"checksum":0,"latency":{"p50":66256.083,"p99":391806.833,"p99.9":403035.458,"max":403035.458},"checked":false,"bytesWritten":336464382,"logicalWritten":319360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":2686.526833,"checksum":50000,"gcCount":1,"gcMillis":45,"allocated":1393864360,"bytesWritten":82315214,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":40.345375,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":7456822,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":115.290542,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9787736,"bytesRead":34558049,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1407.67525,"checksum":1,"gcCount":1,"gcMillis":7,"allocated":1734765344},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":4518.622666,"checksum":831528,"gcCount":2,"gcMillis":111,"allocated":657544944,"bytesRead":435367118,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":357685282,"millis":1000.0,"checksum":357685282},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":5935.366917,"checksum":20000,"gcCount":5,"gcMillis":99,"allocated":5858058248,"bytesWritten":240183861,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3129.150792,"checksum":20000,"gcCount":3,"gcMillis":47,"allocated":3373053120,"bytesWritten":155539450,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":417.5785,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":10183880,"bytesRead":24452678,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":284485960,"millis":1000.0,"checksum":284485960},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.061916,"checksum":0,"gcCount":5,"gcMillis":181,"allocated":6475557040,"latency":{"p50":7037.041,"p99":32009.708,"p99.9":91170.875,"max":91170.875},"checked":false,"bytesWritten":388110834,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":10042.167958,"checksum":0,"gcCount":2,"gcMillis":257,"allocated":2516714280,"latency":{"p50":10012.0,"p99":156459.792,"p99.9":212641.333,"max":212641.333},"checked":false,"bytesWritten":136620760,"logicalWritten":16000,"compactionMillis":161,"reclaimed":54590518},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":261867149,"millis":1000.0,"checksum":261867149},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":139.37975,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83845352,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-2.json b/benchmarks/report/results/s1-sync/hypergraphdb-2.json new file mode 100644 index 0000000..4e2ff93 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hypergraphdb-2.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4179099648,"retainedHeap":123900568,"bytesWritten":3361664429,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":5399.477,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":920040832,"bytesWritten":53206111,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5920.123667,"checksum":362757,"gcCount":3,"gcMillis":137,"allocated":2845827968,"bytesWritten":226333084,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":308.664375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10130000,"bytesRead":15773386,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":162.7305,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12556954,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":2834.346458,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":166875176,"bytesRead":82741054,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":677.819,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417040,"bytesRead":38074806,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":1276.558584,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":364782576},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":586.876958,"checksum":731528,"gcCount":1,"gcMillis":39,"allocated":387117352,"latency":{"p50":2.209,"p99":35.125,"p99.9":242.417,"max":39118.833}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":4465.588166,"checksum":20000,"gcCount":4,"gcMillis":38,"allocated":4839636456,"bytesWritten":214314234,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":31266.329791,"checksum":5000,"gcCount":17,"gcMillis":357,"allocated":19087441664,"latency":{"p50":4785.083,"p99":30396.166,"p99.9":75227.5,"max":100950.959},"bytesWritten":892492454,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5516860,"millis":10005.178041,"checksum":0,"gcCount":10,"gcMillis":162,"allocated":12711236552,"latency":{"p50":3.375,"p99":87.916,"p99.9":1651.875,"max":67143.75},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9730,"millis":10005.178041,"checksum":0,"latency":{"p50":817072.25,"p99":1257877.916,"p99.9":1263879.25,"max":1263879.25},"checked":false,"bytesWritten":286260937,"logicalWritten":311360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1356.406083,"checksum":50000,"gcCount":1,"gcMillis":12,"allocated":1208085328,"bytesWritten":71177557,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":45.87625,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":9264526,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":735.610292,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10487520,"bytesRead":38266217,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1015.809125,"checksum":1,"gcCount":1,"gcMillis":31,"allocated":807138512},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2798.175834,"checksum":831528,"gcCount":2,"gcMillis":72,"allocated":620955584,"bytesRead":522904025,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":450024057,"millis":1000.0,"checksum":450024057},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":9777.907333,"checksum":20000,"gcCount":6,"gcMillis":219,"allocated":6918673096,"bytesWritten":273954577,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":4317.834166,"checksum":20000,"gcCount":3,"gcMillis":102,"allocated":3596239760,"bytesWritten":162437921,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":108.886167,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9736048,"bytesRead":24532079,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":314665827,"millis":1000.0,"checksum":314665827},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.752208,"checksum":0,"gcCount":6,"gcMillis":82,"allocated":6788321800,"latency":{"p50":6701.0,"p99":16290.041,"p99.9":28686.25,"max":28686.25},"checked":false,"bytesWritten":397587794,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.349625,"checksum":0,"gcCount":3,"gcMillis":66,"allocated":3270673584,"latency":{"p50":6662.917,"p99":58786.083,"p99.9":73496.417,"max":73496.417},"checked":false,"bytesWritten":197036643,"logicalWritten":16000,"compactionMillis":30,"reclaimed":2923095},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":244562374,"millis":1000.0,"checksum":244562374},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":122.928417,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":79288360,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-3.json b/benchmarks/report/results/s1-sync/hypergraphdb-3.json new file mode 100644 index 0000000..ec51482 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hypergraphdb-3.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4164419584,"retainedHeap":124775744,"bytesWritten":3792405607,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3205.885459,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":925252392,"bytesWritten":52965684,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":6106.04425,"checksum":362757,"gcCount":4,"gcMillis":118,"allocated":3791586888,"bytesWritten":277856986,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":259.985375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9931608,"bytesRead":17624965,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":85.026959,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":11011048,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":894.31725,"checksum":20287,"gcCount":1,"gcMillis":62,"allocated":166443376,"bytesRead":87311033,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":252.771083,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418104,"bytesRead":29824905,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":471.117917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":547307088},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":559.138458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":509628824,"latency":{"p50":2.042,"p99":33.209,"p99.9":503.541,"max":10947.5}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":5555.180709,"checksum":20000,"gcCount":6,"gcMillis":56,"allocated":6001118568,"bytesWritten":250737386,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":23620.532542,"checksum":5000,"gcCount":21,"gcMillis":268,"allocated":25742855080,"latency":{"p50":4039.417,"p99":13783.875,"p99.9":30767.375,"max":48036.166},"bytesWritten":1307157472,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7311865,"millis":10000.232916,"checksum":0,"gcCount":13,"gcMillis":139,"allocated":16498292384,"latency":{"p50":3.541,"p99":82.5,"p99.9":900.875,"max":62958.041},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.232916,"checksum":0,"latency":{"p50":8346.166,"p99":135697.125,"p99.9":143285.708,"max":143285.708},"checked":false,"bytesWritten":398848855,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":843.102375,"checksum":50000,"gcCount":1,"gcMillis":8,"allocated":902454120,"bytesWritten":61828793,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":49.8525,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":4126551,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":812.016958,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10302104,"bytesRead":67938337,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1274.371084,"checksum":1,"gcCount":1,"gcMillis":17,"allocated":1736868296},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2125.078583,"checksum":831528,"gcCount":2,"gcMillis":42,"allocated":642139664,"bytesRead":364929202,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":365128112,"millis":1000.0,"checksum":365128112},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":5724.072542,"checksum":20000,"gcCount":5,"gcMillis":57,"allocated":5635580304,"bytesWritten":232822030,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2248.780208,"checksum":20000,"gcCount":3,"gcMillis":35,"allocated":3097843392,"bytesWritten":143495084,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":120.140625,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9755336,"bytesRead":18870870,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":271608003,"millis":1000.0,"checksum":271608003},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.054375,"checksum":0,"gcCount":3,"gcMillis":119,"allocated":3953286408,"latency":{"p50":6565.583,"p99":24932.666,"p99.9":56979.791,"max":56979.791},"checked":false,"bytesWritten":235966333,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.270709,"checksum":0,"gcCount":2,"gcMillis":149,"allocated":1656624560,"latency":{"p50":6431.584,"p99":56125.084,"p99.9":102672.25,"max":102672.25},"checked":false,"bytesWritten":102411753,"logicalWritten":16000,"compactionMillis":182,"reclaimed":-34558608},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":281092045,"millis":1000.0,"checksum":281092045},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":149.130833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":81760656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-4.json b/benchmarks/report/results/s1-sync/hypergraphdb-4.json new file mode 100644 index 0000000..2d418c4 --- /dev/null +++ b/benchmarks/report/results/s1-sync/hypergraphdb-4.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4196401152,"retainedHeap":125914864,"bytesWritten":3506102332,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3208.0455,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":920927880,"bytesWritten":53080818,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5160.279334,"checksum":362757,"gcCount":2,"gcMillis":63,"allocated":2848071208,"bytesWritten":228591659,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":184.510708,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10168512,"bytesRead":13308143,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":62.2635,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12565149,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":3029.223333,"checksum":20287,"gcCount":2,"gcMillis":111,"allocated":157990736,"bytesRead":361886067,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":300.565417,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18419672,"bytesRead":21435228,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":998.99625,"checksum":5852224,"gcCount":1,"gcMillis":14,"allocated":363220656},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":398.188458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":433179320,"latency":{"p50":1.75,"p99":21.666,"p99.9":202.875,"max":10247.75}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":4390.158375,"checksum":20000,"gcCount":4,"gcMillis":53,"allocated":3746926304,"bytesWritten":158936406,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":23152.664417,"checksum":5000,"gcCount":21,"gcMillis":262,"allocated":24679586104,"latency":{"p50":4032.791,"p99":12173.75,"p99.9":36008.083,"max":105156.75},"bytesWritten":1246827170,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":6239112,"millis":10003.701959,"checksum":0,"gcCount":10,"gcMillis":153,"allocated":12821412632,"latency":{"p50":2.917,"p99":55.875,"p99.9":1624.708,"max":55830.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9370,"millis":10003.701959,"checksum":0,"latency":{"p50":370695.959,"p99":824950.542,"p99.9":829667.042,"max":829667.042},"checked":false,"bytesWritten":267052906,"logicalWritten":299840},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1729.385833,"checksum":50000,"gcCount":1,"gcMillis":36,"allocated":1396199008,"bytesWritten":83393033,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":77.614042,"checksum":20000000,"gcCount":1,"gcMillis":21,"allocated":462728,"bytesRead":12603978,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":84.377959,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9777528,"bytesRead":26232269,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1042.155209,"checksum":1,"gcCount":1,"gcMillis":17,"allocated":1275720936},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2741.553083,"checksum":831528,"gcCount":2,"gcMillis":64,"allocated":628399480,"bytesRead":470702220,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":367973208,"millis":1000.0,"checksum":367973208},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":9280.499125,"checksum":20000,"gcCount":4,"gcMillis":236,"allocated":5866752496,"bytesWritten":242667288,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3553.806834,"checksum":20000,"gcCount":3,"gcMillis":142,"allocated":3354680400,"bytesWritten":153022677,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":77.936417,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9734632,"bytesRead":14816208,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":291649892,"millis":1000.0,"checksum":291649892},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.709,"checksum":0,"gcCount":3,"gcMillis":42,"allocated":3853409536,"latency":{"p50":6567.625,"p99":11249.5,"p99.9":24734.084,"max":24734.084},"checked":false,"bytesWritten":225190177,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.316791,"checksum":0,"gcCount":1,"gcMillis":70,"allocated":903971184,"latency":{"p50":6147.791,"p99":25107.958,"p99.9":89146.791,"max":89146.791},"checked":false,"bytesWritten":43499386,"logicalWritten":16000,"compactionMillis":56,"reclaimed":56467263},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":241531057,"millis":1000.0,"checksum":241531057},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":171.249583,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":82247984,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-5.json b/benchmarks/report/results/s1-sync/hypergraphdb-5.json new file mode 100644 index 0000000..03e4b1a --- /dev/null +++ b/benchmarks/report/results/s1-sync/hypergraphdb-5.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4201644032,"retainedHeap":124035896,"bytesWritten":3443793787,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3959.679042,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":922205312,"bytesWritten":52927282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":6442.541166,"checksum":362757,"gcCount":2,"gcMillis":132,"allocated":2856328288,"bytesWritten":228130562,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":250.215708,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10678864,"bytesRead":15567314,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":165.229416,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":5256430,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1818.046875,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":161413360,"bytesRead":57651866,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":262.058666,"checksum":281832,"gcCount":1,"gcMillis":23,"allocated":18418528,"bytesRead":38419940,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":849.201,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":555938152},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":449.981667,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":434484448,"latency":{"p50":2.292,"p99":30.0,"p99.9":130.667,"max":1550.875}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":7296.0895,"checksum":20000,"gcCount":5,"gcMillis":140,"allocated":5000578776,"bytesWritten":217542144,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":26592.354542,"checksum":5000,"gcCount":17,"gcMillis":450,"allocated":19239384536,"latency":{"p50":4215.334,"p99":16758.083,"p99.9":42098.75,"max":54472.042},"bytesWritten":919281207,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5288494,"millis":10000.821625,"checksum":0,"gcCount":9,"gcMillis":222,"allocated":11400995808,"latency":{"p50":3.125,"p99":90.709,"p99.9":1738.958,"max":95120.375},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9730,"millis":10000.821625,"checksum":0,"latency":{"p50":270856.75,"p99":763312.875,"p99.9":769138.792,"max":769138.792},"checked":false,"bytesWritten":245555854,"logicalWritten":311360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":2160.875084,"checksum":50000,"gcCount":1,"gcMillis":112,"allocated":1338932792,"bytesWritten":77293879,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":95.659458,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6481351,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":115.117458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9836624,"bytesRead":29672665,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":2417.289,"checksum":1,"gcCount":1,"gcMillis":70,"allocated":701707056},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":5094.897209,"checksum":831528,"gcCount":2,"gcMillis":214,"allocated":656914328,"bytesRead":585203170,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":442025040,"millis":1000.0,"checksum":442025040},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":8597.474959,"checksum":20000,"gcCount":6,"gcMillis":278,"allocated":7081182144,"bytesWritten":282932853,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2498.250125,"checksum":20000,"gcCount":3,"gcMillis":28,"allocated":3435295024,"bytesWritten":154861906,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":269.39825,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":10562744,"bytesRead":22896880,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":377008317,"millis":1000.0,"checksum":377008317},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.061208,"checksum":0,"gcCount":8,"gcMillis":247,"allocated":8978248976,"latency":{"p50":6484.125,"p99":64086.541,"p99.9":109474.833,"max":109474.833},"checked":false,"bytesWritten":523390727,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.711666,"checksum":0,"gcCount":2,"gcMillis":55,"allocated":2402045056,"latency":{"p50":6689.666,"p99":16471.541,"p99.9":39469.166,"max":39469.166},"checked":false,"bytesWritten":134989297,"logicalWritten":16000,"compactionMillis":42,"reclaimed":64973344},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":246865658,"millis":1000.0,"checksum":246865658},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":128.002167,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83804848,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-1.json b/benchmarks/report/results/s4-async/hstore-1.json new file mode 100644 index 0000000..4806426 --- /dev/null +++ b/benchmarks/report/results/s4-async/hstore-1.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4146069504,"retainedHeap":444737256,"bytesWritten":2305078324,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":8655.863708,"checksum":200000,"gcCount":5,"gcMillis":330,"allocated":5291253128,"bytesWritten":65930681,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":47689.885916,"checksum":1453427,"gcCount":46,"gcMillis":3493,"allocated":47557562320,"bytesWritten":570188101,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":138.624875,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":2716864,"bytesRead":4398210,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":608.273375,"checksum":361142,"gcCount":1,"gcMillis":20,"allocated":904828352,"bytesRead":77805459,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":51.244584,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":81941832,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":666.304792,"checksum":280131,"gcCount":1,"gcMillis":4,"allocated":1338919752,"bytesRead":124689877,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":154.431709,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":72612368},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":86.842,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29316240,"latency":{"p50":0.792,"p99":1.666,"p99.9":6.667,"max":98.625}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1707.468709,"checksum":20000,"gcCount":1,"gcMillis":195,"allocated":1027740192,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":3327.388292,"checksum":5000,"gcCount":1,"gcMillis":360,"allocated":795936576,"latency":{"p50":198.75,"p99":6682.458,"p99.9":18924.167,"max":366540.167},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":13504877,"millis":10000.27925,"checksum":0,"gcCount":2,"gcMillis":497,"allocated":2123568936,"latency":{"p50":1.75,"p99":17.792,"p99.9":271.084,"max":286433.875},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.27925,"checksum":0,"latency":{"p50":4189.0,"p99":280138.958,"p99.9":315096.75,"max":315096.75},"checked":false,"bytesWritten":53149590,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":1344.662916,"checksum":200000,"gcCount":0,"gcMillis":0,"allocated":513540200,"bytesWritten":14196352,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":85.668625,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":198736,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":42.536333,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":2429024,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":312.757208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":66246576},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":348.826125,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102190024,"bytesRead":11044181,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":173181483,"millis":1000.0,"checksum":173181483},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":38268.782666,"checksum":80000,"gcCount":24,"gcMillis":1640,"allocated":26403249120,"bytesWritten":1275527774,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":8165.195375,"checksum":20000,"gcCount":3,"gcMillis":289,"allocated":3186031680,"bytesWritten":267859019,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":28.980625,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":142240,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1196213949,"millis":1000.0,"checksum":1196213949},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.09525,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120849064,"latency":{"p50":3378.542,"p99":6834.25,"p99.9":9502.334,"max":9502.334},"checked":false,"bytesWritten":36369585,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.747083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105921192,"latency":{"p50":3117.667,"p99":5708.208,"p99.9":11465.542,"max":11465.542},"checked":false,"bytesWritten":3224983,"logicalWritten":16000,"compactionMillis":82,"reclaimed":-3298395},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170535360,"millis":1000.0,"checksum":170535360},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":402.569333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":245826960,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-2.json b/benchmarks/report/results/s4-async/hstore-2.json new file mode 100644 index 0000000..96ae9b9 --- /dev/null +++ b/benchmarks/report/results/s4-async/hstore-2.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4126146560,"retainedHeap":447346784,"bytesWritten":2216335391,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":7392.157625,"checksum":200000,"gcCount":5,"gcMillis":181,"allocated":5400270224,"bytesWritten":67052769,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":34201.176084,"checksum":1453427,"gcCount":45,"gcMillis":1813,"allocated":46420723280,"bytesWritten":476761893,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":37.612,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":150232,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":457.941125,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":903735504,"bytesRead":77793593,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":81.39825,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":82288608,"bytesRead":38939,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":1117.638417,"checksum":280131,"gcCount":1,"gcMillis":8,"allocated":1337340688,"bytesRead":124526520,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":197.334417,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":70085304},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":97.475667,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":15707192,"latency":{"p50":0.709,"p99":1.416,"p99.9":14.458,"max":8473.708}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":961.842417,"checksum":20000,"gcCount":1,"gcMillis":47,"allocated":1028033152,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":959.628791,"checksum":5000,"gcCount":1,"gcMillis":68,"allocated":812547208,"latency":{"p50":118.292,"p99":1337.625,"p99.9":3960.334,"max":68550.667},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":17624033,"millis":10000.2005,"checksum":0,"gcCount":2,"gcMillis":103,"allocated":2997405104,"latency":{"p50":1.5,"p99":11.667,"p99.9":113.541,"max":76018.459},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.2005,"checksum":0,"latency":{"p50":3906.0,"p99":52770.416,"p99.9":77999.0,"max":77999.0},"checked":false,"bytesWritten":56591276,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":940.494333,"checksum":200000,"gcCount":0,"gcMillis":0,"allocated":548690504,"bytesWritten":14196353,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":50.034667,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":176328,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":14.664667,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":18448,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":237.630916,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":66119376},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":289.004625,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102371608,"bytesRead":11044182,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":173146597,"millis":1000.0,"checksum":173146597},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":52162.505333,"checksum":80000,"gcCount":23,"gcMillis":2246,"allocated":26318688400,"bytesWritten":1275567405,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":7073.17775,"checksum":20000,"gcCount":3,"gcMillis":341,"allocated":3142927688,"bytesWritten":267899102,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":34.880458,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":135360,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1196249345,"millis":1000.0,"checksum":1196249345},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.732542,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120685768,"latency":{"p50":5429.167,"p99":19718.0,"p99.9":41085.208,"max":41085.208},"checked":false,"bytesWritten":36409371,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9988.75425,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":104978256,"latency":{"p50":4891.667,"p99":10273.834,"p99.9":13555.292,"max":13555.292},"checked":false,"bytesWritten":3224983,"logicalWritten":16000,"compactionMillis":77,"reclaimed":-3298395},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170574023,"millis":1000.0,"checksum":170574023},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":202.587958,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":129709408,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-3.json b/benchmarks/report/results/s4-async/hstore-3.json new file mode 100644 index 0000000..e07a8f9 --- /dev/null +++ b/benchmarks/report/results/s4-async/hstore-3.json @@ -0,0 +1 @@ +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4126670848,"retainedHeap":445128760,"bytesWritten":2086996896,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":3677.721292,"checksum":200000,"gcCount":5,"gcMillis":130,"allocated":5243410448,"bytesWritten":63535075,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":13731.081458,"checksum":1453427,"gcCount":45,"gcMillis":598,"allocated":46626547048,"bytesWritten":348992566,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":28.499417,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":138792,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":226.332708,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":903785744,"bytesRead":77798877,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":29.181375,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":83861472,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":444.102042,"checksum":280131,"gcCount":2,"gcMillis":7,"allocated":1338476896,"bytesRead":124641114,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":32.808583,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":75015952},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":35.78575,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29301576,"latency":{"p50":0.333,"p99":0.667,"p99.9":2.333,"max":24.333}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":308.013375,"checksum":20000,"gcCount":1,"gcMillis":8,"allocated":1038687376,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":410.589208,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":804793784,"latency":{"p50":72.709,"p99":230.167,"p99.9":337.875,"max":7560.209},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":20985931,"millis":10000.125333,"checksum":0,"gcCount":2,"gcMillis":41,"allocated":2440713152,"latency":{"p50":2.084,"p99":12.417,"p99.9":31.833,"max":53075.833},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.125333,"checksum":0,"latency":{"p50":3069.041,"p99":15425.375,"p99.9":33146.083,"max":33146.083},"checked":false,"bytesWritten":54563977,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":316.438667,"checksum":200000,"gcCount":1,"gcMillis":15,"allocated":768284560,"bytesWritten":14196352,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":28.257958,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":166848,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.875125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15544,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":126.578833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":68417632},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":54.780625,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102378224,"bytesRead":11044181,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":171110120,"millis":1000.0,"checksum":171110120},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":15947.684458,"checksum":80000,"gcCount":22,"gcMillis":156,"allocated":25761462736,"bytesWritten":1277587609,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2644.125916,"checksum":20000,"gcCount":2,"gcMillis":54,"allocated":2955254488,"bytesWritten":269901425,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":15.06525,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":128592,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1198273724,"millis":1000.0,"checksum":1198273724},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.666291,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120894504,"latency":{"p50":5381.125,"p99":10045.0,"p99.9":11065.25,"max":11065.25},"checked":false,"bytesWritten":36374959,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.794791,"checksum":0,"gcCount":1,"gcMillis":17,"allocated":105956464,"latency":{"p50":5371.833,"p99":9954.208,"p99.9":27274.458,"max":27274.458},"checked":false,"bytesWritten":3212694,"logicalWritten":16000,"compactionMillis":75,"reclaimed":-3286043},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170527468,"millis":1000.0,"checksum":170527468},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":176.874333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":231893792,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-1.json b/benchmarks/report/results/s4-async/hypergraphdb-1.json new file mode 100644 index 0000000..0b199fb --- /dev/null +++ b/benchmarks/report/results/s4-async/hypergraphdb-1.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4223139840,"retainedHeap":317396936,"bytesWritten":98493414902,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":11057.100209,"checksum":200000,"gcCount":5,"gcMillis":182,"allocated":5139836256,"bytesWritten":307491704,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":291965.123916,"checksum":1453427,"gcCount":278,"gcMillis":4408,"allocated":358790172056,"bytesWritten":11617313382,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":214.517209,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":10137496,"bytesRead":78679830,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":58.161417,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":30210646,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":7994.725958,"checksum":20109,"gcCount":5,"gcMillis":192,"allocated":160384888,"bytesRead":1590410086,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":466.921334,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18446304,"bytesRead":67146161,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":765.611167,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":657443512},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":831.992708,"checksum":719693,"gcCount":1,"gcMillis":89,"allocated":604232664,"latency":{"p50":2.333,"p99":86.083,"p99.9":648.792,"max":89946.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":19127.642041,"checksum":20000,"gcCount":19,"gcMillis":428,"allocated":25474380080,"bytesWritten":797167664,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":3613.52825,"checksum":5000,"gcCount":3,"gcMillis":34,"allocated":4623201920,"latency":{"p50":396.458,"p99":4146.833,"p99.9":13039.25,"max":160510.833},"bytesWritten":146984560,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5398244,"millis":10083.284916,"checksum":0,"gcCount":12,"gcMillis":283,"allocated":15186521936,"latency":{"p50":2.708,"p99":87.417,"p99.9":1962.708,"max":96660.083},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9040,"millis":10083.284916,"checksum":0,"latency":{"p50":700593.291,"p99":1300881.041,"p99.9":1315928.333,"max":1315928.333},"checked":false,"bytesWritten":259471056,"logicalWritten":289280},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":26327.465167,"checksum":200000,"gcCount":27,"gcMillis":624,"allocated":35281086688,"bytesWritten":1249081034,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":898.590167,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":71577263,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":104.974625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9805952,"bytesRead":60150621,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1569.177458,"checksum":1,"gcCount":2,"gcMillis":19,"allocated":2449472560},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":16533.038792,"checksum":819693,"gcCount":13,"gcMillis":694,"allocated":2484526792,"bytesRead":4444818269,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2480960849,"millis":1000.0,"checksum":2480960849},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":90354.933333,"checksum":80000,"gcCount":88,"gcMillis":2702,"allocated":115903851040,"bytesWritten":3472506444,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":11562.92975,"checksum":20000,"gcCount":12,"gcMillis":318,"allocated":15122690880,"bytesWritten":458759646,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":130.256542,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9912840,"bytesRead":55071034,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1960416709,"millis":1000.0,"checksum":1960416709},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.419542,"checksum":0,"gcCount":11,"gcMillis":267,"allocated":14444349584,"latency":{"p50":5539.917,"p99":73360.25,"p99.9":124204.334,"max":124204.334},"checked":false,"bytesWritten":466353601,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":71832,"millis":1436624.336417,"checksum":0,"gcCount":1464,"gcMillis":42727,"allocated":1935856795184,"latency":{"p50":5620.833,"p99":92771.458,"p99.9":210770.958,"max":429520.458},"checked":false,"bytesWritten":77624298288,"logicalWritten":2298624,"compactionMillis":1435611,"reclaimed":689296502},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1234929428,"millis":1000.0,"checksum":1234929428},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":1213.614166,"checksum":1,"gcCount":1,"gcMillis":240,"allocated":343651200,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-2.json b/benchmarks/report/results/s4-async/hypergraphdb-2.json new file mode 100644 index 0000000..0a3a956 --- /dev/null +++ b/benchmarks/report/results/s4-async/hypergraphdb-2.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4170186752,"retainedHeap":317886456,"bytesWritten":99483620350,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":9354.516083,"checksum":200000,"gcCount":5,"gcMillis":163,"allocated":5241088080,"bytesWritten":314110025,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":210238.717334,"checksum":1453427,"gcCount":277,"gcMillis":2567,"allocated":358952196728,"bytesWritten":11644932808,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":97.999791,"checksum":719693,"gcCount":1,"gcMillis":9,"allocated":9843232,"bytesRead":58452171,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":91.977833,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":31063693,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":441.72525,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":167257840,"bytesRead":236427141,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":128.05025,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18446648,"bytesRead":75287848,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":448.334792,"checksum":5757544,"gcCount":1,"gcMillis":15,"allocated":893363464},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":274.669041,"checksum":719693,"gcCount":1,"gcMillis":8,"allocated":632283720,"latency":{"p50":1.292,"p99":20.833,"p99.9":46.667,"max":7856.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":16131.895166,"checksum":20000,"gcCount":22,"gcMillis":313,"allocated":28541910200,"bytesWritten":902893446,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":2478.90275,"checksum":5000,"gcCount":3,"gcMillis":17,"allocated":4185081376,"latency":{"p50":297.042,"p99":3189.333,"p99.9":7892.333,"max":9908.208},"bytesWritten":134830026,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":8837808,"millis":10000.448333,"checksum":0,"gcCount":20,"gcMillis":179,"allocated":26323825472,"latency":{"p50":2.416,"p99":46.417,"p99.9":1036.416,"max":48023.833},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.448333,"checksum":0,"latency":{"p50":5951.167,"p99":74803.208,"p99.9":101465.083,"max":101465.083},"checked":false,"bytesWritten":488935039,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":15836.561167,"checksum":200000,"gcCount":26,"gcMillis":167,"allocated":35064748408,"bytesWritten":1247239413,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":141.75925,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":83940881,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":100.89425,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9893088,"bytesRead":58454078,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1209.942709,"checksum":1,"gcCount":2,"gcMillis":37,"allocated":2259939472},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":8451.375417,"checksum":819693,"gcCount":12,"gcMillis":130,"allocated":2468126504,"bytesRead":4372803781,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2493223082,"millis":1000.0,"checksum":2493223082},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":53795.552833,"checksum":80000,"gcCount":88,"gcMillis":893,"allocated":114865595624,"bytesWritten":3438056447,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":7452.05175,"checksum":20000,"gcCount":12,"gcMillis":103,"allocated":15289880776,"bytesWritten":466543950,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":128.685041,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9964680,"bytesRead":70474330,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1966539494,"millis":1000.0,"checksum":1966539494},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.674708,"checksum":0,"gcCount":13,"gcMillis":289,"allocated":16689911416,"latency":{"p50":4202.792,"p99":36697.833,"p99.9":114472.042,"max":114472.042},"checked":false,"bytesWritten":532128041,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":47407,"millis":948123.622917,"checksum":0,"gcCount":1469,"gcMillis":14892,"allocated":1942519563720,"latency":{"p50":5771.084,"p99":17430.084,"p99.9":102915.75,"max":278112.5},"checked":false,"bytesWritten":78160852933,"logicalWritten":1517024,"compactionMillis":947107,"reclaimed":753717251},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1275875206,"millis":1000.0,"checksum":1275875206},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":362.422708,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":337465688,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-3.json b/benchmarks/report/results/s4-async/hypergraphdb-3.json new file mode 100644 index 0000000..b8532cd --- /dev/null +++ b/benchmarks/report/results/s4-async/hypergraphdb-3.json @@ -0,0 +1 @@ +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4169138176,"retainedHeap":305030464,"bytesWritten":106782146000,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":5024.575625,"checksum":200000,"gcCount":5,"gcMillis":106,"allocated":5257543920,"bytesWritten":316023919,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":139094.984666,"checksum":1453427,"gcCount":277,"gcMillis":1192,"allocated":358425474016,"bytesWritten":11587526285,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":99.443583,"checksum":719693,"gcCount":1,"gcMillis":8,"allocated":9805784,"bytesRead":66184417,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":36.820875,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":27447190,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":307.779583,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":166558888,"bytesRead":200114972,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":101.352375,"checksum":280131,"gcCount":1,"gcMillis":3,"allocated":18444464,"bytesRead":65406449,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":431.065208,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":1046213184},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":271.2015,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":721272384,"latency":{"p50":1.375,"p99":18.666,"p99.9":54.417,"max":626.791}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":8045.253625,"checksum":20000,"gcCount":16,"gcMillis":92,"allocated":19600540272,"bytesWritten":612246688,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":1968.186292,"checksum":5000,"gcCount":3,"gcMillis":16,"allocated":4405458272,"latency":{"p50":261.125,"p99":1573.041,"p99.9":6972.333,"max":8054.416},"bytesWritten":144655492,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10191914,"millis":10000.155167,"checksum":0,"gcCount":25,"gcMillis":213,"allocated":32079015144,"latency":{"p50":2.25,"p99":53.791,"p99.9":782.208,"max":112488.209},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.155167,"checksum":0,"latency":{"p50":5669.5,"p99":54757.25,"p99.9":83585.542,"max":83585.542},"checked":false,"bytesWritten":615489544,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":14302.202,"checksum":200000,"gcCount":27,"gcMillis":158,"allocated":35601961664,"bytesWritten":1263499717,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":124.419625,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":83219392,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":87.879542,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9807776,"bytesRead":60364092,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":913.640083,"checksum":1,"gcCount":2,"gcMillis":12,"allocated":2445781312},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":6536.718458,"checksum":819693,"gcCount":12,"gcMillis":88,"allocated":2420011144,"bytesRead":4319194352,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2342039349,"millis":1000.0,"checksum":2342039349},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":43088.326916,"checksum":80000,"gcCount":82,"gcMillis":457,"allocated":107445732936,"bytesWritten":3206428310,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":5599.065542,"checksum":20000,"gcCount":10,"gcMillis":58,"allocated":13513654672,"bytesWritten":411703805,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":87.076334,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9811896,"bytesRead":58593087,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":2039882038,"millis":1000.0,"checksum":2039882038},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9988.087875,"checksum":0,"gcCount":19,"gcMillis":92,"allocated":24494565800,"latency":{"p50":6399.333,"p99":13578.125,"p99.9":20564.083,"max":20564.083},"checked":false,"bytesWritten":779504093,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":56286,"millis":1125704.643708,"checksum":0,"gcCount":1585,"gcMillis":16343,"allocated":2094168474232,"latency":{"p50":4561.833,"p99":2853102.167,"p99.9":4832843.833,"max":5221838.708},"checked":false,"bytesWritten":85603391546,"logicalWritten":1801152,"compactionMillis":1124686,"reclaimed":743592743},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1225504132,"millis":1000.0,"checksum":1225504132},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":545.431875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":338776104,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/sections/abstract.tex b/benchmarks/report/sections/abstract.tex new file mode 100644 index 0000000..a870720 --- /dev/null +++ b/benchmarks/report/sections/abstract.tex @@ -0,0 +1 @@ +We compare HStore, a persistent hypergraph storage engine built on copy-on-write counted B+trees, with HyperGraphDB~1.4 on Berkeley DB Java Edition. Both systems run 26 storage-level workloads on the same skewed synthetic hypergraph, with up to 1.45 million incidences and equal 64\,MiB caches, in three configurations: two data sizes and two commit modes. Every read result is cross-checked between the systems. At scale 1 with asynchronous commits, HStore enumerates incidence sets \M{ratioAreadincidence} faster, counts shared hyperedges \M{ratioAreadcomembership} faster, applies batched updates \M{ratioAwriteupdate} faster, and needs \M{ratioAdisk} less disk. These leads come from storing incidence sets as small sorted trees, and from commits that log only the keys they changed while a background checkpoint writes pages. When the data is four times larger than the cache, HStore keeps most of these leads but falls to \M{sumCreadmembers} of HyperGraphDB's speed on member enumeration, because its member lookups miss the node cache. With synchronous commits, both systems are bound by the cost of one forced write per commit. We also report latency percentiles, bytes written, heap use, space reclamation and crash recovery, the variation over runs, and a durability setting in HyperGraphDB that, left unchecked, makes its synchronous commits unsynced. diff --git a/benchmarks/report/sections/appendix.tex b/benchmarks/report/sections/appendix.tex new file mode 100644 index 0000000..e82e28e --- /dev/null +++ b/benchmarks/report/sections/appendix.tex @@ -0,0 +1,105 @@ +\section{Full results} +\label{app:full} + +Each cell is the median over runs. Throughput is in operations per second (k $=10^3$, M $=10^6$, G $=10^9$). Ratios above 1 favour HStore. ``Agree'' is \emph{yes} when both systems returned the same checksum in every run. + +\subsection{Scale 1, asynchronous commits} + +\begin{center}\footnotesize\input{gen/main-s1-async}\end{center} + +\begin{center}\footnotesize Latency (median of each percentile over runs)\\[2pt]\input{gen/latency-s1-async}\end{center} + +\begin{center}\footnotesize\input{gen/writes-s1-async}\end{center} + +\begin{center}\footnotesize\input{gen/memory-s1-async}\end{center} + +\subsection{Scale 1, synchronous commits} + +\begin{center}\footnotesize\input{gen/main-s1-sync}\end{center} + +\begin{center}\footnotesize Latency (median of each percentile over runs)\\[2pt]\input{gen/latency-s1-sync}\end{center} + +\begin{center}\footnotesize\input{gen/writes-s1-sync}\end{center} + +\begin{center}\footnotesize\input{gen/memory-s1-sync}\end{center} + +\subsection{Scale 4, asynchronous commits} + +\begin{center}\footnotesize\input{gen/main-s4-async}\end{center} + +\begin{center}\footnotesize Latency (median of each percentile over runs)\\[2pt]\input{gen/latency-s4-async}\end{center} + +\begin{center}\footnotesize\input{gen/writes-s4-async}\end{center} + +\begin{center}\footnotesize\input{gen/memory-s4-async}\end{center} + +\clearpage +\section{Run-to-run variability} +\label{app:variability} + +The tables below give the minimum and maximum over runs and the coefficient of variation (standard deviation over mean) of every throughput workload, followed by the value of every run. + +\subsection{Scale 1, asynchronous commits} +\begin{center}\footnotesize\input{gen/ranges-s1-async}\end{center} +{\footnotesize\input{gen/runs-s1-async}} + +\subsection{Scale 1, synchronous commits} +\begin{center}\footnotesize\input{gen/ranges-s1-sync}\end{center} +{\footnotesize\input{gen/runs-s1-sync}} + +\subsection{Scale 4, asynchronous commits} +\begin{center}\footnotesize\input{gen/ranges-s4-async}\end{center} +{\footnotesize\input{gen/runs-s4-async}} + +\clearpage +\section{Reproducing the results} +\label{app:repro} + +The harness is the \texttt{benchmarks} module of the HStore repository. HyperGraphDB and JE are built from source at the commits named in \cref{tab:config}. + +\begin{verbatim} +benchmarks/install-hypergraphdb.sh # builds HyperGraphDB 99485a1 and JE 5.0.73 +SCALE=1 DURABILITY=async CACHE_MB=64 RUNS=5 THREADS=8 benchmarks/run.sh +SCALE=1 DURABILITY=sync CACHE_MB=64 RUNS=5 THREADS=8 benchmarks/run.sh +SCALE=4 DURABILITY=async CACHE_MB=64 RUNS=3 THREADS=8 benchmarks/run.sh +\end{verbatim} + +\texttt{run.sh} builds the module, then runs each system \texttt{RUNS} times in alternation, one JVM per run, with these flags: + +\begin{verbatim} +java -Xms4g -Xmx4g -XX:+UseParallelGC \ + --add-opens java.base/java.lang=ALL-UNNAMED \ + --add-opens java.base/java.lang.ref=ALL-UNNAMED \ + --add-opens java.base/java.util=ALL-UNNAMED \ + io.hstore.bench.Comparison run --store \ + --scale --durability --threads 8 --history 64 --cache-mb 64 +\end{verbatim} + +Each run writes one JSON file with every measurement in this report. \texttt{Comparison report} turns a set of them into Markdown tables. The tables, figures and quoted numbers in this report were generated from the same files by \texttt{benchmarks/report/gen.py}, without manual transcription. The dataset statistics in \cref{tab:dataset} are printed by \texttt{io.hstore.bench.DatasetStats}, run with the same classpath. The raw files are in \texttt{benchmarks/report/results}, and the report is rebuilt with: + +\begin{verbatim} +cd benchmarks/report +python3 gen.py results gen +tectonic report.tex +\end{verbatim} + +\section{HStore settings} +\label{app:settings} + +\begin{center}\footnotesize +\begin{tabular}{@{}llr@{}} +\toprule +Setting & Meaning & Value \\ +\midrule +\texttt{page\_size} & tree node page size & 16\,KiB \\ +segment size & pages per segment file & 16,384 (256\,MiB) \\ +\texttt{node\_cache\_mb} & decoded node cache & 64\,MiB (set) \\ +\texttt{write\_buffer\_mb} & cap on unwritten tree nodes & 64\,MiB \\ +\texttt{checkpoint\_wal\_mb} & log or page bytes that trigger a checkpoint & 256\,MiB \\ +\texttt{compaction\_live\_ratio} & compact segments below this live fraction & 0.5 \\ +\texttt{history\_limit} & generations kept for time travel & 64 \\ +\texttt{wal\_mode} & log page references, not page images & references \\ +maintenance interval & background checkpoint and compaction pass & 500\,ms \\ +\bottomrule +\end{tabular} +\end{center} diff --git a/benchmarks/report/sections/conclusion.tex b/benchmarks/report/sections/conclusion.tex new file mode 100644 index 0000000..561ea6f --- /dev/null +++ b/benchmarks/report/sections/conclusion.tex @@ -0,0 +1,5 @@ +\section{Conclusion} + +At the storage level, HStore is faster than HyperGraphDB on the operations that hypergraph workloads repeat most: finding the hyperedges that contain a node, intersecting those sets, and updating values. It writes and stores far fewer bytes doing so. Its design gives a clear reason for each of these results, and the same design explains its main weakness. When the catalog does not fit in the node cache, member enumeration falls well behind HyperGraphDB, whose atom cache keeps links as arrays outside any byte budget. Synchronous commits are bound by the device in both systems. Crash recovery lost no acknowledged commit in either system. + +The numbers come from one laptop under load, and \cref{sec:validity} lists what that limits. The harness, the adapters, the raw results and the script that produced every table in this report are published with HStore, so each result can be checked or repeated on other hardware. diff --git a/benchmarks/report/sections/discussion.tex b/benchmarks/report/sections/discussion.tex new file mode 100644 index 0000000..689e733 --- /dev/null +++ b/benchmarks/report/sections/discussion.tex @@ -0,0 +1,18 @@ +\section{Discussion} +\label{sec:discussion} + +\paragraph{Incidence and intersection.} An incidence lookup in HStore reads one entry of the reverse index. For most atoms the whole incidence set is stored inline in that entry, and for hubs it is the root of a small tree. The visitor API hands each hyperedge id to the caller without allocating, which is why \wl{read.incidence} allocates about one byte per lookup. HyperGraphDB opens a JE cursor on a duplicate set, decodes 16-byte handles, and builds a result object per lookup. With a 64\,MiB JE cache, part of each lookup also misses the cache and reads from the log files (\cref{app:full}). Co-membership adds a second effect. HStore intersects two sorted incidence trees and skips every subtree whose key range cannot match, so the cost follows the smaller set. HyperGraphDB plans \texttt{hg.and} as a general conjunctive query over two result sets. The two-hop workload combines both lookups, so it inherits both advantages. + +\paragraph{Member enumeration, and where HyperGraphDB leads.} HyperGraphDB keeps a link's targets as a Java array in its atom cache, so enumerating the members of a cached link is an array loop with almost no allocation. HStore reads a hyperedge's members from its record in the catalog tree: from the leaf stored inline in the record for a small edge, or from the edge's own membership tree. At scale 1 the catalog fits in HStore's node cache, and HStore is faster at the median, though the run ranges overlap. At scale 4 it does not. Member lookups then miss the cache and decode catalog pages again, which costs \M{allocChstorereadmembers} of allocation per lookup on average, while HyperGraphDB's atom cache, which the 64\,MiB budget does not limit, still holds the links. This is the clearest weakness this study found in HStore. Two directions would address it: smaller catalog pages, or a cache of decoded edge records separate from the node cache. Both trade memory for this access pattern, and neither is in the version measured here. + +\paragraph{Small writes.} A single-update commit in HStore appends a log record of a few hundred bytes and publishes new roots in memory. The tree pages are written later, by a checkpoint that writes each changed node once for all the commits since the previous one. A JE commit appends to its log a new version of each changed record and of the B+tree internal nodes above it, in every JE database the change touches. The difference shows directly in bytes written per update (\cref{fig:writes}). Under asynchronous commits, the per-commit cost that remains in HStore is the commit itself: copying the changed tree paths in memory and publishing a generation. That cost is close to JE's at the median, and the gap opens in the tail. + +\paragraph{Tail latency.} HStore readers pin an immutable generation and never wait for a writer, a checkpoint or a compaction. Writers wait only for the commit lock, which a checkpoint takes just to swap stored references into the head. HyperGraphDB reads run inside JE transactions, which take record locks, and JE's cleaner and checkpointer compete for the same latches and log. In 15 of the 18 latency measurements (six workloads in three configurations), the gap between the two systems is wider at p99 and p99.9 than at the median. The three exceptions are the \wl{mixed} writer under synchronous commits, where HyperGraphDB fell behind its schedule and the gap is largest at the median, and single-update commits and the \wl{mixed} writer at scale 4, where HStore's advantage stays between 1.3 and 2.5 times across percentiles. The single slowest commit at scale 4 was HStore's. + +\paragraph{Giant hyperedges.} HStore bulk-builds a large membership tree bottom-up in one pass, and a membership probe is one descent of that tree. HyperGraphDB stores the giant link as one record holding 50,000 handles (200,000 at scale 4). A membership probe goes through the node's incidence set instead. + +\paragraph{Footprint and reclamation.} Several layout choices keep HStore's files small: 8-byte ids stored as varint gaps instead of 16-byte handles, node images packed into 64-byte units, and small member trees and incidence sets stored inline in their parent record. HStore's log is also truncated at each checkpoint. A JE log keeps every superseded version until the cleaner copies the live entries out of a file and deletes it, and at scale 4 cleaning dominated HyperGraphDB's bytes written. HStore's weak point is the period right after a large delete. Pages replaced by the deletes stay on disk while any of the 64 retained generations can reach them, so the files grow first and shrink only after the history moves on, here at the restart that follows. An application that deletes heavily and needs its disk space back quickly would set a smaller \texttt{history\_limit}. + +\paragraph{Opening and recovery.} After a clean shutdown, HStore reads one catalog image and finds nothing in the log to replay. HyperGraphDB's open takes longer and allocates far more heap: \M{allocAhgdbreopen} per reopen at scale 1, against \M{allocAhstorereopen} for HStore. After a crash, HStore replays the logged key changes of every commit since its last checkpoint, and JE replays its log from its last checkpoint. HStore's replay cost grows with the number of commits since the last checkpoint, which the write buffer and the checkpoint thresholds bound. The recovery times of the two systems overlap in our runs, so we do not claim a difference. + +\paragraph{What HStore spends.} HStore retains more heap after ingest than HyperGraphDB (\cref{tab:memory}). The retained heap includes nodes not yet written by a checkpoint, up to the 64\,MiB write buffer, and the node cache, whose 64\,MiB budget is an estimate that runs below the true heap size. HStore also allocates slightly more per inserted node at scale 1 and pays more GC time during ingest there, and more GC time during the deletes at scale 4. Its worst single commit at scale 4 was slower than HyperGraphDB's, even though its p99 and p99.9 were lower. diff --git a/benchmarks/report/sections/findings.tex b/benchmarks/report/sections/findings.tex new file mode 100644 index 0000000..ac11265 --- /dev/null +++ b/benchmarks/report/sections/findings.tex @@ -0,0 +1,8 @@ +\begin{itemize} +\item \textbf{Incidence-based reads} are faster in HStore in every configuration: \M{sumCreadincidence} to \M{sumAreadincidence} for incidence sets, \M{sumCreadcomembership} to \M{sumBreadcomembership} for co-membership counts, and \M{sumCreadincidencecold} to \M{sumAreadincidencecold} directly after a reopen. +\item \textbf{Writes} cost HStore far fewer bytes. Single-update commits write \M{bpoAhstorelatencycommit} against \M{bpoAhgdblatencycommit}, and batched updates run \M{sumBwriteupdate} to \M{sumCwriteupdate} faster. Bulk insertion is closer: between \M{sumBingestedges} and \M{sumCingestedges} for hyperedges. +\item \textbf{Member enumeration} is HStore's weakness once the data outgrows its node cache: \M{sumCreadmembers} at scale 4, and \M{sumCreadtwohop} for two-hop neighbourhoods, which depend on it. +\item \textbf{Tail latency.} HStore's p99 and p99.9 latencies are lower than HyperGraphDB's in every latency workload and configuration, by 1.3 to 69 times. The single slowest commit at scale 4 was HStore's. +\item \textbf{Disk.} HStore's files are \M{sumBdisk} to \M{sumCdisk} smaller after loading. After deletes they first grow while retained history still references the replaced pages. +\item \textbf{Recovery.} Neither system lost an acknowledged commit after SIGKILL. Recovery times overlap. +\end{itemize} diff --git a/benchmarks/report/sections/introduction.tex b/benchmarks/report/sections/introduction.tex new file mode 100644 index 0000000..718ed04 --- /dev/null +++ b/benchmarks/report/sections/introduction.tex @@ -0,0 +1,12 @@ +\section{Introduction} + +Many datasets record facts that involve more than two things at once. An insurance claim joins a patient, a provider, a drug and a pharmacy. A paper joins its authors, and a meeting joins its participants. A hypergraph stores each such fact as one hyperedge over all of its participants. A property graph has to break the same fact into a new node and one edge per participant, and a relational schema into a join table. Queries then ask hypergraph questions: which facts involve this entity, which facts involve both of these entities, and what lies two hops away. + +HyperGraphDB~\cite{hgdb} is the established open-source hypergraph database for the JVM. It stores atoms and links in Berkeley DB Java Edition (JE), a general-purpose log-structured key-value store. HStore is a newer engine built for this data model. Its incidence sets and hyperedge memberships are counted B+trees, and its storage, transaction and recovery design is described in \cref{sec:systems}. + +This report compares the two systems at the storage level, on the same machine and data, with the same cache budget. We measure what an embedded application sees: throughput of loads, updates, deletes and reads; per-operation latency, including tail latency while another thread writes; behaviour of giant hyperedges; disk footprint before and after deletes and space reclamation; time to recover after a crash; and the bytes written, heap allocated and GC time behind each result. Every read result is cross-checked between the two systems. + +\Cref{sec:method} describes the dataset, workloads and measurement method. \Cref{sec:results} presents the results, and \cref{sec:discussion} explains them in terms of each system's design. \Cref{sec:validity} lists the limits of this study. Full tables, per-run values and the steps to reproduce every number are in the appendices. + +\paragraph{Summary of findings.} +\input{sections/findings} diff --git a/benchmarks/report/sections/method.tex b/benchmarks/report/sections/method.tex new file mode 100644 index 0000000..669f91b --- /dev/null +++ b/benchmarks/report/sections/method.tex @@ -0,0 +1,100 @@ +\section{Benchmark design} +\label{sec:method} + +\subsection{Dataset} + +The dataset is generated from a fixed seed (20260904), so both systems load exactly the same graph. At scale $s$ it has $50{,}000\,s$ nodes and $100{,}000\,s$ hyperedges. The cardinality of a hyperedge follows a geometric tail: it starts at 2 and each further member is added with probability 0.62, up to 32, for a mean of 3.6. Members are drawn as $\lfloor n\,u^{2.2}\rfloor$ for uniform $u\in[0,1)$ over $n$ nodes. Low-numbered nodes therefore become hubs with large incidence sets, as in co-authorship, claims or citation data. Hyperedges never repeat a member. + +\begin{table}[t] +\centering\footnotesize +\caption{Dataset statistics, computed from the generator. An incidence set is the set of hyperedges that contain a node.} +\label{tab:dataset} +\begin{tabular}{@{}lrr@{}} +\toprule +& Scale 1 & Scale 4 \\ +\midrule +Nodes & 50{,}000 & 200{,}000 \\ +Hyperedges & 100{,}000 & 400{,}000 \\ +Incidences & 362{,}757 & 1{,}453{,}427 \\ +\midrule +Hyperedge cardinality, mean / p99 / max & 3.63 / 11 / 30 & 3.63 / 11 / 30 \\ +Incidence set size, mean & 7.26 & 7.27 \\ +Incidence set size, p50 / p90 / p99 & 5 / 12 / 41 & 5 / 13 / 42 \\ +Incidence set size, p99.9 / max & 145 / 2{,}645 & 142 / 5{,}714 \\ +Nodes in no hyperedge & 575 & 2{,}319 \\ +Incidence sets above 48 entries & 370 & 1{,}521 \\ +\bottomrule +\end{tabular} +\end{table} + +\Cref{tab:dataset} gives the sizes and the skew. The largest incidence set at scale 1 holds 2,645 hyperedges, while half of all nodes are in 5 or fewer. Only 370 incidence sets exceed the 48 entries that HStore stores inline. Probe sets drawn from the same seed hold 100,000 random nodes and hyperedges, 20,000 node pairs taken from a common hyperedge (so that co-membership counts are non-zero), and 20,000 node updates. A separate churn set, drawn after everything else, selects 20\% of the hyperedges for deletion and 20,000 other hyperedges, each with at least three members, that lose one member. + +\subsection{Workloads} + +\Cref{tab:workloads} defines the workloads. They run in the order listed, in one process per system and run, against one database directory. The table also names the API call each system uses. Each adapter uses the plain storage calls of its system and its own identifier type: \texttt{long} ids for HStore and persistent handles for HyperGraphDB. Higher-level traversal frameworks, such as HyperGraphDB's breadth-first traversal, are not used, so the numbers reflect the storage layer. + +\begin{table*}[t] +\centering\footnotesize +\caption{Workloads, in execution order. ``txn'' is a transaction. All read workloads except \wl{read.incidence.cold} run after a warm-up (\cref{sec:measurement}).} +\label{tab:workloads} +\begin{tabularx}{\textwidth}{@{}lXll@{}} +\toprule +Workload & Operation & \hstore{} call & \hgdb{} call \\ +\midrule +\wl{ingest.nodes} & insert all nodes, 1,000 per txn & \texttt{Writer.node} & \texttt{graph.add(String)} \\ +\wl{ingest.edges} & insert all hyperedges, 1,000 per txn & \texttt{Writer.edge}, \texttt{Writer.load} & \texttt{graph.add(HGPlainLink)} \\ +\wl{read.incidence} & enumerate a node's incidence set; 100,000 probes, 1,000 per read txn & \texttt{forEachIncidentEdge} & \texttt{getIncidenceSet} \\ +\wl{read.members} & enumerate a hyperedge's members; 100,000 probes & \texttt{forEachMemberId} & \texttt{getTargetAt} \\ +\wl{read.comembership} & count hyperedges containing both nodes of a pair; 20,000 pairs & leapfrog intersection & \texttt{hg.and(hg.incident, hg.incident)} \\ +\wl{read.twohop} & count distinct nodes sharing a hyperedge with a node; 10,000 probes & incidence, then members & incidence, then targets \\ +\wl{read.incidence.parallel} & \wl{read.incidence} from 8 threads, 100,000 probes each & as above & as above \\ +\wl{latency.read} & one incidence lookup per read txn, each timed; 100,000 txns & as above & as above \\ +\wl{write.update} & replace a node's value; 20,000 updates, 1,000 per txn & \texttt{Writer.set} & \texttt{graph.replace} \\ +\wl{latency.commit} & one update per write txn, each timed; 5,000 txns & \texttt{Writer.set} & \texttt{graph.replace} \\ +\wl{mixed.read} & single incidence lookups from 7 threads for 10\,s, beside \wl{mixed.write} & as \wl{latency.read} & as \wl{latency.read} \\ +\wl{mixed.write} & one thread committing 10 updates per txn on a fixed schedule of 1,000 updates/s & \texttt{Writer.set} & \texttt{graph.replace} \\ +\wl{large.ingest} & create one hyperedge containing every node & \texttt{Writer.load} & one \texttt{HGPlainLink} \\ +\wl{large.scan} & enumerate the large hyperedge 400 times & \texttt{forEachMemberId} & \texttt{getTargetAt} \\ +\wl{large.probe} & test whether a node belongs to the large hyperedge; 100,000 probes & membership-tree lookup & \texttt{getIncidenceSet(n).contains(e)} \\ +\wl{reopen} & close cleanly and open again & \texttt{HypergraphDatabase.open} & \texttt{HGEnvironment.get} \\ +\wl{read.incidence.cold} & \wl{read.incidence} immediately after the reopen, no warm-up & & \\ +\wl{disk} & bytes on disk after a clean shutdown & & \\ +\wl{churn.delete} & delete 20\% of the hyperedges, 1,000 per txn & \texttt{Writer.delete} & \texttt{graph.remove} \\ +\wl{churn.remove} & remove one member from each of 20,000 other hyperedges, 1,000 per txn & \texttt{Writer.remove} & \texttt{graph.replace} with a new link \\ +\wl{read.incidence.churned} & \wl{read.incidence} after the deletes & & \\ +\wl{disk.churned} & bytes on disk after the deletes and a clean shutdown & & \\ +\wl{compaction.baseline} & single-update commits at 50/s for 10\,s, each timed & \texttt{Writer.set} & \texttt{graph.replace} \\ +\wl{compaction} & the same commits while a full space reclamation runs, starting 1\,s in & \texttt{StorageEngine.compact} & \texttt{cleanLog} until idle, then checkpoint \\ +\wl{disk.compacted} & bytes on disk after the reclamation and a clean shutdown & & \\ +\wl{recover} & open a store whose ingest process was killed with SIGKILL & \texttt{HypergraphDatabase.open} & \texttt{HGEnvironment.get} \\ +\bottomrule +\end{tabularx} +\end{table*} + +HyperGraphDB links are immutable, so \wl{churn.remove} replaces each link with a new one that lacks the removed member. \wl{read.twohop} runs before \wl{large.ingest}, because once a hyperedge containing every node exists, every node is two hops from all others. \wl{recover} runs in a child JVM with the same flags. The child ingests nodes in transactions of 1,000 into a fresh directory and prints the count after each acknowledged commit. The parent kills it with SIGKILL once half the nodes are acknowledged, then times opening the directory and counts the surviving nodes. + +\subsection{Measurement} +\label{sec:measurement} + +\paragraph{Throughput.} Each workload is timed with \texttt{System.nanoTime()} around its whole operation set. Throughput is operations divided by elapsed time. Each write workload ends with a flush outside the timed section: an HStore checkpoint, or \texttt{Environment.sync()} for JE. Bytes still in buffers are therefore counted against the workload that produced them. + +\paragraph{Warm-up.} Before every timed read workload except \wl{read.incidence.cold}, the same workload runs untimed for at least 2\,s. Both systems get the same warm-up. The numbers describe a process whose JIT has compiled the read path. A short warm-up mostly measures how fast each system's code gets compiled: in our trials, a warm-up over 10\% of the probes left \wl{read.members} about three times below its steady rate. + +\paragraph{Latency.} \wl{latency.read} and \wl{latency.commit} time every transaction separately and report p50, p99, p99.9 and the maximum. In \wl{mixed} and \wl{compaction}, the writer runs on a fixed schedule (open loop), and each commit's latency is measured from the time it was \emph{scheduled} to start. A stall therefore shows up in the latency of every commit queued behind it, instead of silently slowing the writer down. The readers in \wl{mixed.read} run in a closed loop and keep a uniform sample of 200,000 latencies each, plus the exact maximum. + +\paragraph{Bytes written and read.} Both systems count their own I/O. For HStore this is the log bytes plus the page bytes written, including pages moved by compaction. For HyperGraphDB it is JE's sequential plus random write bytes, including the cleaner. Reads count the bytes each system reads from its files: HStore's segment reads, and JE's sequential plus random read bytes. A read served from the system's own cache counts as zero. A read that reaches a file counts even when the operating system's page cache serves it. Write amplification divides bytes written by an estimate of the logical bytes: 8 bytes per id plus the value, 24 bytes per updated value, $(c+1)\times 8$ bytes for a new or deleted hyperedge of cardinality $c$, and 16 bytes per removed member. + +\paragraph{Memory.} For every workload the harness records the bytes allocated, from \texttt{ThreadMXBean} (the whole process for write workloads, the reading thread alone for single-threaded reads), and the stop-the-world GC time from \texttt{GarbageCollectorMXBean}. After ingest it forces a full GC and records the heap in use, minus a baseline taken before the store opened. This is the \emph{retained heap}. + +\paragraph{Correctness cross-check.} Every read and delete workload returns a checksum: the number of ids visited, intersected or found. Both systems must return the same checksum in every run, or the result is flagged. All checksums agreed in every run reported here. Workloads whose result depends on timing (\wl{mixed}, \wl{compaction}, \wl{recover}) are not checked. + +\subsection{Environment and statistics} +\label{sec:environment} + +All runs used one machine: an Apple M5 with 4 performance and 6 efficiency cores, 24\,GiB of memory, and a 1\,TB Apple SSD formatted with APFS, running macOS~26.5.1. The JVM was Oracle GraalVM for JDK~25.0.4 with the HotSpot JIT, a fixed 4\,GiB heap (\texttt{-Xms4g -Xmx4g}) and the Parallel collector. Each system ran in its own JVM, and runs alternated between the two systems so that slow periods affect both. We ran three configurations: +\begin{itemize} +\item \textbf{S1 async}: scale 1, asynchronous commits, \M{runsA} runs per system; +\item \textbf{S1 sync}: scale 1, synchronous commits, \M{runsB} runs per system; +\item \textbf{S4 async}: scale 4, asynchronous commits, \M{runsC} runs per system. With 64\,MiB caches, the working set is several times larger than either cache. +\end{itemize} +Each table reports the median over runs. Ratios are taken between medians and oriented so that values above 1 favour HStore: throughput divides HStore by HyperGraphDB, while times, latencies and sizes divide HyperGraphDB by HStore. Where the ranges of the two systems' runs overlap, the ratio is marked with $\dagger$, and the difference should not be read as significant. \Cref{app:variability} gives every run and the coefficient of variation of each workload. diff --git a/benchmarks/report/sections/results-configs.tex b/benchmarks/report/sections/results-configs.tex new file mode 100644 index 0000000..e963bcf --- /dev/null +++ b/benchmarks/report/sections/results-configs.tex @@ -0,0 +1,12 @@ +\subsection{Larger data: scale 4} +\label{sec:scale4} + +At scale 4 the dataset has \M{incidencesC} incidences, and the files of both systems are several times larger than their 64\,MiB caches. Most of HStore's leads hold or grow. Hyperedge insertion becomes \M{sumCingestedges} faster (\M{hCingestedges} against \M{gCingestedges} per second), batched updates \M{sumCwriteupdate} and single-update commits \M{sumClatencycommit}, the last with overlapping ranges. Co-membership counts are \M{sumCreadcomembership} faster, incidence lookups \M{sumCreadincidence} (ranges overlapping), and cold lookups after a reopen \M{sumCreadincidencecold}. + +Two read workloads reverse. HStore enumerates the members of \M{hCreadmembers} hyperedges per second against HyperGraphDB's \M{gCreadmembers}, a ratio of \M{sumCreadmembers}. Two-hop neighbourhoods, which enumerate the members of every incident hyperedge, follow at \M{sumCreadtwohop}. The resource counters show why. At scale 4 HStore's member lookups miss its node cache: each one reads about 778 bytes from the files and allocates \M{allocChstorereadmembers} while decoding the catalog page that holds the edge record, and a two-hop query allocates \M{allocChstorereadtwohop}. HyperGraphDB serves the same lookups from its atom cache, which holds each link's targets as a Java array and is not limited by the 64\,MiB budget: it allocates \M{allocChgdbreadmembers} per member lookup. \Cref{sec:discussion} returns to this. + +On disk, HyperGraphDB's files after loading are \M{gCdisk} against HStore's \M{hCdisk}. After the deletes, HStore's files grow to \M{hCdiskchurned}. The deletes rewrite a large part of the incidence index, and the replaced pages remain reachable from the 64 retained generations until the next restart releases that history. The benchmark restarts the store after the deletes, and during the 10\,s \wl{compaction.baseline} window that follows, HStore's background maintenance released and compacted that space. When the measured compaction window began, the explicit compaction took \M{compmsChstore} and found almost nothing left to reclaim, and the final size was \M{hCdiskcompacted}. JE's cleaner ran for \M{compmsChgdb} in the same window and brought HyperGraphDB from \M{gCdiskchurned} to \M{gCdiskcompacted}. + +A clean reopen is \M{sumCreopen} faster in HStore, and opening after a crash takes \M{hCrecover} against \M{gCrecover}. Over the whole run, HStore wrote \M{totalwrittenChstore} and HyperGraphDB \M{totalwrittenChgdb}, most of it during cleaning. HStore's single-update commits have a lower p99 (\M{latChstorelatencycommitptwo} against \M{latChgdblatencycommitptwo}) and p99.9, but a higher maximum: \M{latChstorelatencycommitmax} against \M{latChgdblatencycommitmax}. + +\input{sections/results-sync} diff --git a/benchmarks/report/sections/results-sync.tex b/benchmarks/report/sections/results-sync.tex new file mode 100644 index 0000000..3a503f8 --- /dev/null +++ b/benchmarks/report/sections/results-sync.tex @@ -0,0 +1,10 @@ +\subsection{Synchronous commits} +\label{sec:sync} + +With synchronous commits, every acknowledged commit has been forced to stable storage. On this machine a forced write costs about 4\,ms, and it dominates single-update transactions in both systems. HStore commits \M{hBlatencycommit} of them per second and HyperGraphDB \M{gBlatencycommit} (\M{sumBlatencycommit}). The medians are \M{latBhstorelatencycommitmed} and \M{latBhgdblatencycommitmed}, one forced write each, and HStore's lead comes from the tail: \M{latBhstorelatencycommitpthree} against \M{latBhgdblatencycommitpthree} at p99.9. + +Batched work spreads the forced write over 1,000 operations. HStore inserts nodes \M{sumBingestnodes} as fast as HyperGraphDB and hyperedges \M{sumBingestedges} as fast (ranges overlapping), and updates values \M{sumBwriteupdate} as fast. The open-loop writer of \wl{mixed} commits 10 updates every 10\,ms. HStore held that schedule, with a median commit latency of \M{latBhstoremixedwritemed}. HyperGraphDB fell behind it: its commits waited \M{latBhgdbmixedwritemed} at the median, and its slowest run reached \M{mixminBhgdb} updates per second. + +Under synchronous commits, JE reports about 210\,KiB written per single-update commit, against about 9\,KiB with asynchronous commits. The extra bytes come with the log flush that the adapter adds (\cref{sec:durability}). We have not established what JE writes in that flush, so the bytes-written figures for synchronous single commits in \cref{app:full} should not be read as a comparison. + +Reads do not commit, and HStore leads every read workload in this configuration as well (\cref{tab:summary}). Recovery after SIGKILL takes \M{hBrecover} in HStore and \M{gBrecover} in HyperGraphDB at the median. The ranges overlap, and one HStore run took 1.6\,s. As in every other configuration, no run of either system lost an acknowledged commit. diff --git a/benchmarks/report/sections/results-text.tex b/benchmarks/report/sections/results-text.tex new file mode 100644 index 0000000..a54e01f --- /dev/null +++ b/benchmarks/report/sections/results-text.tex @@ -0,0 +1,76 @@ +\subsection{Loading, updating and deleting} + +At scale 1 with asynchronous commits, HStore inserts nodes \M{ratioAingestnodes} and hyperedges \M{ratioAingestedges} as fast as HyperGraphDB. Both ratios carry the overlap mark: the slowest HStore runs were slower than the fastest HyperGraphDB runs, so bulk insertion should be read as roughly even with a lead for HStore. Updates differ more. Replacing node values 1,000 at a time runs \M{ratioAwriteupdate} faster in HStore (\M{hAwriteupdate} against \M{gAwriteupdate} updates per second). Deleting hyperedges is \M{ratioAchurndelete} faster and removing single members \M{ratioAchurnremove} faster, both with overlapping ranges. Building one hyperedge over all 50,000 nodes takes HStore a single bulk load, \M{ratioAlargeingest} faster than HyperGraphDB. + +Single-update transactions show the smallest write difference: \M{hAlatencycommit} against \M{gAlatencycommit} commits per second (\M{ratioAlatencycommit}, ranges overlapping). With asynchronous commits, neither system waits for the disk, and the cost of one commit is dominated by work in memory. \Cref{sec:latency} shows that the two medians are close and that the difference lies in the tail. + +\subsection{Reads} + +HStore's largest margins are on reads that go through incidence sets. It enumerates a node's incidence set \M{ratioAreadincidence} faster than HyperGraphDB, counts the hyperedges shared by two nodes \M{ratioAreadcomembership} faster, and computes two-hop neighbourhoods \M{ratioAreadtwohop} faster. With 8 threads, incidence lookups reach \M{hAreadincidenceparallel} per second in HStore against \M{gAreadincidenceparallel} (\M{ratioAreadincidenceparallel}). When every lookup is its own read transaction (\wl{latency.read}), the ratio is \M{ratioAlatencyread}. Enumerating the members of a hyperedge, the read where HyperGraphDB's atom cache helps most, is \M{ratioAreadmembers} faster in HStore at the median, with overlapping ranges. + +Two read workloads run under special conditions. Directly after a reopen, before any warm-up, HStore serves incidence lookups at \M{hAreadincidencecold} per second against \M{gAreadincidencecold} (\M{ratioAreadincidencecold}). HStore read \M{coldreadAhstore} per lookup from its files in that phase, against \M{coldreadAhgdb} for JE. After the deletes, incidence lookups are \M{ratioAreadincidencechurned} faster. + +\subsection{Giant hyperedges} + +On the hyperedge that contains all 50,000 nodes, HStore scans members \M{ratioAlargescan} faster (\M{hAlargescan} members per second against \M{gAlargescan}) and tests membership \M{ratioAlargeprobe} faster. A membership test in HStore is one descent of the edge's membership tree. + +\subsection{Latency} +\label{sec:latency} + +\begin{figure*}[t] +\centering +\input{gen/fig-latency} +\caption{Latency percentiles, median of each percentile over runs (log scale). From left: single-update commits with asynchronous and with synchronous commits, single-lookup read transactions, and commits of the open-loop writer while 7 threads read.} +\label{fig:latency} +\end{figure*} + +\begin{table*}[t] +\centering\footnotesize +\caption{S1 async: latency per operation, median of each percentile over runs.} +\label{tab:latency} +\input{gen/latency-s1-async} +\end{table*} + +\Cref{fig:latency} and \cref{tab:latency} give the latency distributions. For single-update commits with asynchronous commits, the medians are close: \M{latAhstorelatencycommitmed} for HStore and \M{latAhgdblatencycommitmed} for HyperGraphDB. The tails are not. At p99, HStore needs \M{latAhstorelatencycommitptwo} and HyperGraphDB \M{latAhgdblatencycommitptwo}. At p99.9 the figures are \M{latAhstorelatencycommitpthree} and \M{latAhgdblatencycommitpthree}. Read transactions take \M{latAhstorelatencyreadmed} at the median in HStore and \M{latAhgdblatencyreadmed} in HyperGraphDB, and \M{latAhstorelatencyreadpthree} against \M{latAhgdblatencyreadpthree} at p99.9. + +\paragraph{Reads beside a writer.} In \wl{mixed}, the writer's median rate was 1,000 updates per second in both systems; its slowest run reached \M{mixminAhstore} updates per second in HStore and \M{mixminAhgdb} in HyperGraphDB. Meanwhile the 7 readers completed \M{hAmixedread} lookups per second in HStore and \M{gAmixedread} in HyperGraphDB (\M{ratioAmixedread}). Reader latency at p99.9 was \M{latAhstoremixedreadpthree} against \M{latAhgdbmixedreadpthree}. The writer's commits, measured from their scheduled start, took \M{latAhstoremixedwriteptwo} against \M{latAhgdbmixedwriteptwo} at p99. + +\paragraph{Commits during space reclamation.} In the \wl{compaction} window, the median commit latency was about the same with and without a reclamation running, for both systems. At p99, HStore's commits took \M{latAhstorecompactionptwo} during compaction against \M{latAhstorecompactionbaselineptwo} without; HyperGraphDB's took \M{latAhgdbcompactionptwo} during cleaning against \M{latAhgdbcompactionbaselineptwo} without. + +\subsection{Disk footprint, deletes and reclamation} + +\begin{figure}[t] +\centering +\input{gen/fig-disk} +\caption{Bytes on disk after load, after deleting 20\% of the hyperedges, and after a full space reclamation, median over runs.} +\label{fig:disk} +\end{figure} + +After loading at scale 1, HStore's files take \M{hAdisk} and HyperGraphDB's \M{gAdisk} (\cref{fig:disk}). After the deletes the figures are \M{hAdiskchurned} and \M{gAdiskchurned}. HStore's files grow at this point. The deletes write new pages, and the old ones stay on disk while any of the 64 retained generations can reach them, until a later checkpoint releases them and compaction removes them. After a full reclamation and a clean shutdown, HStore is back to \M{hAdiskcompacted}, and HyperGraphDB is at \M{gAdiskcompacted}. + +\subsection{Reopen and recovery} + +A clean close and reopen takes \M{hAreopen} in HStore and \M{gAreopen} in HyperGraphDB (\M{ratioAreopen}). After the ingest process is killed with SIGKILL, opening the store takes \M{hArecover} and \M{gArecover} respectively. The ranges overlap, so we read recovery time as even. In every run of both systems, every node acknowledged before the kill was present after recovery. + +\subsection{Resources} +\label{sec:resources} + +\begin{figure}[t] +\centering +\input{gen/fig-writes} +\caption{Bytes written per operation, S1 async, median over runs (log scale). Writes include each system's log, data files, checkpoints and cleaning.} +\label{fig:writes} +\end{figure} + +\begin{table*}[t] +\centering\footnotesize +\caption{Resources per configuration, median over runs. HS: HStore, HG: HyperGraphDB. Space reclaimed is the drop in disk size across the compaction window; it is negative when the writer added more than the reclamation freed.} +\label{tab:memory} +\input{gen/resources} +\end{table*} + +HStore writes fewer bytes per operation than HyperGraphDB in every write workload of every configuration (\cref{fig:writes} and \cref{app:full}). A single-update commit writes \M{bpoAhstorelatencycommit} in HStore and \M{bpoAhgdblatencycommit} in HyperGraphDB. A batched update writes \M{bpoAhstorewriteupdate} against \M{bpoAhgdbwriteupdate}. Over a whole run at scale 1, HStore wrote \M{totalwrittenAhstore} and HyperGraphDB \M{totalwrittenAhgdb}. + +\Cref{tab:memory} summarises memory and reclamation. HStore retained \M{retainedAhstore} of heap after ingest at scale 1, against \M{retainedAhgdb} for HyperGraphDB. Per-workload allocation and GC time are in \cref{app:full}. At scale 1, HStore allocates less per operation in every read workload and slightly more per inserted node and hyperedge, and it allocates more while recovering. At scale 4 it allocates more per member lookup and per two-hop query, the cache misses discussed in \cref{sec:scale4}. + +\input{sections/results-configs} diff --git a/benchmarks/report/sections/results.tex b/benchmarks/report/sections/results.tex new file mode 100644 index 0000000..3467295 --- /dev/null +++ b/benchmarks/report/sections/results.tex @@ -0,0 +1,27 @@ +\section{Results} +\label{sec:results} + +\begin{figure*}[t] +\centering +\input{gen/fig-ratios} +\caption{HStore's advantage over HyperGraphDB for every workload and configuration (log scale). Bars above the dashed line favour HStore. Throughput workloads divide HStore by HyperGraphDB; \wl{reopen}, \wl{recover} and the disk sizes divide HyperGraphDB by HStore.} +\label{fig:ratios} +\end{figure*} + +\Cref{fig:ratios} shows HStore's advantage on every workload in all three configurations, and \cref{tab:summary} gives the same ratios with the run-range test from \cref{sec:environment}. \Cref{tab:main} gives the absolute numbers for the S1 async configuration. The other configurations are tabulated in \cref{app:full}. + +\begin{table}[t] +\centering\footnotesize +\caption{Ratio of medians, HStore over HyperGraphDB, oriented so that values above 1 favour HStore (bold). $^\dagger$: the run ranges of the two systems overlap.} +\label{tab:summary} +\input{gen/summary} +\end{table} + +\begin{table*}[t] +\centering\footnotesize +\caption{S1 async: median throughput (operations per second), times and sizes over \M{runsA} runs per system.} +\label{tab:main} +\input{gen/main-s1-async} +\end{table*} + +\input{sections/results-text} diff --git a/benchmarks/report/sections/systems.tex b/benchmarks/report/sections/systems.tex new file mode 100644 index 0000000..f33774c --- /dev/null +++ b/benchmarks/report/sections/systems.tex @@ -0,0 +1,92 @@ +\section{Systems under test} +\label{sec:systems} + +Both systems are embedded Java libraries. Each runs in its own JVM process, and the benchmark harness calls its API directly. Neither is accessed over a network. + +\subsection{HStore} + +\paragraph{Data model.} Every stored object is an \emph{atom} with a 64-bit identifier. An atom is a node or a hyperedge. A hyperedge has any number of members, and a member can itself be a hyperedge. Hyperedges are either \emph{set} edges, keyed by member, or \emph{ordered} edges, keyed by position. Each membership carries a role set, a weight, a validity interval and a qualifier. The benchmark uses set edges and plain memberships. + +\paragraph{Layout.} All persistent state is held in copy-on-write counted B+trees. Every child reference stores a summary of its subtree: the entry count, the key range and an order-independent fingerprint. Three trees matter here: +\begin{itemize} +\item The \emph{catalog} maps an atom id to its record. A hyperedge record holds a reference to that edge's \emph{membership tree}, keyed by member id. If the membership tree is one leaf of at most 32 entries that fits in about 100 bytes, it is stored inside the record itself and needs no page of its own. +\item The \emph{reverse incidence index} maps an atom id to the set of hyperedges that contain it. Sets of up to 48 entries are stored inline in the index leaf. Larger sets are promoted to a tree of their own. +\item Secondary indexes on node keys and properties, which the benchmark touches only through node insertion and updates. +\end{itemize} +Enumerating an incidence set is a cursor walk over one inline list or one small tree. Counting the hyperedges that contain two given atoms is a leapfrog intersection~\cite{leapfrog} of their two incidence trees. The intersection skips whole subtrees whose key range cannot contain a match. + +\paragraph{Pages and files.} A tree node is written as a self-describing image: an 80-byte header with a CRC32C checksum, followed by the payload. Images are packed into append-only segment files of 256\,MiB, aligned to 64-byte units. A page directory maps each page number to the image's current address, so compaction can move an image without rewriting the trees that refer to it. Reads go through a memory map of each segment. Decoded nodes are kept in a node cache whose budget is set in bytes. + +\paragraph{Generations and readers.} A committed state is a \emph{generation}: an immutable vector of tree roots. A reader pins a generation and reads it without locks. Writers never modify a node that a published generation can reach, as in other copy-on-write B+tree stores~\cite{lmdb}. The last 64 generations are retained for time travel. + +\begin{figure*}[t] +\centering +\begin{tikzpicture}[ + font=\footnotesize, + box/.style={draw=rule,rounded corners=2pt,minimum height=9mm,minimum width=25mm,inner sep=3pt,align=center,fill=white}, + disk/.style={box,fill=hs!9}, + arr/.style={-{Stealth[length=2mm]},rule,semithick}, + lab/.style={font=\scriptsize,align=center,text=black!75}] +\node[box] (txn) at (0,0) {Transaction\\private workspace}; +\node[box] (commit) at (4.4,0) {Commit\\(holds commit lock)}; +\node[box,minimum width=36mm] (gen) at (9.8,0) {Published generation $g$\\changed nodes still in memory}; +\node[box] (read) at (15.1,0) {Readers\\pin $g$, no locks}; +\node[disk] (wal) at (4.4,-2.3) {Write-ahead log\\changed keys per tree}; +\node[box,minimum width=36mm] (ckpt) at (9.8,-2.3) {Checkpoint\\(background thread)}; +\node[disk] (seg) at (15.1,-2.3) {Segment files\\and page directory}; +\node[disk,minimum width=36mm] (cat) at (9.8,-4.3) {Catalog image\\and page reference counts}; +\draw[arr] (txn) -- (commit); +\draw[arr] (commit) -- node[lab,above]{(2) publish\\new roots} (gen); +\draw[arr] (read) -- (gen); +\draw[arr] (commit) -- node[lab,left]{(1) append, then\\one fsync per group} (wal); +\draw[arr] (gen) -- node[lab,left]{(3) nodes no\\checkpoint wrote} (ckpt); +\draw[arr] (ckpt) -- node[lab,below]{(4) write each\\node once, sync} (seg); +\draw[arr] (ckpt) -- node[lab,right]{(5) count, publish} (cat); +\draw[arr] (ckpt) -- node[lab,above]{(7) truncate} (wal); +\draw[arr,dashed] (ckpt.north east) to[out=40,in=-40] node[lab,right]{(6) swap in\\stored references} (gen.south east); +\end{tikzpicture} +\caption{HStore's write path. (1, 2) A commit logs only the keys it changed and publishes new roots whose changed nodes are still in memory. (3--7) A background checkpoint writes those nodes to pages, once for all the commits since the previous checkpoint, then swaps the stored references into the current generation and truncates the log. Only step 6 takes the commit lock.} +\label{fig:writepath} +\end{figure*} + +\paragraph{Commits and checkpoints.} \Cref{fig:writepath} shows the write path. A transaction collects its changes in a private workspace. At commit it applies them to new copies of the affected tree paths, appends one write-ahead log record listing the keys it changed in each tree, and publishes the new generation. The new tree nodes stay in memory, reachable from the published roots. A background checkpoint later writes every node that no earlier checkpoint wrote, syncs the files, updates page reference counts, and publishes a catalog image. It then swaps the stored references into the current generation and truncates the log. Only the swap holds the commit lock. Memory held by unwritten nodes is capped at 64\,MiB. A commit that finds the cap reached waits for a checkpoint. + +Two exceptions write pages before the checkpoint. A large bulk load spills finished subtrees to pages while it runs, and a tree that changed in more than 4,096 places is written to pages at commit and logged by reference. Under \emph{synchronous} durability, a commit returns after its log record is fsynced; concurrent commits share one fsync (group commit). Under \emph{asynchronous} durability the fsync is skipped, but commits are still published strictly in order. + +\paragraph{Recovery.} On open, HStore loads the newest catalog image and replays the logged key changes of every commit after it, in commit order. A commit's changes are applied only if its commit record reached the log, so recovery always yields a prefix of the commit order. + +\paragraph{Space reclamation.} Every page has a reference count, which changes only at checkpoints. A page no generation can reach is released once no pinned reader can see it. A segment whose live bytes fall below half of the bytes written to it is compacted: its live images are copied to the active segment, the page directory is updated, and the old file is deleted once no reader can still be using it. A background maintenance pass, run every 500\,ms, compacts one such segment at a time. An explicit compaction request compacts all of them. + +\subsection{HyperGraphDB} + +HyperGraphDB~\cite{hgdb} is a general hypergraph database for Java. Its atoms are identified by persistent handles (UUIDs). A link stores the ordered array of its target handles. The benchmark uses \texttt{HGPlainLink} for hyperedges and \texttt{String} atoms for nodes. The incidence set of an atom, the links pointing to it, is maintained by HyperGraphDB in a separate index. The atom cache keeps recently used atoms and incidence sets in memory as Java objects. + +We use the Berkeley DB Java Edition storage implementation (\texttt{bdb-je}), the default for HyperGraphDB~1.4. JE is a log-structured B+tree store~\cite{je,lfs}: every update appends a new version of the changed records and internal nodes to the log. A background cleaner copies the live entries out of underused log files and deletes them. A checkpointer bounds recovery time. Incidence sets are stored as sorted duplicate sets in a JE database. A lookup opens a cursor at the atom's handle. + +\subsection{Synchronous commits in HyperGraphDB} +\label{sec:durability} + +HyperGraphDB 1.4 begins every JE transaction with an explicit \texttt{WRITE\_NO\_SYNC} durability. That setting overrides the environment-wide \texttt{COMMIT\_SYNC}, so configuring the environment alone leaves commits unsynced: in a first run of the synchronous configuration, HyperGraphDB committed single updates in about 0.1\,ms, while one forced write on this machine takes about 3.8\,ms. For the synchronous configuration the HyperGraphDB adapter therefore calls \texttt{Environment.flushLog(true)} after every commit. This writes and forces JE's log, which is what \texttt{COMMIT\_SYNC} would do at commit time. The asynchronous configuration is unaffected. + +\subsection{Versions and configuration} + +\Cref{tab:config} lists the configuration of both systems. The two caches are given the same 64\,MiB budget. HStore's budget covers decoded nodes. JE's budget covers its B+tree nodes and records, and HyperGraphDB's atom cache comes on top of it. That cache is sized by HyperGraphDB itself and cannot be capped in bytes. The retained-heap figure in \cref{sec:resources} shows what each store actually kept. + +\begin{table*}[t] +\centering\footnotesize +\caption{System configuration. Both systems use their defaults except where stated.} +\label{tab:config} +\begin{tabularx}{0.82\textwidth}{@{}lXX@{}} +\toprule +& \hstore & \hgdb \\ +\midrule +Version & 0.1.0, commit \texttt{459b8be}, storage format 7 & 1.4, commit \texttt{99485a1}; JE 5.0.73 \\ +Cache & node cache, 64\,MiB & JE cache 64\,MiB, plus atom cache \\ +Async commits & no fsync, ordered publication & JE \texttt{WRITE\_NO\_SYNC} (default) \\ +Sync commits & fsync per commit group & JE log forced after each commit (\cref{sec:durability}) \\ +Page / node size & 16\,KiB pages & JE defaults \\ +Space reclamation & compact segments below 50\% live & cleaner, 50\% minimum utilisation \\ +History & 64 generations & none \\ +\bottomrule +\end{tabularx} +\end{table*} diff --git a/benchmarks/report/sections/validity.tex b/benchmarks/report/sections/validity.tex new file mode 100644 index 0000000..67feacc --- /dev/null +++ b/benchmarks/report/sections/validity.tex @@ -0,0 +1,16 @@ +\section{Threats to validity} +\label{sec:validity} + +\paragraph{One machine, not idle.} All numbers come from one laptop that was also running other work. Sampled before each run, the one-minute load average was between \M{loadmin} and \M{loadmax} (median \M{loadmed}) on 10 cores. That figure includes the tail of the previous run, whose parallel workloads use 8 threads, so only part of it is outside work. Absolute throughput will differ on a server, and short workloads are sensitive to interference. We report medians, show every run in \cref{app:variability}, and mark ratios whose run ranges overlap. Conclusions in this report rest only on ratios that are large compared with that spread. + +\paragraph{Durability.} On this machine one \texttt{FileChannel.force} after a small write takes about 3.8\,ms at the median, the cost of a write that reaches stable storage. Both systems pay it on every synchronous commit: HStore through its group commit, HyperGraphDB through the explicit log flush described in \cref{sec:durability}. Without that flush, HyperGraphDB's synchronous configuration would not sync at all. The \wl{recover} workload kills a process, not the machine, so it measures recovery time and the system's own buffering, not power-failure durability. HStore's behaviour under torn writes and lost syncs is covered by its crash-injection tests, which are outside the scope of this report. + +\paragraph{Memory fits in RAM.} At both scales the files fit in the operating system's page cache. Reads that miss a system's own 64\,MiB cache are served from memory, not from the SSD. The cold-read numbers therefore measure decoding and cache admission, not device latency. + +\paragraph{Cache budgets.} HStore counts its 64\,MiB node cache against an estimate of the heap each decoded node takes, which runs 10 to 40\% below the true size. JE's 64\,MiB budget is exact, but HyperGraphDB's atom and incidence caches are not capped in bytes. Neither side therefore holds exactly 64\,MiB. The retained heap in \cref{tab:memory} shows what each kept after ingest. + +\paragraph{Workload coverage.} The dataset is synthetic. It uses set hyperedges without roles, properties or ordered members, and node values of a few bytes. Queries are single storage operations, not HQL or HyperGraphDB queries, so query planning is not measured. Workloads run in a fixed order within one process, so each one starts from the state the previous one left: cache contents, garbage, and for HStore, unwritten nodes waiting for a checkpoint. + +\paragraph{Versions.} HyperGraphDB 1.4 is built against Berkeley DB JE 5.0.73, which is older than current JE releases. We did not try other JE versions or tuning beyond the cache size. Both systems otherwise use their default settings. + +\paragraph{Authorship.} The HStore authors wrote the harness and both adapters. To limit bias, both adapters call the most direct storage API available, every read result is cross-checked between the systems, and both receive the same warm-up and cache budget. The harness, the adapters and the raw results are published with HStore. diff --git a/docs/benchmark-report.pdf b/docs/benchmark-report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..10d6ae0c8606d0f6593d0fb7c9761a2907fbbb9f GIT binary patch literal 137863 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

TXs7)j(q?9D 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%0AEcU74r0D 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 Date: Mon, 12 Oct 2026 16:11:19 +0530 Subject: [PATCH 05/15] docs(readme): describe logged commits and compaction, and show benchmark results --- README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8bd6168..b330277 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ other hyperedges. HStore stores this structure directly instead of reifying it i relational join tables. The engine implements the design of *Native Persistent Hypergraph Storage Engine* together with its -higher-order extension. Membership and incidence are copy-on-write counted B+trees with monoid summaries. -Commits are atomic root swaps. The database layer adds a query language (HQL), hypergraph relational operators, -a semantic plane, multi-tenancy and a web console. +higher-order extension. Membership and incidence are copy-on-write counted B+trees with monoid summaries. A +commit logs only the keys it changed and publishes a new immutable generation; a background checkpoint later +writes the changed tree nodes to pages, once for all the commits since the last one. The database layer adds a +query language (HQL), hypergraph relational operators, a semantic plane, multi-tenancy and a web console. It is written in Java 25 and ships as one GraalVM native executable, `hstore`. @@ -21,6 +22,11 @@ It is written in Java 25 and ships as one GraalVM native executable, `hstore`. * **Transactions.** Snapshot isolation by default and `SERIALIZABLE` on request. Optimistic concurrency with commutative rebase of set operations. Group commit. Two WAL modes (page references or page images). Crash recovery that never exposes a partial commit. +* **Small commits.** A commit writes a log record of the keys it changed, a few hundred bytes, instead of new + pages. Checkpoints write each changed node once, and `write_buffer_mb` caps the memory held by nodes that + haven't been written yet. +* **Space reclamation.** Pages carry reference counts. Compaction moves the live pages out of mostly dead + segments without rewriting the trees that point at them, while readers keep running. * **Leapfrog incidence intersection.** Co-membership queries intersect sorted incidence trees and skip with subtree summaries. * **Branches.** `CREATE BRANCH`, isolated what-if writes, `DIFF`, three-way `MERGE … ON CONFLICT`. @@ -38,6 +44,25 @@ It is written in Java 25 and ships as one GraalVM native executable, `hstore`. spokes, hulls, ordered chains, time-travel slider), a schema browser, a live engine dashboard and access management. +## Performance + +HStore against HyperGraphDB 1.4 on Berkeley DB JE, both with 64 MiB caches, at scale 1 (50,000 nodes, 100,000 +hyperedges) with `async` durability. Each figure is the median of 5 runs on one laptop: + +| Workload | HStore | HyperGraphDB | | +|---|---:|---:|---:| +| Incidence set of a node | 7.47 M/s | 740 k/s | 10.1× faster | +| Hyperedges shared by two nodes | 448 k/s | 32.5 k/s | 13.8× faster | +| Value updates, 1,000 per transaction | 78.1 k/s | 5.1 k/s | 15.3× faster | +| Bytes written per single-update commit | 649 B | 9.1 KiB | 14× fewer | +| Commit latency, p99 | 430 µs | 1.08 ms | 2.5× lower | +| Disk after loading | 91 MiB | 432 MiB | 4.7× smaller | + +HyperGraphDB is faster at listing a hyperedge's members once the data no longer fits in HStore's node cache: +0.13× at scale 4. With `sync` durability, single commits in both are bound by the cost of a forced write. The +[benchmark report](docs/benchmark-report.pdf) (PDF) covers every workload, configuration and caveat, and +[docs/benchmarks.md](docs/benchmarks.md) explains how to rerun it. + ## Quick start with Docker Images for linux/amd64 and linux/arm64 are published to the GitHub Container Registry with every release: @@ -173,6 +198,7 @@ The full index is [docs/README.md](docs/README.md). | Wire protocol | [docs/operations/wire-protocol.md](docs/operations/wire-protocol.md) | | HStore Studio | [docs/operations/studio.md](docs/operations/studio.md) | | Benchmarks against HyperGraphDB | [docs/benchmarks.md](docs/benchmarks.md) | +| Benchmark report (PDF) | [docs/benchmark-report.pdf](docs/benchmark-report.pdf) | | Development | [docs/development.md](docs/development.md) | ## Contributing From b438d010f2f69e9c741e7a338a596bcf9feaeb5a Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:51:10 +0530 Subject: [PATCH 06/15] perf(tree): measure a read-only leaf only when a write needs its size Decoding a leaf used to work out the encoded size of every key and value straight away, for inline member lists inside catalog records too. Only writes read those sizes. A frozen leaf now measures itself the first time bytes() or heapBytes() is called, and writable leaves still measure when they are created. --- .../main/java/io/hstore/engine/tree/Leaf.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/engine/src/main/java/io/hstore/engine/tree/Leaf.java b/engine/src/main/java/io/hstore/engine/tree/Leaf.java index 1da4499..1d1e1ea 100644 --- a/engine/src/main/java/io/hstore/engine/tree/Leaf.java +++ b/engine/src/main/java/io/hstore/engine/tree/Leaf.java @@ -21,6 +21,7 @@ public final class Leaf extends Node { private int size; private int keyBytes; private int valueBytes; + private volatile boolean measured; private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, long[] words, int size, Summary summary) { super(schema, owner, summary); @@ -28,7 +29,9 @@ private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, l this.values = values; this.words = words; this.size = size; - remeasure(); + if (owner != null) { + remeasure(); + } } private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, int size, Summary summary) { @@ -63,10 +66,18 @@ private static int gap(long low, long high) { } private void remeasure() { - keyBytes = keyStreamBytes(keys, size); - valueBytes = 0; + int valueTotal = 0; for (int i = 0; i < size; i++) { - valueBytes += values != null ? schema.entryBytes(values[i]) : schema.impliedBytes(words[i]); + valueTotal += values != null ? schema.entryBytes(values[i]) : schema.impliedBytes(words[i]); + } + keyBytes = keyStreamBytes(keys, size); + valueBytes = valueTotal; + measured = true; + } + + private void measure() { + if (!measured) { + remeasure(); } } @@ -75,6 +86,7 @@ int heapBytes() { if (values == null) { return NODE_HEAP_BYTES + 2 * ARRAY_HEAP_BYTES + Long.BYTES * keys.length + (words == keys ? 0 : Long.BYTES * words.length); } + measure(); int bytes = NODE_HEAP_BYTES + 2 * ARRAY_HEAP_BYTES + Long.BYTES * keys.length + Integer.BYTES * values.length + ENTRY_HEAP_BYTES * size + valueBytes; return schema.codec().holdsRefs() ? bytes + inlineHeapBytes(schema) : bytes; @@ -152,6 +164,7 @@ Object value(int index) { } int bytes() { + measure(); return keyBytes + valueBytes + schema.leafOverhead(size); } @@ -206,6 +219,7 @@ private Object[] valueArray(int capacity) { } void insert(int position, long key, Object value) { + measure(); if (size == keys.length) { int capacity = Math.max(8, size + (size >> 1)); keys = Arrays.copyOf(keys, capacity); @@ -235,12 +249,14 @@ private int insertionDelta(int position, long key) { } void replace(int position, Object value) { + measure(); valueBytes += schema.entryBytes(value) - schema.entryBytes(values[position]); values[position] = value; invalidate(); } void remove(int position) { + measure(); long key = keys[position]; valueBytes -= schema.entryBytes(values[position]); System.arraycopy(keys, position + 1, keys, position, size - position - 1); From 2ac274db5ad5588e43f0238964b0d2991515963e Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 16:58:32 +0530 Subject: [PATCH 07/15] feat(tree): frame leaf records by length when values decode on their own Codecs that write each value without reference to the others (the catalog, posting lists and row codecs) now say so with independentValues(), and their leaf pages put the length of every value in front of it. A reader can then find one record without decoding the ones before it. Entry sizes count the length, so leaves never overflow a page. Storage format goes to 8. The history compaction test sat right at the 50% live ratio, and one byte per record pushed its early segment over, so it now compacts below 75%. --- .../java/io/hstore/engine/StorageEngine.java | 4 +- .../io/hstore/engine/catalog/AtomCodec.java | 5 +++ .../io/hstore/engine/index/PostingsCodec.java | 5 +++ .../java/io/hstore/engine/tree/NodeCodec.java | 37 ++++++++++++++++++- .../io/hstore/engine/tree/TreeSchema.java | 5 ++- .../io/hstore/engine/tree/ValueCodec.java | 9 +++++ .../java/io/hstore/engine/CompactionTest.java | 2 +- .../java/io/hstore/engine/EngineTest.java | 6 +-- 8 files changed, 64 insertions(+), 9 deletions(-) diff --git a/engine/src/main/java/io/hstore/engine/StorageEngine.java b/engine/src/main/java/io/hstore/engine/StorageEngine.java index 964fdcc..f44a56e 100644 --- a/engine/src/main/java/io/hstore/engine/StorageEngine.java +++ b/engine/src/main/java/io/hstore/engine/StorageEngine.java @@ -44,7 +44,7 @@ public final class StorageEngine implements AutoCloseable { private static final System.Logger LOG = System.getLogger("hstore.engine"); - private static final String FORMAT_VERSION = "7"; + private static final String FORMAT_VERSION = "8"; private static final int CHANGED_COUNTS_LIMIT = 1 << 20; private static final int MAX_ATTEMPTS = 8; @@ -135,7 +135,7 @@ private static void verifyFormat(Path directory, EngineOptions options) throws I } if (!format.getProperty("format", "1").equals(FORMAT_VERSION)) { throw HStoreException.invalid("database " + directory + " uses storage format " + format.getProperty("format", "1") - + "; this build reads format " + FORMAT_VERSION + " (commits logged as key changes); export and reload it"); + + "; this build reads format " + FORMAT_VERSION + " (leaf records framed by length); export and reload it"); } int stored = Integer.parseInt(format.getProperty("page-size")); if (stored != options.pageSize()) { diff --git a/engine/src/main/java/io/hstore/engine/catalog/AtomCodec.java b/engine/src/main/java/io/hstore/engine/catalog/AtomCodec.java index 71e7679..f403a0d 100644 --- a/engine/src/main/java/io/hstore/engine/catalog/AtomCodec.java +++ b/engine/src/main/java/io/hstore/engine/catalog/AtomCodec.java @@ -79,6 +79,11 @@ public boolean holdsRefs() { return true; } + @Override + public boolean independentValues() { + return true; + } + @Override public boolean inlinesSmallTrees() { return true; diff --git a/engine/src/main/java/io/hstore/engine/index/PostingsCodec.java b/engine/src/main/java/io/hstore/engine/index/PostingsCodec.java index f440431..0aab2e2 100644 --- a/engine/src/main/java/io/hstore/engine/index/PostingsCodec.java +++ b/engine/src/main/java/io/hstore/engine/index/PostingsCodec.java @@ -97,6 +97,11 @@ public boolean holdsRefs() { return true; } + @Override + public boolean independentValues() { + return true; + } + @Override public boolean refersToPages(Postings

value) { return value instanceof Postings.Promoted

(Ref root) && root instanceof Ref.Stored; diff --git a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java index 373db8c..b007363 100644 --- a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java +++ b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java @@ -9,6 +9,7 @@ import io.hstore.engine.page.PageType; import java.lang.foreign.MemorySegment; +import java.util.Collections; import java.util.List; public final class NodeCodec { @@ -72,10 +73,30 @@ private static Leaf decodeLeaf(ByteCursor in, TreeSchema schema, long[] keys, return Leaf.implied(schema, keys, words, summary); } Object[] values = new Object[keys.length]; - schema.codec().decode(in, keys, values); + if (schema.codec().independentValues()) { + decodeFramed(in, schema, keys, values); + } else { + schema.codec().decode(in, keys, values); + } return Leaf.decoded(schema, keys, values, summary); } + private static void decodeFramed(ByteCursor in, TreeSchema schema, long[] keys, Object[] into) { + long[] key = new long[1]; + Object[] value = new Object[1]; + for (int i = 0; i < keys.length; i++) { + long length = in.getVarLong(); + long end = in.position() + length; + key[0] = keys[i]; + schema.codec().decode(in, key, value); + if (in.position() != end) { + throw new IllegalStateException("value " + i + " of a " + schema + " leaf is " + length + " bytes long but decoded from " + + (in.position() - end + length) + " bytes"); + } + into[i] = value[0]; + } + } + private static long[] decodeKeys(ByteCursor in, int count) { long[] keys = new long[count]; keys[0] = in.getSignedVarLong(); @@ -93,7 +114,19 @@ private static void encodeValues(ByteCursor out, TreeSchema schema, long[ } @SuppressWarnings("unchecked") List values = (List) leaf.valueList(); - schema.codec().encode(out, keys, values); + if (!schema.codec().independentValues()) { + schema.codec().encode(out, keys, values); + return; + } + ByteCursor value = ByteCursor.growable(256); + long[] key = new long[1]; + for (int i = 0; i < keys.length; i++) { + value.position(0); + key[0] = keys[i]; + schema.codec().encode(value, key, Collections.singletonList(values.get(i))); + MemorySegment written = value.written(); + out.putVarLong(written.byteSize()).putSegment(written); + } } private static boolean refersToPages(Leaf leaf) { diff --git a/engine/src/main/java/io/hstore/engine/tree/TreeSchema.java b/engine/src/main/java/io/hstore/engine/tree/TreeSchema.java index d5a2869..bf47ed3 100644 --- a/engine/src/main/java/io/hstore/engine/tree/TreeSchema.java +++ b/engine/src/main/java/io/hstore/engine/tree/TreeSchema.java @@ -3,6 +3,8 @@ package io.hstore.engine.tree; +import io.hstore.engine.page.ByteCursor; + import java.util.List; public record TreeSchema(int id, String name, FingerprintMode fingerprint, ValueCodec codec, EntryMeasure measure) { @@ -21,7 +23,8 @@ public V cast(Object value) { } int entryBytes(Object value) { - return codec.maxSize(cast(value)); + int size = codec.maxSize(cast(value)); + return codec.independentValues() ? size + ByteCursor.varLongSize(size) : size; } Object implied(long word) { diff --git a/engine/src/main/java/io/hstore/engine/tree/ValueCodec.java b/engine/src/main/java/io/hstore/engine/tree/ValueCodec.java index 77171bd..e54d08c 100644 --- a/engine/src/main/java/io/hstore/engine/tree/ValueCodec.java +++ b/engine/src/main/java/io/hstore/engine/tree/ValueCodec.java @@ -47,6 +47,10 @@ default boolean holdsRefs() { return false; } + default boolean independentValues() { + return false; + } + default V mapRefs(V value, RefMapper mapper) { return value; } @@ -86,6 +90,11 @@ public void decode(ByteCursor in, long[] keys, Object[] into) { into[i] = reader.apply(in); } } + + @Override + public boolean independentValues() { + return true; + } }; } diff --git a/engine/src/test/java/io/hstore/engine/CompactionTest.java b/engine/src/test/java/io/hstore/engine/CompactionTest.java index 5e37647..d5ea5d9 100644 --- a/engine/src/test/java/io/hstore/engine/CompactionTest.java +++ b/engine/src/test/java/io/hstore/engine/CompactionTest.java @@ -73,7 +73,7 @@ void compactionMovesPagesWithoutWritingAGeneration() { @Test void retainedHistoryStaysReadableOnceItsSegmentsAreDeleted() { - EngineOptions options = options().withHistoryLimit(20); + EngineOptions options = options().withHistoryLimit(20).withCompactionLiveRatio(0.75); List written = Stream.iterate(500_000L, i -> i + 1).limit(2_000).toList(); long large; long edge; diff --git a/engine/src/test/java/io/hstore/engine/EngineTest.java b/engine/src/test/java/io/hstore/engine/EngineTest.java index 507ab8c..e951cb6 100644 --- a/engine/src/test/java/io/hstore/engine/EngineTest.java +++ b/engine/src/test/java/io/hstore/engine/EngineTest.java @@ -614,10 +614,10 @@ void dictionaryInternsRolesAcrossRestarts() { } @Test - void directoriesFromTheInlineMemberFormatAreRefused() throws IOException { - Files.writeString(directory.resolve("FORMAT"), "format=6\npage-size=1024\n"); + void directoriesFromBeforeFramedLeafRecordsAreRefused() throws IOException { + Files.writeString(directory.resolve("FORMAT"), "format=7\npage-size=1024\n"); HStoreException refused = assertThrows(HStoreException.class, () -> StorageEngine.open(directory, small()).close()); - assertTrue(refused.getMessage().endsWith("uses storage format 6; this build reads format 7 (commits logged as key changes); export and reload it"), + assertTrue(refused.getMessage().endsWith("uses storage format 7; this build reads format 8 (leaf records framed by length); export and reload it"), refused.getMessage()); } } From bf23f6624d3aac0147614bfdb6ea98656bcb5a97 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 17:11:10 +0530 Subject: [PATCH 08/15] perf(tree): decode a leaf record the first time it is read Loading a catalog or posting-list leaf used to decode every record on the page, with every inline member list, to answer a lookup for one of them. A leaf whose records are framed by length now keeps the page bytes and the offset of each record, and decodes a record on first read. The decoded record is kept in the leaf, published with a compare-and-set so readers on other threads see it whole, and charged to the node cache entry that holds the leaf, so the cache still evicts to its budget as records unpack. Charges for an entry that was evicted or cleared are dropped. A packed leaf costs its page image plus its keys and offsets until records are read, so far more of the catalog fits in a given node cache. --- .../main/java/io/hstore/engine/tree/Leaf.java | 95 +++++++++- .../main/java/io/hstore/engine/tree/Node.java | 5 + .../java/io/hstore/engine/tree/NodeCache.java | 29 ++- .../java/io/hstore/engine/tree/NodeCodec.java | 25 +++ .../io/hstore/engine/tree/MemoryPages.java | 4 + .../io/hstore/engine/tree/PackedLeafTest.java | 171 ++++++++++++++++++ .../java/io/hstore/engine/tree/TreeTest.java | 4 + 7 files changed, 321 insertions(+), 12 deletions(-) create mode 100644 engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java diff --git a/engine/src/main/java/io/hstore/engine/tree/Leaf.java b/engine/src/main/java/io/hstore/engine/tree/Leaf.java index 1d1e1ea..492b260 100644 --- a/engine/src/main/java/io/hstore/engine/tree/Leaf.java +++ b/engine/src/main/java/io/hstore/engine/tree/Leaf.java @@ -5,35 +5,66 @@ import io.hstore.engine.page.ByteCursor; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.util.AbstractList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.function.IntConsumer; import java.util.function.LongConsumer; public final class Leaf extends Node { private static final int ENTRY_HEAP_BYTES = 96; + private static final Object ABSENT = new Object(); + private static final VarHandle UNPACKED_VALUES = MethodHandles.arrayElementVarHandle(Object[].class); + private static final VarHandle UNPACKED_BYTES; + + static { + try { + UNPACKED_BYTES = MethodHandles.lookup().findVarHandle(Leaf.class, "unpackedBytes", int.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } private long[] keys; private Object[] values; private final long[] words; + private final MemorySegment packed; + private final int[] offsets; private int size; private int keyBytes; private int valueBytes; private volatile boolean measured; + private volatile int unpackedBytes; + private volatile IntConsumer charge; private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, long[] words, int size, Summary summary) { super(schema, owner, summary); this.keys = keys; this.values = values; this.words = words; + this.packed = null; + this.offsets = null; this.size = size; if (owner != null) { remeasure(); } } + private Leaf(TreeSchema schema, long[] keys, MemorySegment packed, int[] offsets, Summary summary) { + super(schema, null, summary); + this.keys = keys; + this.values = new Object[keys.length]; + this.words = null; + this.packed = packed; + this.offsets = offsets; + this.size = keys.length; + } + private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, int size, Summary summary) { this(schema, owner, keys, values, null, size, summary); } @@ -50,6 +81,10 @@ static Leaf implied(TreeSchema schema, long[] keys, long[] words, Summary sum return new Leaf(schema, null, keys, null, words, keys.length, summary); } + static Leaf packed(TreeSchema schema, long[] keys, MemorySegment page, int[] offsets, Summary summary) { + return new Leaf(schema, keys, page, offsets, summary); + } + static int keyStreamBytes(long[] keys, int size) { if (size == 0) { return 0; @@ -68,7 +103,7 @@ private static int gap(long low, long high) { private void remeasure() { int valueTotal = 0; for (int i = 0; i < size; i++) { - valueTotal += values != null ? schema.entryBytes(values[i]) : schema.impliedBytes(words[i]); + valueTotal += values != null ? schema.entryBytes(value(i)) : schema.impliedBytes(words[i]); } keyBytes = keyStreamBytes(keys, size); valueBytes = valueTotal; @@ -81,8 +116,19 @@ private void measure() { } } + @Override + void chargeTo(IntConsumer charge) { + if (packed != null) { + this.charge = charge; + } + } + @Override int heapBytes() { + if (packed != null) { + return NODE_HEAP_BYTES + 3 * ARRAY_HEAP_BYTES + Long.BYTES * keys.length + Integer.BYTES * (values.length + offsets.length) + + Math.toIntExact(packed.byteSize()) + unpackedBytes; + } if (values == null) { return NODE_HEAP_BYTES + 2 * ARRAY_HEAP_BYTES + Long.BYTES * keys.length + (words == keys ? 0 : Long.BYTES * words.length); } @@ -93,15 +139,20 @@ int heapBytes() { } private int inlineHeapBytes(TreeSchema typed) { + int total = 0; + for (int i = 0; i < size; i++) { + total += inlineHeapBytes(typed, value(i)); + } + return total; + } + + private static int inlineHeapBytes(TreeSchema typed, Object value) { int[] total = new int[1]; - ValueCodec.RefVisitor visitor = (ref, _) -> { + typed.codec().forEachRef(typed.cast(value), (ref, _) -> { if (ref instanceof Ref.Inline(Leaf inline)) { total[0] += inline.heapBytes(); } - }; - for (int i = 0; i < size; i++) { - typed.codec().forEachRef(typed.cast(value(i)), visitor); - } + }); return total[0]; } @@ -160,7 +211,35 @@ long[] impliedWords() { Object value(int index) { Object[] stored = values; - return stored != null ? stored[index] : schema.implied(words[index]); + if (stored == null) { + return schema.implied(words[index]); + } + if (packed == null) { + return stored[index]; + } + Object unpacked = UNPACKED_VALUES.getAcquire(stored, index); + if (unpacked == null) { + unpacked = unpack(stored, index); + } + return unpacked == ABSENT ? null : unpacked; + } + + private Object unpack(Object[] stored, int index) { + Object[] decoded = new Object[1]; + schema.codec().decode(ByteCursor.over(packed).position(offsets[index]), new long[]{keys[index]}, decoded); + Object value = decoded[0] == null ? ABSENT : decoded[0]; + Object raced = UNPACKED_VALUES.compareAndExchangeRelease(stored, index, null, value); + if (raced != null) { + return raced; + } + int grown = ENTRY_HEAP_BYTES + schema.entryBytes(decoded[0]) + + (schema.codec().holdsRefs() ? inlineHeapBytes(schema, decoded[0]) : 0); + UNPACKED_BYTES.getAndAdd(this, grown); + IntConsumer target = charge; + if (target != null) { + target.accept(grown); + } + return value; } int bytes() { @@ -208,7 +287,7 @@ Leaf frozenWith(Object[] mappedValues) { } private Object[] valueArray(int capacity) { - if (values != null) { + if (values != null && packed == null) { return Arrays.copyOf(values, capacity); } Object[] copy = new Object[capacity]; diff --git a/engine/src/main/java/io/hstore/engine/tree/Node.java b/engine/src/main/java/io/hstore/engine/tree/Node.java index 0416ca3..7a66b9b 100644 --- a/engine/src/main/java/io/hstore/engine/tree/Node.java +++ b/engine/src/main/java/io/hstore/engine/tree/Node.java @@ -3,6 +3,8 @@ package io.hstore.engine.tree; +import java.util.function.IntConsumer; + public sealed abstract class Node permits Leaf, Branch { static final int NODE_HEAP_BYTES = 144; @@ -66,4 +68,7 @@ final boolean writableBy(WriteScope scope) { abstract int size(); abstract int heapBytes(); + + void chargeTo(IntConsumer charge) { + } } diff --git a/engine/src/main/java/io/hstore/engine/tree/NodeCache.java b/engine/src/main/java/io/hstore/engine/tree/NodeCache.java index b9997fb..85ed75e 100644 --- a/engine/src/main/java/io/hstore/engine/tree/NodeCache.java +++ b/engine/src/main/java/io/hstore/engine/tree/NodeCache.java @@ -69,21 +69,23 @@ private Shard shard(long pageId) { private static final class Slot { final long pageId; final Node node; - final int bytes; + final int generation; + int bytes; volatile boolean referenced; Slot previous; Slot next; - Slot(long pageId, Node node, int bytes) { + Slot(long pageId, Node node, int bytes, int generation) { this.pageId = pageId; this.node = node; this.bytes = bytes; + this.generation = generation; } } private static final class Shard { private static final int MIN_TABLE = 64; - private static final Slot GONE = new Slot(0, null, 0); + private static final Slot GONE = new Slot(0, null, 0, -1); private static final VarHandle SLOTS = MethodHandles.arrayElementVarHandle(Slot[].class); private final ReentrantLock admission = new ReentrantLock(); @@ -92,6 +94,7 @@ private static final class Shard { private volatile long bytes; private int live; private int gone; + private int generation; private Slot hand; Shard(long budget) { @@ -190,9 +193,26 @@ void put(long pageId, Node node, int weight) { while (bytes + weight > budget) { evict(); } - Slot slot = new Slot(pageId, node, weight); + Slot slot = new Slot(pageId, node, weight, generation); link(slot); insert(slot); + node.chargeTo(grown -> charge(slot, grown)); + } finally { + admission.unlock(); + } + } + + private void charge(Slot slot, int grown) { + admission.lock(); + try { + if (slot.next == null || slot.generation != generation) { + return; + } + slot.bytes += grown; + bytes += grown; + while (bytes > budget && hand != null) { + evict(); + } } finally { admission.unlock(); } @@ -243,6 +263,7 @@ void clear() { table = new Slot[MIN_TABLE]; live = 0; gone = 0; + generation++; hand = null; bytes = 0; } finally { diff --git a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java index b007363..0cb1d92 100644 --- a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java +++ b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java @@ -9,6 +9,7 @@ import io.hstore.engine.page.PageType; import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.util.Collections; import java.util.List; @@ -81,6 +82,27 @@ private static Leaf decodeLeaf(ByteCursor in, TreeSchema schema, long[] keys, return Leaf.decoded(schema, keys, values, summary); } + private static int[] offsets(ByteCursor in, int count, PageHeader header, long pageId) { + int[] offsets = new int[count]; + long end = PageHeader.SIZE + (long) header.payloadLength(); + for (int i = 0; i < count; i++) { + long length = in.getVarLong(); + offsets[i] = Math.toIntExact(in.position()); + if (length < 0 || in.position() + length > end) { + throw HStoreException.corrupt(pageId, "value " + i + " runs past the end of the page"); + } + in.skip(length); + } + if (in.position() != end) { + throw HStoreException.corrupt(pageId, "values end at byte " + in.position() + ", not at the end of the payload " + end); + } + return offsets; + } + + private static MemorySegment owned(MemorySegment page) { + return page.isNative() ? MemorySegment.ofArray(page.toArray(ValueLayout.JAVA_BYTE)) : page; + } + private static void decodeFramed(ByteCursor in, TreeSchema schema, long[] keys, Object[] into) { long[] key = new long[1]; Object[] value = new Object[1]; @@ -170,6 +192,9 @@ public static Node decode(MemorySegment page, PageHeader header, long pageId, Tr long[] keys = decodeKeys(in, count); return switch (header.type()) { case LEAF -> { + if (schema.codec().independentValues()) { + yield Leaf.packed(schema, keys, owned(page), offsets(in, count, header, pageId), summary); + } Leaf leaf = decodeLeaf(in, schema, keys, summary); if (header.refersToPages() != refersToPages(leaf)) { throw HStoreException.corrupt(pageId, "page header and contents disagree on whether it refers to other pages"); diff --git a/engine/src/test/java/io/hstore/engine/tree/MemoryPages.java b/engine/src/test/java/io/hstore/engine/tree/MemoryPages.java index 679ca5c..118b507 100644 --- a/engine/src/test/java/io/hstore/engine/tree/MemoryPages.java +++ b/engine/src/test/java/io/hstore/engine/tree/MemoryPages.java @@ -37,6 +37,10 @@ public void accept(long pageId, MemorySegment image, Node frozen) { return tree.withRoot(materializer.materialize(tree.root(), tree.schema())); } + MemorySegment image(long pageId) { + return pages.get(pageId); + } + @Override public Node load(long pageId, TreeSchema schema) { loads++; diff --git a/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java new file mode 100644 index 0000000..44ac2db --- /dev/null +++ b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: Metacog Labs +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 + +package io.hstore.engine.tree; + +import io.hstore.engine.HStoreException; +import io.hstore.engine.catalog.AtomRecord; +import io.hstore.engine.catalog.EngineSlots; +import io.hstore.engine.page.ByteCursor; +import io.hstore.engine.page.PageHeader; +import io.hstore.engine.topology.EdgeKind; +import io.hstore.engine.topology.Incidence; +import io.hstore.engine.topology.TopologySchemas; + +import org.junit.jupiter.api.Test; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; +import java.util.random.RandomGenerator; +import java.util.random.RandomGeneratorFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PackedLeafTest { + + private static final int PAGE = 16 * 1024; + private static final int RECORDS = 60; + + private final MemoryPages pages = new MemoryPages(PAGE); + private final List expected = new ArrayList<>(); + private long rootPage; + + PackedLeafTest() { + WriteScope scope = new WriteScope(); + Tree catalog = Tree.empty(EngineSlots.ATOM_SCHEMA, pages); + for (int atom = 0; atom < RECORDS; atom++) { + AtomRecord record = atom % 3 == 0 ? AtomRecord.NodeRecord.of(1, "node-" + atom) : edge(scope, atom); + catalog = catalog.put(scope, atom, record); + } + Tree written = pages.persist(catalog); + rootPage = ((Ref.Stored) written.root()).pageId(); + written.stream().forEach(entry -> expected.add(entry.value())); + } + + private AtomRecord edge(WriteScope scope, int atom) { + Tree members = Tree.empty(TopologySchemas.SET_MEMBERS, pages); + for (long member = 1; member <= 1 + atom % 6; member++) { + members = members.put(scope, 1_000L * atom + member, Incidence.of(1_000L * atom + member)); + } + return new AtomRecord.EdgeRecord(2, EdgeKind.SET, members.root(), Ref.EMPTY, atom, 0, 0, 0); + } + + private Leaf load() { + return (Leaf) NodeCodec.decode(pages.image(rootPage), rootPage, EngineSlots.ATOM_SCHEMA); + } + + @Test + void aPackedLeafDecodesEachRecordOnlyWhenItIsRead() { + Leaf leaf = load(); + assertEquals(RECORDS, leaf.size()); + int packedOnly = leaf.heapBytes(); + assertEquals(expected.get(17), leaf.value(17)); + int oneRecord = leaf.heapBytes(); + assertTrue(oneRecord > packedOnly); + assertEquals(expected.get(17), leaf.value(17)); + assertEquals(oneRecord, leaf.heapBytes(), "a record read twice is unpacked once"); + for (int i = 0; i < RECORDS; i++) { + assertEquals(expected.get(i), leaf.value(i)); + } + Leaf eager = Leaf.decoded(EngineSlots.ATOM_SCHEMA, leaf.keyArray(), expected.toArray(), null); + assertTrue(leaf.sameEntries(eager)); + assertEquals(eager.bytes(), leaf.bytes()); + } + + @Test + void threadsReadingOneLeafSeeTheSameRecordsAndChargeEachOnce() throws Exception { + Leaf leaf = load(); + int packedOnly = leaf.heapBytes(); + AtomicLong charged = new AtomicLong(); + leaf.chargeTo(charged::addAndGet); + List> readers = new ArrayList<>(); + try (ExecutorService pool = Executors.newFixedThreadPool(8)) { + for (int thread = 0; thread < 8; thread++) { + RandomGenerator random = RandomGeneratorFactory.of("L64X128MixRandom").create(thread); + readers.add(pool.submit(() -> { + for (int round = 0; round < 2_000; round++) { + int i = random.nextInt(RECORDS); + assertEquals(expected.get(i), leaf.value(i)); + } + })); + } + for (Future reader : readers) { + reader.get(); + } + } + for (int i = 0; i < RECORDS; i++) { + leaf.value(i); + } + assertEquals(leaf.heapBytes() - packedOnly, charged.get()); + } + + @Test + void theCacheCountsRecordsAsTheyAreUnpackedAndStaysWithinBudget() { + Leaf leaf = load(); + NodeCache cache = new NodeCache(1 << 20, PAGE); + cache.put(rootPage, leaf, leaf.heapBytes()); + long before = cache.residentBytes(); + for (int i = 0; i < RECORDS; i++) { + leaf.value(i); + } + assertTrue(cache.residentBytes() > before); + assertEquals(leaf.heapBytes(), cache.residentBytes()); + + Leaf small = load(); + NodeCache tight = new NodeCache(small.heapBytes() + 2_000, PAGE); + tight.put(rootPage, small, small.heapBytes()); + for (int i = 0; i < RECORDS; i++) { + small.value(i); + assertTrue(tight.residentBytes() <= small.heapBytes() + 2_000, "resident " + tight.residentBytes()); + } + assertEquals(0, tight.residentBytes(), "the grown leaf no longer fits and is evicted"); + } + + @Test + void chargesForAnEvictedOrClearedLeafAreIgnored() { + NodeCache cache = new NodeCache(1 << 20, PAGE); + Leaf evicted = load(); + cache.put(rootPage, evicted, evicted.heapBytes()); + cache.put(rootPage, load(), 100); + long resident = cache.residentBytes(); + evicted.value(3); + assertEquals(resident, cache.residentBytes()); + + Leaf cleared = load(); + cache.put(rootPage + 1, cleared, cleared.heapBytes()); + cache.clear(); + cleared.value(4); + assertEquals(0, cache.residentBytes()); + } + + @Test + void aLengthThatDoesNotMatchTheRecordsIsReportedAsCorruption() { + MemorySegment image = pages.image(rootPage); + PageHeader header = PageHeader.verify(image, rootPage); + ByteCursor in = ByteCursor.over(image).position(PageHeader.SIZE); + for (int field = 0; field < 5; field++) { + in.getSignedVarLong(); + } + in.getLong(); + in.getSignedVarLong(); + for (int key = 1; key < header.slotCount(); key++) { + in.getVarLong(); + } + long first = in.position(); + MemorySegment corrupt = MemorySegment.ofArray(image.toArray(ValueLayout.JAVA_BYTE)); + byte length = corrupt.get(ValueLayout.JAVA_BYTE, first); + assertNotEquals(0, length); + corrupt.set(ValueLayout.JAVA_BYTE, first, (byte) (length + 1)); + PageHeader.seal(corrupt, header.payloadLength()); + assertThrows(HStoreException.class, () -> NodeCodec.decode(corrupt, rootPage, EngineSlots.ATOM_SCHEMA)); + } +} diff --git a/engine/src/test/java/io/hstore/engine/tree/TreeTest.java b/engine/src/test/java/io/hstore/engine/tree/TreeTest.java index abc1695..52dbcfc 100644 --- a/engine/src/test/java/io/hstore/engine/tree/TreeTest.java +++ b/engine/src/test/java/io/hstore/engine/tree/TreeTest.java @@ -406,6 +406,10 @@ void materializingAnEdgeRecordInlinesSmallTreesWithTheSameSummary() { assertEquals(edge.contentHash(), inlined.contentHash()); assertEquals(small.stream().toList(), new Tree<>(TopologySchemas.SET_MEMBERS, pages, inlined.members()).stream().toList()); Leaf root = (Leaf) written.node(written.root()); + int packedOnly = root.heapBytes(); + assertEquals(inlined, root.value(0)); + assertEquals(paged, root.value(1)); + assertTrue(root.heapBytes() > packedOnly); Leaf bare = Leaf.decoded(EngineSlots.ATOM_SCHEMA, new long[]{1}, new Object[]{AtomRecord.EdgeRecord.of(2, EdgeKind.SET)}, null); assertTrue(root.heapBytes() >= bare.heapBytes() + ((Ref.Inline) inlined.members()).leaf().heapBytes()); } From 5a1ca6927042fd71139e935df6ce5de959211a1b Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 17:20:10 +0530 Subject: [PATCH 09/15] docs: describe framed leaf records, packed leaves and format 8 --- docs/architecture/overview.md | 4 ++-- docs/operations/docker.md | 4 ++-- docs/operations/logging.md | 2 +- docs/storage/pages.md | 10 +++++++--- docs/storage/persistent-tree.md | 2 +- docs/transactions/wal-and-recovery.md | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index b6e01a3..dd3793f 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -154,7 +154,7 @@ sequenceDiagram Notes: -* Nodes are decoded once and then shared by every reader through `NodeCache`. It has 16 shards, each an open-addressing table keyed by page number plus a CLOCK (second-chance) ring, with a total byte budget of `node_cache_mb`, each node counted at an estimate of its heap size (`Node.heapBytes`). Lookups are lock-free; only admission takes a per-shard lock. Decoded nodes are immutable (`owner == null`), so sharing them needs no copying. See [pages.md](../storage/pages.md#caching-and-io-accounting). +* Nodes are decoded once and then shared by every reader through `NodeCache`. Catalog and posting-list leaves are kept packed and decode each record the first time it is read. It has 16 shards, each an open-addressing table keyed by page number plus a CLOCK (second-chance) ring, with a total byte budget of `node_cache_mb`, each node counted at an estimate of its heap size (`Node.heapBytes`). Lookups are lock-free; only admission takes a per-shard lock. Decoded nodes are immutable (`owner == null`), so sharing them needs no copying. See [pages.md](../storage/pages.md#caching-and-io-accounting). * `IoTrace` is bound with `ScopedValue.where(...)` around a query. `recordRead` throws `HStoreException.limit` once page reads plus cache hits exceed the query's page budget (`query_page_budget`). * A tree changed since the last checkpoint starts with in-memory nodes (`Ref.Pending`). A read follows those directly and goes through `PagedNodeSource` only once it reaches a stored reference. * Historical reads use `TransactionManager.snapshotAt(generation, branch)`. It looks the generation up in the retained `history` map; see [catalog-and-generations.md](../storage/catalog-and-generations.md#history-retention-and-time-travel). @@ -248,7 +248,7 @@ semantic/state 53 B persisted HNSW index state (datab | Path | Owner | Format | |---|---|---| -| `FORMAT` | `StorageEngine.verifyFormat` | `Properties` with `format=7` (commits logged as key changes and tree pages written at checkpoints, page numbers resolved through a page directory, reference counts per page, small member trees inline in their edge record) and `page-size` (the maximum node size). Opening a directory written in an older format, or opening with a different page size, fails. | +| `FORMAT` | `StorageEngine.verifyFormat` | `Properties` with `format=8` (leaf records framed by length, commits logged as key changes and tree pages written at checkpoints, page numbers resolved through a page directory, reference counts per page, small member trees inline in their edge record) and `page-size` (the maximum node size). Opening a directory written in an older format, or opening with a different page size, fails. | | `LOCK` | `StorageEngine.open` | An exclusive OS file lock. A second process gets `database ... is opened by another process`. | | `catalog/generations/%016x.cat` | `CatalogStore.publish` | `int magic 0x54414348`, `int crc32c(payload)`, `long length`, then the `CatalogImage` payload. Written to `*.tmp`, fsynced, atomically renamed, and the directory fsynced. The three newest files are kept. | | `catalog/manifest` | `CatalogStore` | The file name of the latest `.cat`, written durably the same way. | diff --git a/docs/operations/docker.md b/docs/operations/docker.md index 29b5888..cfdd6cc 100644 --- a/docs/operations/docker.md +++ b/docs/operations/docker.md @@ -259,8 +259,8 @@ skipped because `FORMAT` exists. To verify a backup, run `docker run --rm -v restored:/var/lib/hstore/data hstore check /var/lib/hstore/data`. To upgrade, stop the container, back up the volume, and start the new image on the same volume. `FORMAT` -records the on-disk format version (`format=7`, commits logged as key changes) and the page size. Both are checked at +records the on-disk format version (`format=8`, leaf records framed by length) and the page size. Both are checked at open. A directory written in another format is refused with -`database … uses storage format N; this build reads format 7 (commits logged as key changes); export and reload it`, +`database … uses storage format N; this build reads format 8 (leaf records framed by length); export and reload it`, so the server never misreads it. Export the data with HQL against the old version and reload it into a fresh volume. diff --git a/docs/operations/logging.md b/docs/operations/logging.md index 1319577..cbeaf4f 100644 --- a/docs/operations/logging.md +++ b/docs/operations/logging.md @@ -115,7 +115,7 @@ and in Docker it is the last line of `docker logs`. The storage-integrity failur | Message | Meaning | |---|---| -| `database

uses storage format N; this build reads format 7 (commits logged as key changes); export and reload it` | `FORMAT` was written by an incompatible build. | +| `database uses storage format N; this build reads format 8 (leaf records framed by length); export and reload it` | `FORMAT` was written by an incompatible build. | | `database uses N byte pages, options request M` | `page_size` differs from the value fixed at `init`. | | `write-ahead log segment is damaged () but later segments exist; replay would silently drop committed transactions` | `CORRUPT_LOG`: damage in a non-tail WAL segment. A torn record at the end of the *last* segment is normal after a crash. Replay simply ends there. Damage before later segments means storage corruption, so recovery refuses to guess. Restore from backup. | diff --git a/docs/storage/pages.md b/docs/storage/pages.md index b239157..96322d3 100644 --- a/docs/storage/pages.md +++ b/docs/storage/pages.md @@ -76,6 +76,8 @@ zigzag timeMax i64le roleBits keys: zigzag keys[0], then varint (keys[i] − keys[i−1]) for i = 1..slotCount−1 LEAF: schema.codec().encode(out, keys, values) (value format owned by the schema) + or, for a codec with independentValues(): + (varint length | value) × slotCount (catalog, posting lists, row codecs) INTERNAL: Ref × slotCount Ref := u8 0 empty tree | u8 1 | i64le pageId | varint units | Summary stored tree @@ -88,6 +90,8 @@ For an internal node, "keys" are the separators. `separators[0]` is the first ch **Reference encoding** (`tree/Ref.java`). Every reference starts with a tag byte. `0` is the empty tree and nothing follows. `1` is a stored tree: the page number, the image size in 64-byte units and the subtree's summary. `2` is an inline tree: the entry count `n` (1 to 32), then the leaf exactly as a leaf page payload encodes it, keys as a zigzag first key and varint gaps followed by `schema.codec().encode(...)`. There is no summary, because the reader recomputes it from the entries. An inline tree can only be decoded where the reader knows its schema, through `Ref.read(in, schema)`. `AtomCodec` passes the member schema of the edge's kind for `members` and `order-index` for `order`. The tree changes in a WAL `Changes` record are read with their schema as well, so a nested tree change can carry an inline tree. Everywhere else (branch children, catalog images, posting and property directories) `Ref.read(in)` rejects tag `2`, since only nested references in codecs that opt in with `inlinesSmallTrees()` are ever inline. An inline reference is never larger than `Ref.maxEncodedSize()` (108 bytes); see [persistent-tree.md](persistent-tree.md#inline-trees) for why that keeps pages from overflowing. +**Framed values.** A codec whose values are written one after another, each without reference to the others, says so with `ValueCodec.independentValues()`: `AtomCodec` (the catalog), `PostingsCodec` (posting directories) and every `ValueCodec.rows` codec. In their leaves `NodeCodec` writes each value's length in front of it, so a reader can find one value without decoding the ones before it. The length is counted in `TreeSchema.entryBytes`, so the leaf budget still keeps every image within a page. Columnar codecs, such as the member and incidence codecs, keep the plain layout. When a framed leaf is read from a page, `NodeCodec.decode` walks the lengths to record where each value starts, rejects the image if a value runs past the payload or the values stop short of its end, and returns a packed leaf ([caching](#nodecache-treenodecachejava)). An inline tree of a framed codec uses the same layout and is decoded in full. + An edge record with three plain members `{1000, 1001, 1005}` holds this as its `members` reference. It takes 7 bytes. A stored reference to the same tree would take 65 bytes, most of them summary, and point at an image of at least 128: ```text @@ -346,10 +350,10 @@ and the reference journal. ### `NodeCache` (`tree/NodeCache.java`) -The cache stores **decoded, frozen** `Node` objects, not bytes. A hit therefore costs no decoding and no allocation. The cache has 16 shards; a node goes to shard `mix(pageId) >>> 60`. Each shard has: +The cache stores **decoded, frozen** `Node` objects, not bytes. A hit therefore costs no decoding and no allocation. The exception is a leaf of framed values, which stays **packed**: it holds the page image, the decoded keys and the offset of every value, and decodes a value the first time it is read (`Leaf.value`). The decoded value is kept in the leaf, published with a compare-and-set so another thread sees it whole, and two threads that decode the same value at once keep whichever got there first. A catalog lookup on a cold page therefore decodes the keys and one record instead of every record and every inline member list on the page. The cache has 16 shards; a node goes to shard `mix(pageId) >>> 60`. Each shard has: * an open-addressing table of `Slot(pageId, node, volatile referenced)`, probed linearly from `mix(pageId)`, so a lookup needs no boxed key. It is kept at most half full, counting removed slots, and is rebuilt into a new array when it would pass that; -* a byte budget of `max(64 KiB, node_cache_mb / 16)` and a circular, doubly linked ring of slots with a CLOCK hand. Each slot weighs `Node.heapBytes()`, an estimate of the decoded node's heap size: 144 bytes for the node and its summary, its key and value or child arrays, then 96 bytes plus the encoded value per leaf entry, or 128 bytes per child reference in a branch. A leaf whose values are implied by its keys or member ids counts only its `long` arrays, and a catalog leaf adds the estimate of every inline tree it holds. Those constants come from measuring retained heap against cache contents at two dataset sizes; +* a byte budget of `max(64 KiB, node_cache_mb / 16)` and a circular, doubly linked ring of slots with a CLOCK hand. Each slot weighs `Node.heapBytes()`, an estimate of the decoded node's heap size: 144 bytes for the node and its summary, its key and value or child arrays, then 96 bytes plus the encoded value per leaf entry, or 128 bytes per child reference in a branch. A leaf whose values are implied by its keys or member ids counts only its `long` arrays, and a catalog leaf adds the estimate of every inline tree it holds. A packed leaf starts at its image size plus its key and offset arrays. Each value it decodes adds 96 bytes plus the encoded value and any inline tree in it, and charges that to the leaf's slot (`Node.chargeTo`), which evicts under the shard lock until the shard is back within budget. A charge for a slot that was evicted, replaced or cleared is dropped. Those constants come from measuring retained heap against cache contents at two dataset sizes; * a `ReentrantLock` taken only for admission. **Reads are lock-free.** `get` probes the table, reading each slot with acquire ordering, plus setting `referenced = true` if it was clear. Writers change the table only under the shard lock, publishing each slot with release ordering, and a rebuild publishes the new array through a volatile field. A reader still probing the old array can find a node that was just evicted; that is harmless, because a page number's contents never change. The check avoids a redundant volatile write on hot entries. @@ -390,7 +394,7 @@ Because images are never overwritten in place, a torn write can only damage an i | Version | Where | Meaning | |---|---|---| -| `format=7` in `/FORMAT` | `StorageEngine.verifyFormat` | Commits logged as key changes (`Changes` WAL records), tree pages written at checkpoints, no history in catalog images. Also tagged references with small member and order trees inline in their edge record, reference counts in `pages.ref`, page numbers resolved through `pages.dir`, images packed in 64-byte units, image sizes in every stored reference. Directories written by older builds (`format=1` fixed page slots, `format=2` references without sizes, `format=3` references by address, `format=4` no reference counts, `format=5` untagged references with every nested tree on its own page, `format=6` commits that logged their pages) fail to open: `uses storage format 6; this build reads format 7 (commits logged as key changes); export and reload it`. | +| `format=8` in `/FORMAT` | `StorageEngine.verifyFormat` | Leaf values of codecs with independent values framed by their length. Also commits logged as key changes (`Changes` WAL records), tree pages written at checkpoints, no history in catalog images, tagged references with small member and order trees inline in their edge record, reference counts in `pages.ref`, page numbers resolved through `pages.dir`, images packed in 64-byte units, image sizes in every stored reference. Directories written by older builds (`format=1` fixed page slots, `format=2` references without sizes, `format=3` references by address, `format=4` no reference counts, `format=5` untagged references with every nested tree on its own page, `format=6` commits that logged their pages, `format=7` unframed leaf values) fail to open: `uses storage format 7; this build reads format 8 (leaf records framed by length); export and reload it`. | | `page-size=N` in `/FORMAT` | same | Fixed at creation. A mismatching `--page_size` is rejected. | | header byte 4 = `1` | `PageHeader.FORMAT` | Node image layout; unchanged by packing and by page numbers. | | `pages.dir` header | `PageDirectory` | `i32 magic 0x52494450`, `i32 format 1`. | diff --git a/docs/storage/persistent-tree.md b/docs/storage/persistent-tree.md index d29de88..a0e1d4d 100644 --- a/docs/storage/persistent-tree.md +++ b/docs/storage/persistent-tree.md @@ -204,7 +204,7 @@ An inline leaf is frozen (`owner == null`), so a writer copies it before changin **Equality.** `Ref.same` compares stored references by page id and pending ones by identity. A pending node stamped by a published epoch is also the same as the reference it was written as, and two such nodes are the same when they were written as the same reference. That matters to a transaction that began before a checkpoint: the checkpoint swaps the head generation's pending nodes for stored references, and without this rule every tree the transaction touched would look changed. Inline references have no identity worth comparing: two reads of the same catalog page decode two separate leaves. They are compared by content instead. Two inline references are the same when they belong to the same schema and have the same keys and equal values (`Leaf.sameEntries`). The same content test applies between a pending leaf and an inline reference, and between two pending leaves, when both are small enough that a checkpoint would store them inline (`Ref.fitsInline`). `Ref.Inline.equals` uses the content test, so `EdgeRecord.equals` compares inline member trees by content too. This is what the conflict checks in `Transaction` and `Op`, the shortcuts in `TreeAlgebra` and `TreeDiff`, and the read validation of serializable transactions rely on. A stored and an inline reference are never the same, even with equal entries. Every caller treats "not the same" as "look closer", so that only costs a slower path. -**Heap.** The node cache weighs a catalog leaf with `Leaf.heapBytes()`, which adds the heap estimate of every inline leaf it holds, so inline trees count against `node_cache_mb` like any other node. +**Heap.** The node cache weighs a catalog leaf with `Leaf.heapBytes()`, which adds the heap estimate of every inline leaf it holds, so inline trees count against `node_cache_mb` like any other node. A catalog leaf read from a page is packed: it decodes a record, and the inline trees in it, only when that record is read, and charges the cache as it does ([pages.md](pages.md#nodecache-treenodecachejava)). ## Reading diff --git a/docs/transactions/wal-and-recovery.md b/docs/transactions/wal-and-recovery.md index f117699..dd16a69 100644 --- a/docs/transactions/wal-and-recovery.md +++ b/docs/transactions/wal-and-recovery.md @@ -27,7 +27,7 @@ Related: [transactions](transactions.md), [change feed](../storage/change-feed.m ```text / - FORMAT properties: format=7, page-size= + FORMAT properties: format=8, page-size= LOCK exclusive FileLock held while open catalog/manifest name of the current catalog image file catalog/generations/%016x.cat checkpointed catalog images (3 newest retained) From 5a0ea40e3382eb869a080e37d66313b10f3a5103 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 17:28:08 +0530 Subject: [PATCH 10/15] fix(tree): check a packed leaf's page flag when the tree is verified Decoding a leaf compared the header's refers-to-pages flag with its values, which a packed leaf can't do without decoding every record. The packed leaf now keeps the flag, and TreeVerifier, which decodes every record anyway, compares the two, so hstore check still catches a header that disagrees with its contents. --- docs/storage/maintenance.md | 1 + .../main/java/io/hstore/engine/tree/Leaf.java | 13 ++++++++++--- .../java/io/hstore/engine/tree/NodeCodec.java | 4 ++-- .../io/hstore/engine/tree/TreeVerifier.java | 3 +++ .../io/hstore/engine/tree/PackedLeafTest.java | 18 ++++++++++++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/docs/storage/maintenance.md b/docs/storage/maintenance.md index 7b306d9..9b2ec34 100644 --- a/docs/storage/maintenance.md +++ b/docs/storage/maintenance.md @@ -268,6 +268,7 @@ History and pins are also what hold dead pages back. A page waits until every ge * magic, format, page identity and CRC-32C (`PageHeader.verify`); * the schema id against the tree being read (`NodeCodec.decode`); * strictly ascending keys in leaves; +* for a packed leaf, that the header's refers-to-pages flag matches its records once all of them are decoded. Loading a packed leaf skips that comparison, because it would decode every record; * separators that route each child; * uniform height (balance); * every stored child summary equal to its recomputed contents; diff --git a/engine/src/main/java/io/hstore/engine/tree/Leaf.java b/engine/src/main/java/io/hstore/engine/tree/Leaf.java index 492b260..2ee4256 100644 --- a/engine/src/main/java/io/hstore/engine/tree/Leaf.java +++ b/engine/src/main/java/io/hstore/engine/tree/Leaf.java @@ -35,6 +35,7 @@ public final class Leaf extends Node { private final long[] words; private final MemorySegment packed; private final int[] offsets; + private final boolean flaggedAsReferring; private int size; private int keyBytes; private int valueBytes; @@ -49,19 +50,21 @@ private Leaf(TreeSchema schema, Object owner, long[] keys, Object[] values, l this.words = words; this.packed = null; this.offsets = null; + this.flaggedAsReferring = false; this.size = size; if (owner != null) { remeasure(); } } - private Leaf(TreeSchema schema, long[] keys, MemorySegment packed, int[] offsets, Summary summary) { + private Leaf(TreeSchema schema, long[] keys, MemorySegment packed, int[] offsets, boolean flaggedAsReferring, Summary summary) { super(schema, null, summary); this.keys = keys; this.values = new Object[keys.length]; this.words = null; this.packed = packed; this.offsets = offsets; + this.flaggedAsReferring = flaggedAsReferring; this.size = keys.length; } @@ -81,8 +84,12 @@ static Leaf implied(TreeSchema schema, long[] keys, long[] words, Summary sum return new Leaf(schema, null, keys, null, words, keys.length, summary); } - static Leaf packed(TreeSchema schema, long[] keys, MemorySegment page, int[] offsets, Summary summary) { - return new Leaf(schema, keys, page, offsets, summary); + static Leaf packed(TreeSchema schema, long[] keys, MemorySegment page, int[] offsets, boolean flaggedAsReferring, Summary summary) { + return new Leaf(schema, keys, page, offsets, flaggedAsReferring, summary); + } + + boolean pageFlagMatches() { + return packed == null || flaggedAsReferring == NodeCodec.refersToPages(this); } static int keyStreamBytes(long[] keys, int size) { diff --git a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java index 0cb1d92..17a79ac 100644 --- a/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java +++ b/engine/src/main/java/io/hstore/engine/tree/NodeCodec.java @@ -151,7 +151,7 @@ private static void encodeValues(ByteCursor out, TreeSchema schema, long[ } } - private static boolean refersToPages(Leaf leaf) { + static boolean refersToPages(Leaf leaf) { return leaf.schema.codec().holdsRefs() && refersToPages(leaf.schema, leaf.valueList()); } @@ -193,7 +193,7 @@ public static Node decode(MemorySegment page, PageHeader header, long pageId, Tr return switch (header.type()) { case LEAF -> { if (schema.codec().independentValues()) { - yield Leaf.packed(schema, keys, owned(page), offsets(in, count, header, pageId), summary); + yield Leaf.packed(schema, keys, owned(page), offsets(in, count, header, pageId), header.refersToPages(), summary); } Leaf leaf = decodeLeaf(in, schema, keys, summary); if (header.refersToPages() != refersToPages(leaf)) { diff --git a/engine/src/main/java/io/hstore/engine/tree/TreeVerifier.java b/engine/src/main/java/io/hstore/engine/tree/TreeVerifier.java index 2714d51..2a5b1d8 100644 --- a/engine/src/main/java/io/hstore/engine/tree/TreeVerifier.java +++ b/engine/src/main/java/io/hstore/engine/tree/TreeVerifier.java @@ -60,6 +60,9 @@ private Visit visit(Ref ref, TreeSchema schema, int expectedHeight) { report = report.plus(nested(schema, leaf.value(i))); } } + if (!leaf.pageFlagMatches()) { + throw fault(ref, "page header and contents disagree on whether it refers to other pages in " + schema); + } report = report.plus(new Report(0, leaf.size(), 0, 0)); } case Branch branch -> { diff --git a/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java index 44ac2db..b06a216 100644 --- a/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java +++ b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java @@ -37,7 +37,8 @@ class PackedLeafTest { private final MemoryPages pages = new MemoryPages(PAGE); private final List expected = new ArrayList<>(); - private long rootPage; + private final Ref root; + private final long rootPage; PackedLeafTest() { WriteScope scope = new WriteScope(); @@ -47,7 +48,8 @@ class PackedLeafTest { catalog = catalog.put(scope, atom, record); } Tree written = pages.persist(catalog); - rootPage = ((Ref.Stored) written.root()).pageId(); + root = written.root(); + rootPage = ((Ref.Stored) root).pageId(); written.stream().forEach(entry -> expected.add(entry.value())); } @@ -147,6 +149,18 @@ void chargesForAnEvictedOrClearedLeafAreIgnored() { assertEquals(0, cache.residentBytes()); } + @Test + void checkingTheTreeFindsAPackedLeafWhoseHeaderClaimsPageReferencesItLacks() { + MemorySegment image = pages.image(rootPage); + PageHeader header = PageHeader.verify(image, rootPage); + assertTrue(!header.refersToPages()); + new TreeVerifier(pages).verify(root, EngineSlots.ATOM_SCHEMA); + image.set(ValueLayout.JAVA_BYTE, 7, (byte) (image.get(ValueLayout.JAVA_BYTE, 7) | 1)); + PageHeader.seal(image, header.payloadLength()); + RuntimeException fault = assertThrows(RuntimeException.class, () -> new TreeVerifier(pages).verify(root, EngineSlots.ATOM_SCHEMA)); + assertTrue(fault.getMessage().contains("disagree on whether it refers to other pages"), fault.getMessage()); + } + @Test void aLengthThatDoesNotMatchTheRecordsIsReportedAsCorruption() { MemorySegment image = pages.image(rootPage); From 9291e1802f961b3ee21030f22c0eb9d96b0148c4 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Mon, 12 Oct 2026 17:52:30 +0530 Subject: [PATCH 11/15] perf(tree): copy or measure a packed leaf without keeping its records A writer copying a packed leaf, or measuring it, read every record through value(), which kept each decoded record in the frozen leaf and charged the node cache for it, one shard lock per record, for a leaf the write was about to replace. Both now decode records straight into the copy or the measurement and leave the packed leaf as it was. --- .../main/java/io/hstore/engine/tree/Leaf.java | 22 ++++++++++++++++--- .../io/hstore/engine/tree/PackedLeafTest.java | 15 +++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/engine/src/main/java/io/hstore/engine/tree/Leaf.java b/engine/src/main/java/io/hstore/engine/tree/Leaf.java index 2ee4256..9c31d1d 100644 --- a/engine/src/main/java/io/hstore/engine/tree/Leaf.java +++ b/engine/src/main/java/io/hstore/engine/tree/Leaf.java @@ -110,7 +110,7 @@ private static int gap(long low, long high) { private void remeasure() { int valueTotal = 0; for (int i = 0; i < size; i++) { - valueTotal += values != null ? schema.entryBytes(value(i)) : schema.impliedBytes(words[i]); + valueTotal += values != null ? schema.entryBytes(peek(i)) : schema.impliedBytes(words[i]); } keyBytes = keyStreamBytes(keys, size); valueBytes = valueTotal; @@ -231,9 +231,25 @@ Object value(int index) { return unpacked == ABSENT ? null : unpacked; } - private Object unpack(Object[] stored, int index) { + private Object peek(int index) { + if (packed == null) { + return value(index); + } + Object unpacked = UNPACKED_VALUES.getAcquire(values, index); + if (unpacked == null) { + return decode(index); + } + return unpacked == ABSENT ? null : unpacked; + } + + private Object decode(int index) { Object[] decoded = new Object[1]; schema.codec().decode(ByteCursor.over(packed).position(offsets[index]), new long[]{keys[index]}, decoded); + return decoded[0]; + } + + private Object unpack(Object[] stored, int index) { + Object[] decoded = {decode(index)}; Object value = decoded[0] == null ? ABSENT : decoded[0]; Object raced = UNPACKED_VALUES.compareAndExchangeRelease(stored, index, null, value); if (raced != null) { @@ -299,7 +315,7 @@ private Object[] valueArray(int capacity) { } Object[] copy = new Object[capacity]; for (int i = 0; i < size; i++) { - copy[i] = value(i); + copy[i] = peek(i); } return copy; } diff --git a/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java index b06a216..5eb195b 100644 --- a/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java +++ b/engine/src/test/java/io/hstore/engine/tree/PackedLeafTest.java @@ -83,6 +83,21 @@ void aPackedLeafDecodesEachRecordOnlyWhenItIsRead() { assertEquals(eager.bytes(), leaf.bytes()); } + @Test + void aWriterCopiesAPackedLeafWithoutKeepingOrChargingItsRecords() { + Leaf leaf = load(); + int packedOnly = leaf.heapBytes(); + AtomicLong charged = new AtomicLong(); + leaf.chargeTo(charged::addAndGet); + Leaf copy = leaf.copyFor(new WriteScope()); + for (int i = 0; i < RECORDS; i++) { + assertEquals(expected.get(i), copy.value(i)); + } + assertEquals(leaf.bytes(), copy.bytes()); + assertEquals(packedOnly, leaf.heapBytes()); + assertEquals(0, charged.get()); + } + @Test void threadsReadingOneLeafSeeTheSameRecordsAndChargeEachOnce() throws Exception { Leaf leaf = load(); From f65a7abacd51a119de2721a6fc7b897d80e1717b Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Tue, 13 Oct 2026 06:42:38 +0530 Subject: [PATCH 12/15] feat(bench): name the slowest single operation in the report macros The latency tables show the median of each run's maximum. The report also needs the single slowest operation across all runs, so gen.py now defines it for every latency workload. --- benchmarks/report/gen.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/report/gen.py b/benchmarks/report/gen.py index 63a993b..ecf1887 100644 --- a/benchmarks/report/gen.py +++ b/benchmarks/report/gen.py @@ -214,6 +214,8 @@ def latency_table(cfg, runs): cells.append(fmt_us(med(v)) if v else "--") if v: macro(f"lat{key}{engine.replace('hypergraphdb', 'hgdb')}{workload.replace('.', '')}{PCT[p]}", fmt_us(med(v))) + if p == "max": + macro(f"lat{key}{engine.replace('hypergraphdb', 'hgdb')}{workload.replace('.', '')}worst", fmt_us(max(v))) if all(c == "--" for c in cells): continue f.write(f"\\texttt{{{workload}}} & {label} & " + " & ".join(cells) + " \\\\\n") From cc914b6f7c52d96d0314f4663df853717c55af5f Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Tue, 13 Oct 2026 06:42:38 +0530 Subject: [PATCH 13/15] docs(report): rerun the comparison on leaf records decoded on demand All three configurations ran again on commit 9291e18, with both engines alternating in the same window. HStore now lists a hyperedge's members 3.2x faster than HyperGraphDB at scale 4, where it had been 0.13x, and two-hop queries are 4.4x faster. The text follows the new runs. HyperGraphDB opens a crashed store faster at scale 1, the mixed writer kept its schedule in both engines under sync, and JE wrote 497 KiB per synced single commit. The discussion explains why decoding one record per lookup keeps the catalog in a 64 MiB cache. --- benchmarks/report/report.tex | 2 +- benchmarks/report/results/progress.log | 109 +++++++++--------- .../report/results/s1-async/hstore-1.json | 2 +- .../report/results/s1-async/hstore-2.json | 2 +- .../report/results/s1-async/hstore-3.json | 2 +- .../report/results/s1-async/hstore-4.json | 2 +- .../report/results/s1-async/hstore-5.json | 2 +- .../results/s1-async/hypergraphdb-1.json | 2 +- .../results/s1-async/hypergraphdb-2.json | 2 +- .../results/s1-async/hypergraphdb-3.json | 2 +- .../results/s1-async/hypergraphdb-4.json | 2 +- .../results/s1-async/hypergraphdb-5.json | 2 +- .../report/results/s1-sync/hstore-1.json | 2 +- .../report/results/s1-sync/hstore-2.json | 2 +- .../report/results/s1-sync/hstore-3.json | 2 +- .../report/results/s1-sync/hstore-4.json | 2 +- .../report/results/s1-sync/hstore-5.json | 2 +- .../results/s1-sync/hypergraphdb-1.json | 2 +- .../results/s1-sync/hypergraphdb-2.json | 2 +- .../results/s1-sync/hypergraphdb-3.json | 2 +- .../results/s1-sync/hypergraphdb-4.json | 2 +- .../results/s1-sync/hypergraphdb-5.json | 2 +- .../report/results/s4-async/hstore-1.json | 2 +- .../report/results/s4-async/hstore-2.json | 2 +- .../report/results/s4-async/hstore-3.json | 2 +- .../results/s4-async/hypergraphdb-1.json | 2 +- .../results/s4-async/hypergraphdb-2.json | 2 +- .../results/s4-async/hypergraphdb-3.json | 2 +- benchmarks/report/sections/abstract.tex | 2 +- benchmarks/report/sections/conclusion.tex | 2 +- benchmarks/report/sections/discussion.tex | 10 +- benchmarks/report/sections/findings.tex | 10 +- benchmarks/report/sections/method.tex | 2 +- .../report/sections/results-configs.tex | 8 +- benchmarks/report/sections/results-sync.tex | 6 +- benchmarks/report/sections/results-text.tex | 12 +- benchmarks/report/sections/results.tex | 4 +- benchmarks/report/sections/systems.tex | 4 +- benchmarks/report/sections/validity.tex | 2 +- docs/benchmark-report.pdf | Bin 137863 -> 137844 bytes 40 files changed, 109 insertions(+), 116 deletions(-) diff --git a/benchmarks/report/report.tex b/benchmarks/report/report.tex index b99b507..81011d2 100644 --- a/benchmarks/report/report.tex +++ b/benchmarks/report/report.tex @@ -4,7 +4,7 @@ \input{gen/summary-fig} \title{\Large\bfseries HStore and HyperGraphDB:\\ a storage-level benchmark of two persistent hypergraph engines} -\author{Metacog Labs\\[2pt]\normalsize HStore 0.1.0, commit \texttt{459b8be}} +\author{Metacog Labs\\[2pt]\normalsize HStore 0.1.0, commit \texttt{9291e18}} \date{October 2026} \begin{document} diff --git a/benchmarks/report/results/progress.log b/benchmarks/report/results/progress.log index 6dc3133..c3f270d 100644 --- a/benchmarks/report/results/progress.log +++ b/benchmarks/report/results/progress.log @@ -1,57 +1,52 @@ -s1-async hstore run 1 start 13:45:31 load { 6.56 6.15 5.48 } -s1-async hstore run 1 exit 0 took 56s -s1-async hypergraphdb run 1 start 13:46:27 load { 6.82 6.30 5.58 } -s1-async hypergraphdb run 1 exit 0 took 72s -s1-async hstore run 2 start 13:47:39 load { 7.09 6.58 5.75 } -s1-async hstore run 2 exit 0 took 57s -s1-async hypergraphdb run 2 start 13:48:36 load { 8.27 7.14 6.02 } -s1-async hypergraphdb run 2 exit 0 took 90s -s1-async hstore run 3 start 13:50:06 load { 10.63 8.52 6.68 } -s1-async hstore run 3 exit 0 took 65s -s1-async hypergraphdb run 3 start 13:51:11 load { 8.31 8.34 6.74 } -s1-async hypergraphdb run 3 exit 0 took 115s -s1-async hstore run 4 start 13:53:06 load { 12.56 10.43 7.79 } -s1-async hstore run 4 exit 0 took 58s -s1-async hypergraphdb run 4 start 13:54:04 load { 7.35 9.32 7.56 } -s1-async hypergraphdb run 4 exit 0 took 85s -s1-async hstore run 5 start 13:55:29 load { 11.56 10.06 8.00 } -s1-async hstore run 5 exit 0 took 57s -s1-async hypergraphdb run 5 start 13:56:26 load { 8.61 9.55 7.94 } -s1-async hypergraphdb run 5 exit 0 took 86s -s1-async report done -s4-async hstore run 1 start 14:13:02 load { 8.95 8.88 8.85 } -s4-async hstore run 1 exit 0 took 170s -s4-async hypergraphdb run 1 start 14:15:52 load { 11.15 10.49 9.54 } -s4-async hypergraphdb run 1 exit 0 took 2000s -s4-async hstore run 2 start 14:49:12 load { 10.38 13.12 13.22 } -s4-async hstore run 2 exit 0 took 163s -s4-async hypergraphdb run 2 start 14:51:55 load { 8.82 11.80 12.68 } -s4-async hypergraphdb run 2 exit 0 took 1341s -s4-async hstore run 3 start 15:14:16 load { 6.71 8.33 9.84 } -s4-async hstore run 3 exit 0 took 89s -s4-async hypergraphdb run 3 start 15:15:45 load { 4.40 7.20 9.26 } -s4-async hypergraphdb run 3 exit 0 took 1626s -s4-async report done -ALL DONE -s1-sync hstore run 1 start 15:43:19 load { 8.80 17.45 15.33 } -s1-sync hstore run 1 exit 0 took 87s -s1-sync hypergraphdb run 1 start 15:44:46 load { 16.71 16.65 15.17 } -s1-sync hypergraphdb run 1 exit 0 took 128s -s1-sync hstore run 2 start 15:46:54 load { 13.48 15.05 14.70 } -s1-sync hstore run 2 exit 0 took 102s -s1-sync hypergraphdb run 2 start 15:48:36 load { 12.43 15.05 14.80 } -s1-sync hypergraphdb run 2 exit 0 took 136s -s1-sync hstore run 3 start 15:50:52 load { 14.54 15.45 15.02 } -s1-sync hstore run 3 exit 0 took 80s -s1-sync hypergraphdb run 3 start 15:52:12 load { 9.84 13.60 14.34 } -s1-sync hypergraphdb run 3 exit 0 took 113s -s1-sync hstore run 4 start 15:54:05 load { 8.01 11.98 13.62 } -s1-sync hstore run 4 exit 0 took 81s -s1-sync hypergraphdb run 4 start 15:55:26 load { 6.58 10.63 12.95 } -s1-sync hypergraphdb run 4 exit 0 took 120s -s1-sync hstore run 5 start 15:57:26 load { 9.35 10.26 12.50 } -s1-sync hstore run 5 exit 0 took 80s -s1-sync hypergraphdb run 5 start 15:58:46 load { 6.86 9.29 11.93 } -s1-sync hypergraphdb run 5 exit 0 took 133s -s1-sync report done -RERUN DONE +s1-async hstore run 1 start 04:40:47 load { 3.41 8.19 7.72 } +s1-async hstore run 1 exit 0 took 55s +s1-async hypergraphdb run 1 start 04:41:42 load { 5.33 8.06 7.71 } +s1-async hypergraphdb run 1 exit 0 took 77s +s1-async hstore run 2 start 04:42:59 load { 7.56 8.29 7.83 } +s1-async hstore run 2 exit 0 took 53s +s1-async hypergraphdb run 2 start 04:43:52 load { 7.07 8.17 7.82 } +s1-async hypergraphdb run 2 exit 0 took 68s +s1-async hstore run 3 start 04:45:00 load { 6.00 7.86 7.74 } +s1-async hstore run 3 exit 0 took 54s +s1-async hypergraphdb run 3 start 04:45:54 load { 5.68 7.55 7.64 } +s1-async hypergraphdb run 3 exit 0 took 68s +s1-async hstore run 4 start 04:47:02 load { 7.54 7.80 7.73 } +s1-async hstore run 4 exit 0 took 54s +s1-async hypergraphdb run 4 start 04:47:56 load { 5.59 7.23 7.52 } +s1-async hypergraphdb run 4 exit 0 took 70s +s1-async hstore run 5 start 04:49:06 load { 6.98 7.49 7.60 } +s1-async hstore run 5 exit 0 took 53s +s1-async hypergraphdb run 5 start 04:49:59 load { 5.41 7.01 7.42 } +s1-async hypergraphdb run 5 exit 0 took 80s +s1-sync hstore run 1 start 04:51:19 load { 6.04 6.81 7.30 } +s1-sync hstore run 1 exit 0 took 74s +s1-sync hypergraphdb run 1 start 04:52:33 load { 4.49 6.15 7.00 } +s1-sync hypergraphdb run 1 exit 0 took 90s +s1-sync hstore run 2 start 04:54:03 load { 5.99 6.46 7.05 } +s1-sync hstore run 2 exit 0 took 73s +s1-sync hypergraphdb run 2 start 04:55:16 load { 4.63 5.95 6.81 } +s1-sync hypergraphdb run 2 exit 0 took 92s +s1-sync hstore run 3 start 04:56:48 load { 5.95 6.22 6.83 } +s1-sync hstore run 3 exit 0 took 74s +s1-sync hypergraphdb run 3 start 04:58:02 load { 5.38 6.06 6.72 } +s1-sync hypergraphdb run 3 exit 0 took 92s +s1-sync hstore run 4 start 04:59:34 load { 6.11 6.33 6.77 } +s1-sync hstore run 4 exit 0 took 77s +s1-sync hypergraphdb run 4 start 05:00:51 load { 6.02 6.33 6.73 } +s1-sync hypergraphdb run 4 exit 0 took 99s +s1-sync hstore run 5 start 05:02:30 load { 8.15 7.06 6.97 } +s1-sync hstore run 5 exit 0 took 81s +s1-sync hypergraphdb run 5 start 05:03:51 load { 9.65 8.32 7.49 } +s1-sync hypergraphdb run 5 exit 0 took 102s +s4-async hstore run 1 start 05:05:33 load { 9.27 8.98 7.87 } +s4-async hstore run 1 exit 0 took 106s +s4-async hypergraphdb run 1 start 05:07:19 load { 7.57 8.94 8.01 } +s4-async hypergraphdb run 1 exit 0 took 1361s +s4-async hstore run 2 start 05:30:00 load { 10.90 9.07 9.32 } +s4-async hstore run 2 exit 0 took 92s +s4-async hypergraphdb run 2 start 05:31:32 load { 5.57 7.90 8.85 } +s4-async hypergraphdb run 2 exit 0 took 1893s +s4-async hstore run 3 start 06:03:05 load { 18.27 17.17 15.83 } +s4-async hstore run 3 exit 0 took 125s +s4-async hypergraphdb run 3 start 06:05:10 load { 15.86 16.83 15.90 } +s4-async hypergraphdb run 3 exit 0 took 1529s diff --git a/benchmarks/report/results/s1-async/hstore-1.json b/benchmarks/report/results/s1-async/hstore-1.json index 868be9c..60e30d5 100644 --- a/benchmarks/report/results/s1-async/hstore-1.json +++ b/benchmarks/report/results/s1-async/hstore-1.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4063756288,"retainedHeap":198440360,"bytesWritten":104663286,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1110.59275,"checksum":50000,"gcCount":1,"gcMillis":15,"allocated":999467040,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2934.5035,"checksum":362757,"gcCount":3,"gcMillis":91,"allocated":3797123584,"bytesWritten":37988472,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.105625,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":132032,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":35.78425,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029624,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":30.677792,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":78692608,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":35.613166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729544,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":46.690125,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1362464},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":28.88175,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26904392,"latency":{"p50":0.209,"p99":1.167,"p99.9":5.167,"max":34.333}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":248.694083,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":453815560,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":367.043291,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":384471168,"latency":{"p50":51.167,"p99":220.0,"p99.9":375.417,"max":3087.583},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":25295439,"millis":10000.096583,"checksum":0,"gcCount":2,"gcMillis":20,"allocated":1770223712,"latency":{"p50":1.5,"p99":11.166,"p99.9":41.166,"max":50947.292},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.096583,"checksum":0,"latency":{"p50":2377.416,"p99":12467.541,"p99.9":46956.208,"max":46956.208},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":123.239917,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":165889560,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.362084,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185880,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.183792,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16048,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":116.423542,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26924008},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":36.35375,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26598080,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95658930,"millis":1000.0,"checksum":95658930},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":977.084292,"checksum":20000,"gcCount":1,"gcMillis":40,"allocated":970820536,"bytesWritten":14633585,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":842.283042,"checksum":20000,"gcCount":1,"gcMillis":69,"allocated":576755456,"bytesWritten":13094827,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":14.518,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139936,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":125084385,"millis":1000.0,"checksum":125084385},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9980.762833,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85376576,"latency":{"p50":3234.208,"p99":5642.583,"p99.9":8913.167,"max":8913.167},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.198458,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81644440,"latency":{"p50":3066.125,"p99":7852.875,"p99.9":33621.583,"max":33621.583},"checked":false,"bytesWritten":10301605,"logicalWritten":16000,"compactionMillis":144,"reclaimed":31714458},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95197702,"millis":1000.0,"checksum":95197702},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":128.143208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":123668096,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4091543552,"retainedHeap":198474184,"bytesWritten":99703930,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":979.102541,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":1012632648,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2361.876625,"checksum":362757,"gcCount":3,"gcMillis":55,"allocated":3882120208,"bytesWritten":38760172,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":15.964541,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":145728,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":46.583333,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4026984,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":32.77125,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":82692232,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":27.086166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729784,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":26.8535,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1415360},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":26.084917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26937560,"latency":{"p50":0.209,"p99":0.5,"p99.9":1.5,"max":91.916}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":305.719375,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":475855128,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":403.635041,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":398671576,"latency":{"p50":48.542,"p99":297.667,"p99.9":441.042,"max":7103.125},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":25420570,"millis":10000.125625,"checksum":0,"gcCount":1,"gcMillis":7,"allocated":1629705568,"latency":{"p50":1.542,"p99":10.375,"p99.9":30.666,"max":38283.292},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.125625,"checksum":0,"latency":{"p50":2478.708,"p99":14502.083,"p99.9":45643.75,"max":45643.75},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":174.389209,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":115764688,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":4.531584,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176736,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.091792,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16000,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":111.496458,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26931424},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":28.911208,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28056488,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96884225,"millis":1000.0,"checksum":96884225},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":991.621291,"checksum":20000,"gcCount":1,"gcMillis":87,"allocated":1061541856,"bytesWritten":15127084,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":463.443459,"checksum":20000,"gcCount":1,"gcMillis":43,"allocated":484267280,"bytesWritten":5929386,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":10.795459,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":125760,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":119597544,"millis":1000.0,"checksum":119597544},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.07325,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":103065488,"latency":{"p50":3312.166,"p99":5419.333,"p99.9":7087.666,"max":7087.666},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.020666,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":99604520,"latency":{"p50":3440.666,"p99":5602.458,"p99.9":9286.083,"max":9286.083},"checked":false,"bytesWritten":10762850,"logicalWritten":16000,"compactionMillis":141,"reclaimed":25796186},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95658103,"millis":1000.0,"checksum":95658103},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":127.91475,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":125736984,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-2.json b/benchmarks/report/results/s1-async/hstore-2.json index 0c341eb..6f9f32a 100644 --- a/benchmarks/report/results/s1-async/hstore-2.json +++ b/benchmarks/report/results/s1-async/hstore-2.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":3995598848,"retainedHeap":197260856,"bytesWritten":101110329,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":756.365667,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":990208600,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2682.222,"checksum":362757,"gcCount":3,"gcMillis":97,"allocated":3671635576,"bytesWritten":37990098,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.383042,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134360,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":19.221208,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029432,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":34.6085,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":78692488,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":41.534167,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729904,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":35.300125,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1314496},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":45.575541,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29315392,"latency":{"p50":0.333,"p99":2.0,"p99.9":18.5,"max":805.083}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":375.880042,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":441976848,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":635.719708,"checksum":5000,"gcCount":1,"gcMillis":11,"allocated":396433248,"latency":{"p50":85.084,"p99":429.625,"p99.9":1622.708,"max":14678.5},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":22423724,"millis":10000.23475,"checksum":0,"gcCount":2,"gcMillis":38,"allocated":2016530984,"latency":{"p50":1.625,"p99":11.875,"p99.9":57.416,"max":51617.583},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.23475,"checksum":0,"latency":{"p50":2443.209,"p99":23564.542,"p99.9":47717.0,"max":47717.0},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":113.728875,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":102521152,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.466458,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186960,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.455083,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15736,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":119.029708,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26926216},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":36.214584,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26584888,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95661962,"millis":1000.0,"checksum":95661962},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":974.477625,"checksum":20000,"gcCount":1,"gcMillis":47,"allocated":922298896,"bytesWritten":14598133,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":602.26525,"checksum":20000,"gcCount":1,"gcMillis":73,"allocated":495480008,"bytesWritten":9583710,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":38.072459,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":125640,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":121520706,"millis":1000.0,"checksum":121520706},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9980.727458,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84942160,"latency":{"p50":3400.25,"p99":9814.291,"p99.9":14704.375,"max":14704.375},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9980.950125,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81384768,"latency":{"p50":3069.708,"p99":5722.208,"p99.9":7893.583,"max":7893.583},"checked":false,"bytesWritten":10293591,"logicalWritten":16000,"compactionMillis":131,"reclaimed":28158938},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95189543,"millis":1000.0,"checksum":95189543},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":163.017458,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121208816,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4114087936,"retainedHeap":200992232,"bytesWritten":101402368,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":719.336,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":989389576,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":1825.084208,"checksum":362757,"gcCount":3,"gcMillis":52,"allocated":3631105240,"bytesWritten":36645552,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":15.293542,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":140272,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":19.499625,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4016336,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":33.034416,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81184048,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":18.405458,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729808,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":19.70775,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1533816},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":22.939834,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":16253024,"latency":{"p50":0.208,"p99":0.334,"p99.9":0.459,"max":43.958}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":269.118833,"checksum":20000,"gcCount":1,"gcMillis":6,"allocated":512738464,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":431.66675,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":393578752,"latency":{"p50":54.25,"p99":295.625,"p99.9":672.042,"max":1372.708},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":24671256,"millis":10000.152084,"checksum":0,"gcCount":3,"gcMillis":33,"allocated":2423673848,"latency":{"p50":1.791,"p99":10.292,"p99.9":25.667,"max":33406.666},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.152084,"checksum":0,"latency":{"p50":2404.334,"p99":11794.875,"p99.9":20479.917,"max":20479.917},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":102.870916,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":174374056,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":4.939542,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185424,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.809125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":6415952,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":107.149916,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26940120},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":24.140917,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28043920,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":94754971,"millis":1000.0,"checksum":94754971},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":816.675083,"checksum":20000,"gcCount":1,"gcMillis":24,"allocated":1088859816,"bytesWritten":14955062,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":417.530958,"checksum":20000,"gcCount":1,"gcMillis":32,"allocated":535310256,"bytesWritten":9910298,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":11.906834,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":4132936,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":121305832,"millis":1000.0,"checksum":121305832},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.5945,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":102783528,"latency":{"p50":3349.0,"p99":5301.875,"p99.9":8799.75,"max":8799.75},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.838,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":99499904,"latency":{"p50":3382.459,"p99":5317.417,"p99.9":5929.084,"max":5929.084},"checked":false,"bytesWritten":10767018,"logicalWritten":16000,"compactionMillis":136,"reclaimed":27508122},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95654455,"millis":1000.0,"checksum":95654455},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":152.832917,"checksum":1,"gcCount":1,"gcMillis":41,"allocated":127721952,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-3.json b/benchmarks/report/results/s1-async/hstore-3.json index 3903e60..4a078b0 100644 --- a/benchmarks/report/results/s1-async/hstore-3.json +++ b/benchmarks/report/results/s1-async/hstore-3.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4095737856,"retainedHeap":197986800,"bytesWritten":115546988,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1053.86975,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":987869088,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4429.275083,"checksum":362757,"gcCount":3,"gcMillis":123,"allocated":3691878408,"bytesWritten":40040087,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":25.017625,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":120496,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":24.439875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4018544,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":157.194666,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612536,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":33.197042,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3730240,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":32.734333,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1651592},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":50.7425,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":23692928,"latency":{"p50":0.375,"p99":2.709,"p99.9":27.75,"max":166.834}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":357.3,"checksum":20000,"gcCount":1,"gcMillis":6,"allocated":419038416,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":936.245292,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":389565280,"latency":{"p50":146.166,"p99":881.583,"p99.9":4865.833,"max":8791.416},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":18796611,"millis":10040.486084,"checksum":0,"gcCount":3,"gcMillis":120,"allocated":2109244600,"latency":{"p50":1.041,"p99":12.666,"p99.9":232.042,"max":82056.916},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10040.486084,"checksum":0,"latency":{"p50":3601.584,"p99":62396.125,"p99.9":75346.459,"max":75346.459},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":190.891167,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103018120,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":10.395958,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186144,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":39.434125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":2432240,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":373.048959,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":28532144},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":359.733791,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26597768,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97726824,"millis":1000.0,"checksum":97726824},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3303.012208,"checksum":20000,"gcCount":1,"gcMillis":125,"allocated":1039731824,"bytesWritten":23350772,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1382.23125,"checksum":20000,"gcCount":1,"gcMillis":109,"allocated":495051496,"bytesWritten":13203797,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":29.9955,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139384,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":136052276,"millis":1000.0,"checksum":136052276},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.352083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84424736,"latency":{"p50":5325.0,"p99":53032.542,"p99.9":92712.458,"max":92712.458},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9982.936,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80885240,"latency":{"p50":5412.0,"p99":34581.959,"p99.9":80797.584,"max":80797.584},"checked":false,"bytesWritten":10307535,"logicalWritten":16000,"compactionMillis":253,"reclaimed":42677082},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95202978,"millis":1000.0,"checksum":95202978},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":356.205583,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121119712,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4103077888,"retainedHeap":199512344,"bytesWritten":103744704,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":738.11075,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":974522832,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":1998.317583,"checksum":362757,"gcCount":3,"gcMillis":52,"allocated":3797253160,"bytesWritten":38936844,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":18.199292,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134144,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":15.850292,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4015904,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":25.369208,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81183808,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":17.421166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":21.869917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1614312},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":41.423167,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29297648,"latency":{"p50":0.334,"p99":0.875,"p99.9":3.209,"max":1458.0}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":397.962417,"checksum":20000,"gcCount":1,"gcMillis":8,"allocated":458383432,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":480.330459,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":400663080,"latency":{"p50":59.209,"p99":408.334,"p99.9":1064.25,"max":1850.083},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":24256450,"millis":10004.688875,"checksum":0,"gcCount":3,"gcMillis":36,"allocated":2893813872,"latency":{"p50":1.917,"p99":10.959,"p99.9":25.833,"max":28509.166},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10004.688875,"checksum":0,"latency":{"p50":2325.417,"p99":6227.417,"p99.9":22015.083,"max":22015.083},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":81.951042,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":115072032,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.702834,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176544,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.569542,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15664,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":111.559875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26931392},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":24.722833,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28057904,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97063290,"millis":1000.0,"checksum":97063290},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":892.22825,"checksum":20000,"gcCount":1,"gcMillis":75,"allocated":1161214736,"bytesWritten":15000052,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":468.443542,"checksum":20000,"gcCount":1,"gcMillis":40,"allocated":624004168,"bytesWritten":9925897,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":12.087541,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139576,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":123673576,"millis":1000.0,"checksum":123673576},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.920375,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105591776,"latency":{"p50":3241.417,"p99":5668.459,"p99.9":7184.875,"max":7184.875},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.31,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":102411824,"latency":{"p50":3358.208,"p99":5443.625,"p99.9":6480.042,"max":6480.042},"checked":false,"bytesWritten":10757473,"logicalWritten":16000,"compactionMillis":125,"reclaimed":29878490},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95651798,"millis":1000.0,"checksum":95651798},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":129.437375,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":127222768,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-4.json b/benchmarks/report/results/s1-async/hstore-4.json index 85f6342..7b5348b 100644 --- a/benchmarks/report/results/s1-async/hstore-4.json +++ b/benchmarks/report/results/s1-async/hstore-4.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4089970688,"retainedHeap":200245080,"bytesWritten":109641607,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1108.050708,"checksum":50000,"gcCount":1,"gcMillis":15,"allocated":985598200,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4435.9505,"checksum":362757,"gcCount":3,"gcMillis":99,"allocated":3860986248,"bytesWritten":37955181,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":13.108334,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":131960,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":27.070375,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":16120,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":50.317875,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612512,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":29.462125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3730048,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":22.989209,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1580856},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":31.755125,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26915264,"latency":{"p50":0.291,"p99":0.583,"p99.9":1.375,"max":22.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":241.306667,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":432211440,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":477.44425,"checksum":5000,"gcCount":1,"gcMillis":5,"allocated":381770392,"latency":{"p50":68.042,"p99":260.375,"p99.9":360.958,"max":4704.625},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":18645214,"millis":10000.100209,"checksum":0,"gcCount":2,"gcMillis":25,"allocated":2258659360,"latency":{"p50":2.042,"p99":14.583,"p99.9":56.75,"max":35207.75},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.100209,"checksum":0,"latency":{"p50":3093.834,"p99":16699.5,"p99.9":44883.542,"max":44883.542},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":110.352542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":166203976,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":8.851125,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176640,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.086125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16120,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":137.524833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26928576},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":35.694458,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26596688,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95626418,"millis":1000.0,"checksum":95626418},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1983.6315,"checksum":20000,"gcCount":1,"gcMillis":94,"allocated":1078600728,"bytesWritten":23168100,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":748.663667,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":544472168,"bytesWritten":9592470,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":13.349,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139936,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":130133248,"millis":1000.0,"checksum":130133248},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.382667,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84995992,"latency":{"p50":5151.5,"p99":9627.042,"p99.9":10007.917,"max":10007.917},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.801833,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81324416,"latency":{"p50":5154.792,"p99":9392.5,"p99.9":16033.542,"max":16033.542},"checked":false,"bytesWritten":10281059,"logicalWritten":16000,"compactionMillis":121,"reclaimed":36784474},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95176549,"millis":1000.0,"checksum":95176549},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":112.418083,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":120433272,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4114087936,"retainedHeap":198646712,"bytesWritten":102847801,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":685.309792,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":978189216,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2079.485292,"checksum":362757,"gcCount":3,"gcMillis":54,"allocated":3657551624,"bytesWritten":38257459,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":12.205333,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":146016,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":16.353917,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029744,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":26.163666,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81183832,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":29.777875,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729808,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":38.185917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1416048},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":38.934917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26918072,"latency":{"p50":0.333,"p99":0.875,"p99.9":1.75,"max":42.833}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":286.207042,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":519803216,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":391.8315,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":387807400,"latency":{"p50":51.958,"p99":295.708,"p99.9":421.625,"max":3631.417},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":24574903,"millis":10000.099292,"checksum":0,"gcCount":3,"gcMillis":19,"allocated":2737518992,"latency":{"p50":1.792,"p99":10.459,"p99.9":24.125,"max":26011.959},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.099292,"checksum":0,"latency":{"p50":2308.959,"p99":9654.334,"p99.9":21528.917,"max":21528.917},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":77.01275,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":112959136,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.401625,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176208,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.921917,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15856,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":110.852625,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26939008},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":28.7235,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":27026264,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96377516,"millis":1000.0,"checksum":96377516},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":708.286833,"checksum":20000,"gcCount":1,"gcMillis":55,"allocated":1012562832,"bytesWritten":14864709,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":402.713417,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":476658608,"bytesWritten":9838345,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":12.734916,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139600,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":122763975,"millis":1000.0,"checksum":122763975},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.116292,"checksum":0,"gcCount":1,"gcMillis":34,"allocated":103038752,"latency":{"p50":3350.0,"p99":5651.375,"p99.9":16125.375,"max":16125.375},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.993166,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":99832912,"latency":{"p50":3182.833,"p99":5382.5,"p99.9":5584.333,"max":5584.333},"checked":false,"bytesWritten":10762850,"logicalWritten":16000,"compactionMillis":135,"reclaimed":28962586},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95658134,"millis":1000.0,"checksum":95658134},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":105.829875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":126893448,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hstore-5.json b/benchmarks/report/results/s1-async/hstore-5.json index 28fb3bc..0d91c17 100644 --- a/benchmarks/report/results/s1-async/hstore-5.json +++ b/benchmarks/report/results/s1-async/hstore-5.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094164992,"retainedHeap":200531568,"bytesWritten":105137547,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1518.893958,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":985770024,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3546.368917,"checksum":362757,"gcCount":3,"gcMillis":77,"allocated":3706344024,"bytesWritten":37893618,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":16.430833,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134192,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":65.399458,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":18760,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":44.663916,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":84040936,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":28.766875,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729928,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":30.294625,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1650528},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":35.596875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26911304,"latency":{"p50":0.333,"p99":0.583,"p99.9":1.792,"max":57.792}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":256.111792,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":421594592,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":865.030208,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":408607112,"latency":{"p50":150.291,"p99":469.0,"p99.9":3506.25,"max":21544.75},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":17827972,"millis":10000.165958,"checksum":0,"gcCount":2,"gcMillis":43,"allocated":2467485280,"latency":{"p50":2.083,"p99":16.625,"p99.9":63.792,"max":63347.042},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.165958,"checksum":0,"latency":{"p50":3234.958,"p99":11541.542,"p99.9":30306.5,"max":30306.5},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":103.976542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103415608,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":11.76425,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176376,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.400458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15664,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":120.964958,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26953384},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":33.046209,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26597984,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95564338,"millis":1000.0,"checksum":95564338},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1227.695584,"checksum":20000,"gcCount":1,"gcMillis":40,"allocated":1075214984,"bytesWritten":18705415,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":820.833834,"checksum":20000,"gcCount":1,"gcMillis":34,"allocated":634479424,"bytesWritten":9607370,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":13.673375,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139408,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":125586081,"millis":1000.0,"checksum":125586081},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.770583,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85422328,"latency":{"p50":5059.666,"p99":9601.666,"p99.9":10602.958,"max":10602.958},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.094083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81504920,"latency":{"p50":5352.75,"p99":9473.5,"p99.9":9900.917,"max":9900.917},"checked":false,"bytesWritten":10286347,"logicalWritten":16000,"compactionMillis":135,"reclaimed":32232282},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95181574,"millis":1000.0,"checksum":95181574},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":106.864125,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":118872736,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4093640704,"retainedHeap":199019744,"bytesWritten":103454943,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":809.52525,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":959535112,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":1958.658208,"checksum":362757,"gcCount":3,"gcMillis":57,"allocated":3717006136,"bytesWritten":38961762,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":26.04875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":159032,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":15.605167,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029600,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":35.694375,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81184000,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":17.815625,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3728856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":21.883583,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1399576},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":21.184833,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29321504,"latency":{"p50":0.208,"p99":0.292,"p99.9":0.375,"max":21.958}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":185.181209,"checksum":20000,"gcCount":1,"gcMillis":10,"allocated":460237920,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":335.724708,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":390118080,"latency":{"p50":48.458,"p99":248.917,"p99.9":320.083,"max":368.167},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":23684964,"millis":10000.08025,"checksum":0,"gcCount":2,"gcMillis":15,"allocated":1837281264,"latency":{"p50":1.958,"p99":10.791,"p99.9":23.917,"max":26809.625},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.08025,"checksum":0,"latency":{"p50":2305.916,"p99":8403.333,"p99.9":24257.166,"max":24257.166},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":66.216042,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":124266488,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":4.070875,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185520,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":10.389541,"checksum":100000,"gcCount":1,"gcMillis":1,"allocated":6420920,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":131.383042,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26925816},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":24.179625,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":27012496,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97088769,"millis":1000.0,"checksum":97088769},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":668.516791,"checksum":20000,"gcCount":1,"gcMillis":49,"allocated":1115357056,"bytesWritten":14761514,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":512.802833,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":623618424,"bytesWritten":9837728,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":11.34475,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":133392,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":123368488,"millis":1000.0,"checksum":123368488},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9981.852042,"checksum":0,"gcCount":1,"gcMillis":20,"allocated":105524400,"latency":{"p50":3148.833,"p99":5432.5,"p99.9":21670.083,"max":21670.083},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9982.379625,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":102277536,"latency":{"p50":3306.791,"p99":5571.416,"p99.9":6167.875,"max":6167.875},"checked":false,"bytesWritten":10769501,"logicalWritten":16000,"compactionMillis":136,"reclaimed":29567770},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95657430,"millis":1000.0,"checksum":95657430},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":106.008625,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":126626976,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-1.json b/benchmarks/report/results/s1-async/hypergraphdb-1.json index 187a27c..a3d294b 100644 --- a/benchmarks/report/results/s1-async/hypergraphdb-1.json +++ b/benchmarks/report/results/s1-async/hypergraphdb-1.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4198498304,"retainedHeap":124392728,"bytesWritten":3234823471,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1513.681,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":932255920,"bytesWritten":53141197,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2778.429583,"checksum":362757,"gcCount":3,"gcMillis":58,"allocated":3019900976,"bytesWritten":240007394,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":135.2295,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9949680,"bytesRead":23181792,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":46.060333,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":8794237,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":364.878916,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":160164320,"bytesRead":88678933,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":76.568125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417480,"bytesRead":44603919,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":367.729292,"checksum":5852224,"gcCount":1,"gcMillis":7,"allocated":717458144},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":335.150208,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":446144992,"latency":{"p50":1.5,"p99":16.042,"p99.9":223.333,"max":6152.834}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":2288.69475,"checksum":20000,"gcCount":3,"gcMillis":62,"allocated":2924873256,"bytesWritten":128570323,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":521.339541,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":766743616,"latency":{"p50":47.917,"p99":737.583,"p99.9":7922.916,"max":25559.959},"bytesWritten":36413008,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10212917,"millis":10000.349417,"checksum":0,"gcCount":21,"gcMillis":230,"allocated":25420805624,"latency":{"p50":3.042,"p99":52.792,"p99.9":595.458,"max":87423.5},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.349417,"checksum":0,"latency":{"p50":3349.667,"p99":80123.667,"p99.9":97707.958,"max":97707.958},"checked":false,"bytesWritten":604649487,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":925.804083,"checksum":50000,"gcCount":2,"gcMillis":23,"allocated":1620325720,"bytesWritten":92232677,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":17.090167,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":7669449,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":129.551917,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10119240,"bytesRead":41327494,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1368.52825,"checksum":1,"gcCount":2,"gcMillis":10,"allocated":2931026872},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1809.977458,"checksum":831528,"gcCount":2,"gcMillis":70,"allocated":594967960,"bytesRead":445314319,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":392387800,"millis":1000.0,"checksum":392387800},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3130.773625,"checksum":20000,"gcCount":5,"gcMillis":106,"allocated":5426157848,"bytesWritten":220849834,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1282.161875,"checksum":20000,"gcCount":3,"gcMillis":30,"allocated":3215441736,"bytesWritten":145907712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":41.606041,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9753792,"bytesRead":14659339,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":284950082,"millis":1000.0,"checksum":284950082},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.897292,"checksum":0,"gcCount":3,"gcMillis":170,"allocated":3898173400,"latency":{"p50":3171.167,"p99":27988.417,"p99.9":67654.208,"max":67654.208},"checked":false,"bytesWritten":230752466,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.537541,"checksum":0,"gcCount":4,"gcMillis":60,"allocated":4202197208,"latency":{"p50":3251.458,"p99":6651.541,"p99.9":28435.708,"max":28435.708},"checked":false,"bytesWritten":253277189,"logicalWritten":16000,"compactionMillis":40,"reclaimed":-24535345},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":213885415,"millis":1000.0,"checksum":213885415},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":81.578875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83435656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4199022592,"retainedHeap":124140736,"bytesWritten":4560098472,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1249.94025,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":933521920,"bytesWritten":52955141,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2859.487375,"checksum":362757,"gcCount":3,"gcMillis":58,"allocated":2987110392,"bytesWritten":238357636,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":64.046042,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9780880,"bytesRead":24615421,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":33.352334,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":16718180,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":8942.771,"checksum":20287,"gcCount":16,"gcMillis":133,"allocated":160143672,"bytesRead":3929815270,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":140.832166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18415888,"bytesRead":17593801,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":343.74275,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":500398744},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":231.4905,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":404496928,"latency":{"p50":1.583,"p99":11.667,"p99.9":38.167,"max":450.333}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1046.197417,"checksum":20000,"gcCount":2,"gcMillis":13,"allocated":2342886240,"bytesWritten":116022737,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":203.156791,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":473593944,"latency":{"p50":28.792,"p99":247.125,"p99.9":546.708,"max":7362.292},"bytesWritten":28652809,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":11320098,"millis":10000.123875,"checksum":0,"gcCount":22,"gcMillis":151,"allocated":28878509976,"latency":{"p50":3.333,"p99":54.542,"p99.9":295.625,"max":28501.584},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.123875,"checksum":0,"latency":{"p50":2843.542,"p99":16937.875,"p99.9":34414.417,"max":34414.417},"checked":false,"bytesWritten":855484992,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":478.62375,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":1306875344,"bytesWritten":79615321,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":15.422292,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6996097,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":48.629625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9786056,"bytesRead":26101120,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1361.156958,"checksum":1,"gcCount":2,"gcMillis":9,"allocated":2819783688},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1103.657167,"checksum":831528,"gcCount":2,"gcMillis":43,"allocated":573054024,"bytesRead":330238625,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":363228978,"millis":1000.0,"checksum":363228978},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2190.502041,"checksum":20000,"gcCount":4,"gcMillis":28,"allocated":4780839720,"bytesWritten":199184041,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1243.9325,"checksum":20000,"gcCount":2,"gcMillis":18,"allocated":2819573736,"bytesWritten":135815276,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":107.29025,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9871544,"bytesRead":31027227,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":297276902,"millis":1000.0,"checksum":297276902},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.679042,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":340630232,"latency":{"p50":3363.083,"p99":5659.083,"p99.9":10355.25,"max":10355.25},"checked":false,"bytesWritten":4139954,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.8075,"checksum":0,"gcCount":1,"gcMillis":104,"allocated":1575473592,"latency":{"p50":3169.417,"p99":26950.875,"p99.9":106715.792,"max":106715.792},"checked":false,"bytesWritten":99282792,"logicalWritten":16000,"compactionMillis":48,"reclaimed":683041},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":263891698,"millis":1000.0,"checksum":263891698},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":72.812208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":81774872,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-2.json b/benchmarks/report/results/s1-async/hypergraphdb-2.json index 61d82f9..652aeda 100644 --- a/benchmarks/report/results/s1-async/hypergraphdb-2.json +++ b/benchmarks/report/results/s1-async/hypergraphdb-2.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4210032640,"retainedHeap":124633288,"bytesWritten":3161349189,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":2005.627542,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":925782544,"bytesWritten":52909220,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3798.647041,"checksum":362757,"gcCount":2,"gcMillis":58,"allocated":2987198488,"bytesWritten":236525956,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":109.109084,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9863384,"bytesRead":15186575,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":58.748458,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":13480269,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":436.989083,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":157969832,"bytesRead":99888201,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":134.086792,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418248,"bytesRead":36838574,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":438.069833,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":424480128},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":296.171542,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":458090088,"latency":{"p50":1.459,"p99":19.666,"p99.9":69.583,"max":914.042}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3912.796542,"checksum":20000,"gcCount":5,"gcMillis":50,"allocated":5083525064,"bytesWritten":212817273,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":862.323042,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":1035299024,"latency":{"p50":91.459,"p99":1082.834,"p99.9":5997.167,"max":40446.542},"bytesWritten":50194913,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":4311312,"millis":10001.007875,"checksum":0,"gcCount":9,"gcMillis":264,"allocated":9911623144,"latency":{"p50":2.666,"p99":93.0,"p99.9":2331.458,"max":169146.167},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10001.007875,"checksum":0,"latency":{"p50":102162.959,"p99":862188.167,"p99.9":898486.75,"max":898486.75},"checked":false,"bytesWritten":193918603,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1800.184667,"checksum":50000,"gcCount":3,"gcMillis":42,"allocated":2826642632,"bytesWritten":126270976,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":64.780459,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":469128,"bytesRead":20078093,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":124.262583,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9946080,"bytesRead":36530604,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1491.12475,"checksum":1,"gcCount":1,"gcMillis":10,"allocated":1528400848},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":6457.439834,"checksum":831528,"gcCount":3,"gcMillis":317,"allocated":711168688,"bytesRead":723010333,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":453242306,"millis":1000.0,"checksum":453242306},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6617.6005,"checksum":20000,"gcCount":6,"gcMillis":87,"allocated":7834882992,"bytesWritten":301605846,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2463.311125,"checksum":20000,"gcCount":3,"gcMillis":35,"allocated":3613826032,"bytesWritten":158495248,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":90.332792,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9886368,"bytesRead":21291900,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":365838705,"millis":1000.0,"checksum":365838705},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9981.265875,"checksum":0,"gcCount":6,"gcMillis":81,"allocated":7849799024,"latency":{"p50":2988.334,"p99":28888.0,"p99.9":52857.209,"max":52857.209},"checked":false,"bytesWritten":457171628,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.1375,"checksum":0,"gcCount":7,"gcMillis":222,"allocated":8883107128,"latency":{"p50":3155.459,"p99":32542.209,"p99.9":72048.5,"max":72048.5},"checked":false,"bytesWritten":532017704,"logicalWritten":16000,"compactionMillis":71,"reclaimed":23471106},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":304780122,"millis":1000.0,"checksum":304780122},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":142.6095,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":79450672,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4193779712,"retainedHeap":124334272,"bytesWritten":3695948322,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1257.588084,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":934785256,"bytesWritten":53215798,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3004.694291,"checksum":362757,"gcCount":3,"gcMillis":61,"allocated":3059859816,"bytesWritten":241380708,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":63.082209,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9800488,"bytesRead":19947593,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":37.549959,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12734755,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":260.927792,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":159768792,"bytesRead":92738505,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":67.223,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417152,"bytesRead":30730687,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":299.02625,"checksum":5852224,"gcCount":1,"gcMillis":4,"allocated":753207912},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":218.819458,"checksum":731528,"gcCount":1,"gcMillis":5,"allocated":714789544,"latency":{"p50":0.916,"p99":13.375,"p99.9":22.584,"max":4543.75}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1491.963041,"checksum":20000,"gcCount":3,"gcMillis":24,"allocated":3460046920,"bytesWritten":155922140,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":369.183792,"checksum":5000,"gcCount":1,"gcMillis":6,"allocated":983344000,"latency":{"p50":45.458,"p99":473.042,"p99.9":834.375,"max":7200.25},"bytesWritten":49674267,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":11995998,"millis":10000.091625,"checksum":0,"gcCount":25,"gcMillis":152,"allocated":30784621496,"latency":{"p50":3.208,"p99":52.208,"p99.9":288.542,"max":16326.25},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.091625,"checksum":0,"latency":{"p50":2901.625,"p99":24367.958,"p99.9":39799.208,"max":39799.208},"checked":false,"bytesWritten":815552894,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":367.469417,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":772118832,"bytesWritten":55944741,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":20.270583,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":11011238,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":115.532834,"checksum":100000,"gcCount":1,"gcMillis":14,"allocated":10011784,"bytesRead":36479141,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":751.518875,"checksum":1,"gcCount":1,"gcMillis":6,"allocated":1848637424},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1123.088042,"checksum":831528,"gcCount":2,"gcMillis":41,"allocated":603836712,"bytesRead":446186487,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":389884525,"millis":1000.0,"checksum":389884525},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2249.2675,"checksum":20000,"gcCount":4,"gcMillis":29,"allocated":5370015880,"bytesWritten":220296444,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1144.393,"checksum":20000,"gcCount":3,"gcMillis":23,"allocated":3084797176,"bytesWritten":144587387,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":39.730042,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9740256,"bytesRead":9325195,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":223894057,"millis":1000.0,"checksum":223894057},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.715209,"checksum":0,"gcCount":3,"gcMillis":99,"allocated":2933573536,"latency":{"p50":3496.709,"p99":7805.417,"p99.9":62458.417,"max":62458.417},"checked":false,"bytesWritten":181049947,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.102958,"checksum":0,"gcCount":2,"gcMillis":92,"allocated":3180852928,"latency":{"p50":3241.083,"p99":11585.833,"p99.9":84258.042,"max":84258.042},"checked":false,"bytesWritten":200722311,"logicalWritten":16000,"compactionMillis":32,"reclaimed":-68910300},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":207769912,"millis":1000.0,"checksum":207769912},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":60.926875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":82949592,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-3.json b/benchmarks/report/results/s1-async/hypergraphdb-3.json index c7ed181..dab158a 100644 --- a/benchmarks/report/results/s1-async/hypergraphdb-3.json +++ b/benchmarks/report/results/s1-async/hypergraphdb-3.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4179099648,"retainedHeap":124163288,"bytesWritten":2768140389,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":4452.646209,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":913590144,"bytesWritten":53301644,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":9047.272292,"checksum":362757,"gcCount":3,"gcMillis":201,"allocated":2883991920,"bytesWritten":231265231,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":450.563209,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10087368,"bytesRead":8452457,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":207.138416,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":15266130,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":673.274667,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":165134592,"bytesRead":76508977,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":537.70375,"checksum":281832,"gcCount":1,"gcMillis":92,"allocated":18418408,"bytesRead":34991976,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":1255.271834,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":318539360},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":935.807,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":382003992,"latency":{"p50":2.041,"p99":114.208,"p99.9":1029.459,"max":16317.334}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":6885.081708,"checksum":20000,"gcCount":4,"gcMillis":155,"allocated":4032032168,"bytesWritten":184567009,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":1096.415416,"checksum":5000,"gcCount":1,"gcMillis":9,"allocated":1256273440,"latency":{"p50":138.084,"p99":1296.792,"p99.9":3386.625,"max":11593.583},"bytesWritten":60537564,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":3171949,"millis":10011.327375,"checksum":0,"gcCount":7,"gcMillis":225,"allocated":6956336736,"latency":{"p50":2.5,"p99":106.0,"p99.9":4107.667,"max":129864.292},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":7960,"millis":10011.327375,"checksum":0,"latency":{"p50":1416594.459,"p99":2368270.75,"p99.9":2375131.292,"max":2375131.292},"checked":false,"bytesWritten":125417600,"logicalWritten":254720},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":3630.943875,"checksum":50000,"gcCount":3,"gcMillis":92,"allocated":3431022744,"bytesWritten":148639128,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":56.835833,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":10563083,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":252.378875,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10282104,"bytesRead":35301899,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":2087.711459,"checksum":1,"gcCount":1,"gcMillis":96,"allocated":579319112},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":5863.692875,"checksum":831528,"gcCount":3,"gcMillis":187,"allocated":686034552,"bytesRead":740226346,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":561174783,"millis":1000.0,"checksum":561174783},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":12782.528791,"checksum":20000,"gcCount":8,"gcMillis":417,"allocated":9797202384,"bytesWritten":359806395,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3523.472125,"checksum":20000,"gcCount":3,"gcMillis":110,"allocated":3886121304,"bytesWritten":166512442,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":85.662125,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9805432,"bytesRead":17076284,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":273864518,"millis":1000.0,"checksum":273864518},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.39125,"checksum":0,"gcCount":7,"gcMillis":62,"allocated":7706728592,"latency":{"p50":4869.75,"p99":12091.5,"p99.9":17663.125,"max":17663.125},"checked":false,"bytesWritten":450856937,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.095667,"checksum":0,"gcCount":6,"gcMillis":75,"allocated":6883786200,"latency":{"p50":5114.292,"p99":10098.959,"p99.9":33702.375,"max":33702.375},"checked":false,"bytesWritten":397002316,"logicalWritten":16000,"compactionMillis":50,"reclaimed":102897546},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":302659677,"millis":1000.0,"checksum":302659677},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":105.132333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83293656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4189061120,"retainedHeap":124149144,"bytesWritten":3738439496,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1524.189917,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":931160336,"bytesWritten":53062668,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2534.5795,"checksum":362757,"gcCount":3,"gcMillis":51,"allocated":3031657008,"bytesWritten":239349960,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":71.457667,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9836392,"bytesRead":19302912,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":61.691,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":19761727,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":317.626042,"checksum":20287,"gcCount":1,"gcMillis":40,"allocated":160143696,"bytesRead":103782131,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":79.107083,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18420112,"bytesRead":29971805,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":356.0815,"checksum":5852224,"gcCount":1,"gcMillis":6,"allocated":790826568},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":181.182458,"checksum":731528,"gcCount":1,"gcMillis":4,"allocated":477481864,"latency":{"p50":0.917,"p99":12.0,"p99.9":19.292,"max":3956.084}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1893.072041,"checksum":20000,"gcCount":3,"gcMillis":16,"allocated":4226775032,"bytesWritten":188286435,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":354.9145,"checksum":5000,"gcCount":1,"gcMillis":8,"allocated":942908112,"latency":{"p50":44.708,"p99":439.084,"p99.9":962.5,"max":9300.917},"bytesWritten":44842326,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10599077,"millis":10000.160625,"checksum":0,"gcCount":23,"gcMillis":161,"allocated":28108262632,"latency":{"p50":3.291,"p99":57.791,"p99.9":364.958,"max":147391.042},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.160625,"checksum":0,"latency":{"p50":3086.25,"p99":139885.791,"p99.9":165602.291,"max":165602.291},"checked":false,"bytesWritten":749882709,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":399.888625,"checksum":50000,"gcCount":1,"gcMillis":6,"allocated":819828224,"bytesWritten":57779265,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":15.618083,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":7750724,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":54.803125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9809688,"bytesRead":21628724,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":975.502959,"checksum":1,"gcCount":2,"gcMillis":8,"allocated":2565388144},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":730.1095,"checksum":831528,"gcCount":2,"gcMillis":30,"allocated":567049136,"bytesRead":322712926,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":326196634,"millis":1000.0,"checksum":326196634},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2530.311334,"checksum":20000,"gcCount":5,"gcMillis":54,"allocated":5384099024,"bytesWritten":224334633,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1012.464375,"checksum":20000,"gcCount":2,"gcMillis":14,"allocated":2800505080,"bytesWritten":133159926,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":55.672084,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9761064,"bytesRead":12490214,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":316780575,"millis":1000.0,"checksum":316780575},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.616625,"checksum":0,"gcCount":5,"gcMillis":29,"allocated":6554709552,"latency":{"p50":3318.417,"p99":8321.375,"p99.9":11598.209,"max":11598.209},"checked":false,"bytesWritten":394050363,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9982.609208,"checksum":0,"gcCount":4,"gcMillis":108,"allocated":4784539304,"latency":{"p50":3344.792,"p99":7604.667,"p99.9":66175.333,"max":66175.333},"checked":false,"bytesWritten":297236761,"logicalWritten":16000,"compactionMillis":31,"reclaimed":2720558},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":278592814,"millis":1000.0,"checksum":278592814},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":79.405,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83649184,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-4.json b/benchmarks/report/results/s1-async/hypergraphdb-4.json index 4fcf8fc..ace1260 100644 --- a/benchmarks/report/results/s1-async/hypergraphdb-4.json +++ b/benchmarks/report/results/s1-async/hypergraphdb-4.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4147118080,"retainedHeap":124149040,"bytesWritten":3502731577,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1559.083333,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":932219816,"bytesWritten":53252654,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4999.62525,"checksum":362757,"gcCount":3,"gcMillis":93,"allocated":2976431072,"bytesWritten":233744915,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":107.781875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9865272,"bytesRead":14778537,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":45.02925,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":2885788,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1081.91125,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":157978656,"bytesRead":73154978,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":229.792791,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418536,"bytesRead":26026248,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":579.332083,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":514102376},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":371.226916,"checksum":731528,"gcCount":1,"gcMillis":7,"allocated":442489296,"latency":{"p50":1.584,"p99":26.542,"p99.9":206.959,"max":7286.917}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3072.328667,"checksum":20000,"gcCount":3,"gcMillis":24,"allocated":3817791152,"bytesWritten":164758291,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":930.579959,"checksum":5000,"gcCount":1,"gcMillis":16,"allocated":701834656,"latency":{"p50":86.708,"p99":1785.166,"p99.9":5015.084,"max":29427.0},"bytesWritten":32951380,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7919073,"millis":10000.202375,"checksum":0,"gcCount":17,"gcMillis":193,"allocated":19778582712,"latency":{"p50":3.334,"p99":73.583,"p99.9":867.833,"max":43150.416},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.202375,"checksum":0,"latency":{"p50":4734.667,"p99":57323.625,"p99.9":77639.542,"max":77639.542},"checked":false,"bytesWritten":434278266,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1402.369917,"checksum":50000,"gcCount":1,"gcMillis":26,"allocated":1296386672,"bytesWritten":73012744,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":32.750791,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":5798223,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":93.1705,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9812512,"bytesRead":27101870,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1111.3515,"checksum":1,"gcCount":1,"gcMillis":7,"allocated":1523540288},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1851.4795,"checksum":831528,"gcCount":2,"gcMillis":41,"allocated":662172784,"bytesRead":549603950,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":429678903,"millis":1000.0,"checksum":429678903},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6858.336708,"checksum":20000,"gcCount":7,"gcMillis":135,"allocated":7965550424,"bytesWritten":314831679,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2222.34975,"checksum":20000,"gcCount":2,"gcMillis":19,"allocated":3442632936,"bytesWritten":150759895,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":127.37475,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9920520,"bytesRead":18432947,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":307510627,"millis":1000.0,"checksum":307510627},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9998.707958,"checksum":0,"gcCount":6,"gcMillis":40,"allocated":8494310944,"latency":{"p50":5174.625,"p99":10503.75,"p99.9":18631.542,"max":18631.542},"checked":false,"bytesWritten":506939419,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.853416,"checksum":0,"gcCount":9,"gcMillis":116,"allocated":10748703144,"latency":{"p50":5184.791,"p99":13317.666,"p99.9":22195.541,"max":22195.541},"checked":false,"bytesWritten":648467584,"logicalWritten":16000,"compactionMillis":53,"reclaimed":-45586996},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":286815553,"millis":1000.0,"checksum":286815553},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":157.172166,"checksum":1,"gcCount":1,"gcMillis":43,"allocated":81903144,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4174381056,"retainedHeap":124079976,"bytesWritten":3320975034,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1327.543458,"checksum":50000,"gcCount":1,"gcMillis":10,"allocated":926300464,"bytesWritten":53183399,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2677.496792,"checksum":362757,"gcCount":2,"gcMillis":48,"allocated":3003227200,"bytesWritten":239054275,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":67.53075,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9763344,"bytesRead":17876481,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":41.583625,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":13222908,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":371.0615,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":160072776,"bytesRead":90876876,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":109.34825,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18415520,"bytesRead":36581514,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":410.84625,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":743003576},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":268.631417,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":401879592,"latency":{"p50":1.375,"p99":16.041,"p99.9":100.458,"max":1062.208}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1656.351208,"checksum":20000,"gcCount":4,"gcMillis":25,"allocated":3778102880,"bytesWritten":166603240,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":691.683042,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":727897176,"latency":{"p50":58.667,"p99":1526.042,"p99.9":3816.125,"max":9509.333},"bytesWritten":34928692,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":11774401,"millis":10007.38075,"checksum":0,"gcCount":26,"gcMillis":162,"allocated":30996626824,"latency":{"p50":3.375,"p99":56.708,"p99.9":305.458,"max":24409.209},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10007.38075,"checksum":0,"latency":{"p50":2887.292,"p99":11868.084,"p99.9":20565.292,"max":20565.292},"checked":false,"bytesWritten":820880919,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":595.760834,"checksum":50000,"gcCount":1,"gcMillis":9,"allocated":1322019136,"bytesWritten":79087904,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":17.810416,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":11176024,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":113.945625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10101576,"bytesRead":47264859,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":674.381791,"checksum":1,"gcCount":1,"gcMillis":4,"allocated":1714719848},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":862.313333,"checksum":831528,"gcCount":2,"gcMillis":26,"allocated":598469032,"bytesRead":390911507,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":378890768,"millis":1000.0,"checksum":378890768},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3375.644541,"checksum":20000,"gcCount":5,"gcMillis":70,"allocated":5458748920,"bytesWritten":224546222,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1237.864417,"checksum":20000,"gcCount":2,"gcMillis":14,"allocated":3086666600,"bytesWritten":145673663,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":41.935292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9736704,"bytesRead":18250194,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":230499453,"millis":1000.0,"checksum":230499453},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.402875,"checksum":0,"gcCount":1,"gcMillis":9,"allocated":1305772272,"latency":{"p50":3339.375,"p99":7502.833,"p99.9":9586.625,"max":9586.625},"checked":false,"bytesWritten":76527072,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.73675,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":652313288,"latency":{"p50":3378.125,"p99":6768.792,"p99.9":8027.125,"max":8027.125},"checked":false,"bytesWritten":25458656,"logicalWritten":16000,"compactionMillis":52,"reclaimed":3784011},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":205643161,"millis":1000.0,"checksum":205643161},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":87.820917,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":80360824,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-async/hypergraphdb-5.json b/benchmarks/report/results/s1-async/hypergraphdb-5.json index 01ac637..9fe84bd 100644 --- a/benchmarks/report/results/s1-async/hypergraphdb-5.json +++ b/benchmarks/report/results/s1-async/hypergraphdb-5.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4159176704,"retainedHeap":124325120,"bytesWritten":3179241547,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3376.1705,"checksum":50000,"gcCount":1,"gcMillis":23,"allocated":923708992,"bytesWritten":53039261,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4444.402709,"checksum":362757,"gcCount":2,"gcMillis":58,"allocated":2947683952,"bytesWritten":233414583,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":141.570916,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9907480,"bytesRead":16176148,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":161.184875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":9523814,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":614.668084,"checksum":20287,"gcCount":1,"gcMillis":80,"allocated":158068928,"bytesRead":90283627,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":130.780416,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18419904,"bytesRead":21007066,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":472.835958,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":632197392},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":382.333333,"checksum":731528,"gcCount":1,"gcMillis":8,"allocated":485734800,"latency":{"p50":1.666,"p99":26.291,"p99.9":181.667,"max":8425.291}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":5370.347625,"checksum":20000,"gcCount":4,"gcMillis":96,"allocated":4435258208,"bytesWritten":193403929,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":730.458208,"checksum":5000,"gcCount":1,"gcMillis":11,"allocated":986632336,"latency":{"p50":96.083,"p99":874.583,"p99.9":3184.042,"max":12076.459},"bytesWritten":46471493,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7619950,"millis":10000.488375,"checksum":0,"gcCount":16,"gcMillis":212,"allocated":18115100160,"latency":{"p50":2.875,"p99":63.666,"p99.9":1029.417,"max":57008.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.488375,"checksum":0,"latency":{"p50":4848.75,"p99":61049.792,"p99.9":110425.709,"max":110425.709},"checked":false,"bytesWritten":391536008,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":924.582209,"checksum":50000,"gcCount":1,"gcMillis":14,"allocated":1229361456,"bytesWritten":69219988,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":27.414125,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6862866,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":396.900458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10220208,"bytesRead":54541760,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":939.744292,"checksum":1,"gcCount":1,"gcMillis":20,"allocated":1038603648},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2386.74825,"checksum":831528,"gcCount":2,"gcMillis":47,"allocated":668935176,"bytesRead":559065944,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":464298411,"millis":1000.0,"checksum":464298411},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":6697.696708,"checksum":20000,"gcCount":6,"gcMillis":83,"allocated":7738269704,"bytesWritten":299135623,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2466.233792,"checksum":20000,"gcCount":3,"gcMillis":48,"allocated":3391427968,"bytesWritten":149771955,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":92.983542,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9750344,"bytesRead":26409149,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":353599977,"millis":1000.0,"checksum":353599977},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.196958,"checksum":0,"gcCount":8,"gcMillis":154,"allocated":9980967552,"latency":{"p50":5330.75,"p99":38144.583,"p99.9":110508.708,"max":110508.708},"checked":false,"bytesWritten":588484574,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.304,"checksum":0,"gcCount":4,"gcMillis":189,"allocated":4751092064,"latency":{"p50":5217.833,"p99":56751.916,"p99.9":135370.0,"max":135370.0},"checked":false,"bytesWritten":286600784,"logicalWritten":16000,"compactionMillis":29,"reclaimed":13352971},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":270310331,"millis":1000.0,"checksum":270310331},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":131.530083,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":77147216,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"async","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4119855104,"retainedHeap":124335776,"bytesWritten":4191296353,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1401.548958,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":937763024,"bytesWritten":52872178,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2921.814042,"checksum":362757,"gcCount":3,"gcMillis":57,"allocated":2980397016,"bytesWritten":236792852,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":67.1495,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9751112,"bytesRead":18084256,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":28.685625,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":5633180,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":8208.856541,"checksum":20287,"gcCount":14,"gcMillis":131,"allocated":159964584,"bytesRead":3242874253,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":101.756166,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18415792,"bytesRead":30757673,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":366.530291,"checksum":5852224,"gcCount":1,"gcMillis":6,"allocated":543401680},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":179.108334,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":473265472,"latency":{"p50":0.917,"p99":12.667,"p99.9":21.583,"max":143.458}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1540.132917,"checksum":20000,"gcCount":3,"gcMillis":29,"allocated":3112128128,"bytesWritten":153581110,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":336.733542,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":668142408,"latency":{"p50":46.417,"p99":465.5,"p99.9":936.083,"max":5275.25},"bytesWritten":35315761,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":11280912,"millis":10000.212834,"checksum":0,"gcCount":22,"gcMillis":151,"allocated":27869333104,"latency":{"p50":3.666,"p99":51.083,"p99.9":310.333,"max":33213.958},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.212834,"checksum":0,"latency":{"p50":2952.792,"p99":11922.334,"p99.9":19312.75,"max":19312.75},"checked":false,"bytesWritten":760874124,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":429.959208,"checksum":50000,"gcCount":1,"gcMillis":7,"allocated":807088064,"bytesWritten":60645518,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":33.881125,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":10785597,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":51.5335,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9763680,"bytesRead":23212358,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":924.11575,"checksum":1,"gcCount":1,"gcMillis":11,"allocated":1271949984},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1042.807083,"checksum":831528,"gcCount":1,"gcMillis":45,"allocated":556650944,"bytesRead":243621633,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":303626709,"millis":1000.0,"checksum":303626709},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2588.547083,"checksum":20000,"gcCount":5,"gcMillis":38,"allocated":5056119480,"bytesWritten":216146927,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1091.459333,"checksum":20000,"gcCount":2,"gcMillis":14,"allocated":2542923048,"bytesWritten":119755589,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":47.509708,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9747744,"bytesRead":21590479,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":213056956,"millis":1000.0,"checksum":213056956},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.127584,"checksum":0,"gcCount":1,"gcMillis":9,"allocated":1475363824,"latency":{"p50":3436.375,"p99":6463.709,"p99.9":10520.5,"max":10520.5},"checked":false,"bytesWritten":87602328,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.490458,"checksum":0,"gcCount":2,"gcMillis":143,"allocated":1575255920,"latency":{"p50":3129.833,"p99":24152.25,"p99.9":102953.416,"max":102953.416},"checked":false,"bytesWritten":96471171,"logicalWritten":16000,"compactionMillis":82,"reclaimed":3498510},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":197227454,"millis":1000.0,"checksum":197227454},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":75.60775,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":80324712,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-1.json b/benchmarks/report/results/s1-sync/hstore-1.json index e62ca36..ed71a08 100644 --- a/benchmarks/report/results/s1-sync/hstore-1.json +++ b/benchmarks/report/results/s1-sync/hstore-1.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4097835008,"retainedHeap":188799144,"bytesWritten":113076016,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1685.761791,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":969623504,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4335.848959,"checksum":362757,"gcCount":3,"gcMillis":82,"allocated":3686535512,"bytesWritten":37424796,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":22.879458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":120784,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":37.364792,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029384,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":55.591167,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612800,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":70.229792,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729832,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":88.816375,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1527168},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":49.804917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29313944,"latency":{"p50":0.458,"p99":0.792,"p99.9":1.125,"max":39.458}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":468.6185,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":432704208,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":20969.67125,"checksum":5000,"gcCount":1,"gcMillis":10,"allocated":659274392,"latency":{"p50":4006.209,"p99":7827.625,"p99.9":16855.916,"max":43443.5},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15860952,"millis":10005.659458,"checksum":0,"gcCount":2,"gcMillis":76,"allocated":1563769760,"latency":{"p50":1.791,"p99":17.416,"p99.9":131.125,"max":57834.791},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10005.659458,"checksum":0,"latency":{"p50":6282.958,"p99":93358.667,"p99.9":114175.042,"max":114175.042},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":499.3435,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":103008216,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":11.153209,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185784,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":11.064625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15856,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":165.413167,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26964864},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":52.109834,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26578264,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95090633,"millis":1000.0,"checksum":95090633},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2595.688292,"checksum":20000,"gcCount":2,"gcMillis":105,"allocated":1160337208,"bytesWritten":23317289,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":920.718417,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":608965448,"bytesWritten":13369260,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":24.888292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":125688,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":133548190,"millis":1000.0,"checksum":133548190},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.509792,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84783952,"latency":{"p50":6793.459,"p99":167889.417,"p99.9":194173.542,"max":194173.542},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.989375,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80346408,"latency":{"p50":7400.084,"p99":45918.792,"p99.9":87426.75,"max":87426.75},"checked":false,"bytesWritten":10319874,"logicalWritten":16000,"compactionMillis":274,"reclaimed":40160026},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95215939,"millis":1000.0,"checksum":95215939},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":1646.139583,"checksum":1,"gcCount":1,"gcMillis":96,"allocated":123989536,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4105699328,"retainedHeap":183681648,"bytesWritten":104290386,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":889.4455,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":966171496,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2529.399291,"checksum":362757,"gcCount":3,"gcMillis":57,"allocated":3722827576,"bytesWritten":39661175,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":16.125083,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134432,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":18.439666,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":16072,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":26.657083,"checksum":20287,"gcCount":1,"gcMillis":0,"allocated":81183808,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":17.573042,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3331504,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":19.511792,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1555480},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":21.729292,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29301200,"latency":{"p50":0.208,"p99":0.292,"p99.9":0.417,"max":38.75}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":257.97575,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":466323584,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19393.206042,"checksum":5000,"gcCount":1,"gcMillis":6,"allocated":659979368,"latency":{"p50":3979.875,"p99":5214.291,"p99.9":7932.625,"max":10432.417},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":23468102,"millis":10000.1035,"checksum":0,"gcCount":2,"gcMillis":29,"allocated":2419718320,"latency":{"p50":1.875,"p99":11.375,"p99.9":27.833,"max":35320.208},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.1035,"checksum":0,"latency":{"p50":4952.5,"p99":17012.834,"p99.9":32684.25,"max":32684.25},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":71.297167,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":113149120,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":4.988833,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176616,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.800041,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":6416096,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":127.634209,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":27487624},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":24.209167,"checksum":831528,"gcCount":1,"gcMillis":3,"allocated":28057328,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97789208,"millis":1000.0,"checksum":97789208},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":742.727916,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":1052400216,"bytesWritten":14814165,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":488.701208,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":526517320,"bytesWritten":9925897,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":11.515333,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139552,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":124211718,"millis":1000.0,"checksum":124211718},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.638416,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105721248,"latency":{"p50":6507.833,"p99":8583.958,"p99.9":10229.75,"max":10229.75},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.33575,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":102094672,"latency":{"p50":6380.667,"p99":8510.167,"p99.9":10334.458,"max":10334.458},"checked":false,"bytesWritten":10764711,"logicalWritten":16000,"compactionMillis":176,"reclaimed":30409306},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95659124,"millis":1000.0,"checksum":95659124},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":111.02925,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":127410360,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-2.json b/benchmarks/report/results/s1-sync/hstore-2.json index db42979..0819267 100644 --- a/benchmarks/report/results/s1-sync/hstore-2.json +++ b/benchmarks/report/results/s1-sync/hstore-2.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094689280,"retainedHeap":187697944,"bytesWritten":114140476,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":2252.37275,"checksum":50000,"gcCount":1,"gcMillis":32,"allocated":984738248,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":19527.900584,"checksum":362757,"gcCount":3,"gcMillis":430,"allocated":3676133056,"bytesWritten":39197247,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":138.342958,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":147888,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":72.694791,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4032192,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":273.494917,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612584,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":89.15475,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729880,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":206.976834,"checksum":5852224,"gcCount":1,"gcMillis":4,"allocated":31203928},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":166.1565,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26918048,"latency":{"p50":1.125,"p99":3.709,"p99.9":108.208,"max":1451.166}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3317.954417,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":420396288,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":20141.207292,"checksum":5000,"gcCount":1,"gcMillis":34,"allocated":610679208,"latency":{"p50":3993.958,"p99":6699.5,"p99.9":10366.125,"max":34350.875},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":12485054,"millis":10000.095875,"checksum":0,"gcCount":2,"gcMillis":131,"allocated":1934722448,"latency":{"p50":1.667,"p99":20.708,"p99.9":374.625,"max":110962.459},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.095875,"checksum":0,"latency":{"p50":38808.084,"p99":692672.584,"p99.9":725905.042,"max":725905.042},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":144.143584,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":106482096,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":13.248583,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":176208,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":12.968417,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":14420728,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":166.417375,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26082544},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":66.753625,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26599824,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96875176,"millis":1000.0,"checksum":96875176},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1350.391458,"checksum":20000,"gcCount":1,"gcMillis":27,"allocated":1013180728,"bytesWritten":18851261,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2617.760875,"checksum":20000,"gcCount":1,"gcMillis":42,"allocated":613834000,"bytesWritten":17154051,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":22.030875,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139264,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":134641844,"millis":1000.0,"checksum":134641844},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.830959,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84555304,"latency":{"p50":6594.0,"p99":9681.792,"p99.9":12790.042,"max":12790.042},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.826625,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81026408,"latency":{"p50":6654.75,"p99":10008.333,"p99.9":12608.5,"max":12608.5},"checked":false,"bytesWritten":10293120,"logicalWritten":16000,"compactionMillis":173,"reclaimed":41281178},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95188450,"millis":1000.0,"checksum":95188450},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":155.749208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":119037768,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4117233664,"retainedHeap":181479520,"bytesWritten":104785481,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":821.754375,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":965311224,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2300.713417,"checksum":362757,"gcCount":3,"gcMillis":54,"allocated":3639410776,"bytesWritten":40072321,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":11.784875,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":134480,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":16.551416,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029072,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":32.0815,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":74898616,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":34.925125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729904,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":29.828459,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1739360},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":27.837375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":15705328,"latency":{"p50":0.25,"p99":0.542,"p99.9":2.875,"max":36.833}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":312.07075,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":468644872,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19219.532833,"checksum":5000,"gcCount":1,"gcMillis":22,"allocated":664234728,"latency":{"p50":3979.25,"p99":5094.667,"p99.9":7983.083,"max":26041.459},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":22911226,"millis":10000.086375,"checksum":0,"gcCount":2,"gcMillis":42,"allocated":2519979712,"latency":{"p50":1.959,"p99":11.875,"p99.9":26.333,"max":38666.25},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.086375,"checksum":0,"latency":{"p50":5085.416,"p99":17664.041,"p99.9":47127.208,"max":47127.208},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":74.842292,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":111636808,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":5.185667,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186048,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":8.9815,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":14415952,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":119.047125,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":27485520},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":23.913333,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28057544,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":98204042,"millis":1000.0,"checksum":98204042},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":711.875167,"checksum":20000,"gcCount":1,"gcMillis":11,"allocated":1072380280,"bytesWritten":14911436,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":516.121875,"checksum":20000,"gcCount":1,"gcMillis":36,"allocated":625677160,"bytesWritten":9910298,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":15.202958,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139576,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":124709349,"millis":1000.0,"checksum":124709349},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.79625,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":103597168,"latency":{"p50":5956.166,"p99":8508.333,"p99.9":9889.25,"max":9889.25},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.694542,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":100400656,"latency":{"p50":6670.0,"p99":9015.375,"p99.9":13144.458,"max":13144.458},"checked":false,"bytesWritten":10766988,"logicalWritten":16000,"compactionMillis":162,"reclaimed":30904282},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95661812,"millis":1000.0,"checksum":95661812},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":183.751458,"checksum":1,"gcCount":1,"gcMillis":61,"allocated":129273320,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-3.json b/benchmarks/report/results/s1-sync/hstore-3.json index be33e29..b8a57ac 100644 --- a/benchmarks/report/results/s1-sync/hstore-3.json +++ b/benchmarks/report/results/s1-sync/hstore-3.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4084727808,"retainedHeap":202348264,"bytesWritten":109128526,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1609.993625,"checksum":50000,"gcCount":1,"gcMillis":11,"allocated":983709184,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3968.36825,"checksum":362757,"gcCount":3,"gcMillis":80,"allocated":3704112728,"bytesWritten":38007036,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":35.642584,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":4129872,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":36.918,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029600,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":84.113791,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612632,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":41.771167,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729928,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":46.969917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":33374256},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":55.337292,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26912288,"latency":{"p50":0.459,"p99":1.125,"p99.9":2.958,"max":46.625}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":590.236542,"checksum":20000,"gcCount":1,"gcMillis":21,"allocated":423237552,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19990.880083,"checksum":5000,"gcCount":1,"gcMillis":15,"allocated":657944792,"latency":{"p50":3993.208,"p99":6025.959,"p99.9":8836.375,"max":23176.791},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15831963,"millis":10000.138292,"checksum":0,"gcCount":1,"gcMillis":17,"allocated":1702337872,"latency":{"p50":2.167,"p99":18.75,"p99.9":83.5,"max":41337.5},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.138292,"checksum":0,"latency":{"p50":5243.833,"p99":185143.083,"p99.9":200318.792,"max":200318.792},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":188.796292,"checksum":50000,"gcCount":1,"gcMillis":38,"allocated":102554336,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":7.895542,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186408,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":10.420875,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15712,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":155.088084,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26946696},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":52.075542,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26605176,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95678706,"millis":1000.0,"checksum":95678706},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1361.068958,"checksum":20000,"gcCount":1,"gcMillis":39,"allocated":1108232176,"bytesWritten":18827829,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1188.353792,"checksum":20000,"gcCount":1,"gcMillis":58,"allocated":617488944,"bytesWritten":13354916,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":25.599958,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139480,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":129598080,"millis":1000.0,"checksum":129598080},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.319292,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85245032,"latency":{"p50":6452.292,"p99":8543.583,"p99.9":12619.833,"max":12619.833},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.649959,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81309672,"latency":{"p50":6615.584,"p99":9630.917,"p99.9":11795.459,"max":11795.459},"checked":false,"bytesWritten":10293948,"logicalWritten":16000,"compactionMillis":174,"reclaimed":36236698},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95189157,"millis":1000.0,"checksum":95189157},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":211.654875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":121309320,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4040687616,"retainedHeap":199619840,"bytesWritten":102911942,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":895.566667,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":967973288,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":2698.263208,"checksum":362757,"gcCount":3,"gcMillis":59,"allocated":3654286840,"bytesWritten":38190214,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":45.830167,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":132008,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":14.470041,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":16720,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":27.452333,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81183952,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":17.382708,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729880,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":95.880041,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":33411752},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":23.240917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":22903576,"latency":{"p50":0.208,"p99":0.333,"p99.9":0.417,"max":28.792}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":290.766625,"checksum":20000,"gcCount":1,"gcMillis":7,"allocated":469508904,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19785.433125,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":665137424,"latency":{"p50":3990.917,"p99":5861.875,"p99.9":8162.667,"max":11599.625},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":22833293,"millis":10000.075333,"checksum":0,"gcCount":3,"gcMillis":25,"allocated":2295545240,"latency":{"p50":1.875,"p99":11.125,"p99.9":25.583,"max":43435.792},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.075333,"checksum":0,"latency":{"p50":4735.041,"p99":19110.375,"p99.9":29531.208,"max":29531.208},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":81.749875,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":119577040,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":6.208583,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186192,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.928084,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":6415520,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":121.544542,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26930776},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":46.113,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28051216,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96309433,"millis":1000.0,"checksum":96309433},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":736.499292,"checksum":20000,"gcCount":1,"gcMillis":35,"allocated":1125721328,"bytesWritten":14911436,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":542.980916,"checksum":20000,"gcCount":1,"gcMillis":36,"allocated":621681560,"bytesWritten":9927139,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":11.077375,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":1733200,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":122830790,"millis":1000.0,"checksum":122830790},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.420833,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105403592,"latency":{"p50":6325.125,"p99":8381.417,"p99.9":11080.5,"max":11080.5},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9988.448042,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":101904696,"latency":{"p50":6391.125,"p99":8575.542,"p99.9":10344.25,"max":10344.25},"checked":false,"bytesWritten":10758715,"logicalWritten":16000,"compactionMillis":151,"reclaimed":29034458},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95653077,"millis":1000.0,"checksum":95653077},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":112.538792,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":125699008,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-4.json b/benchmarks/report/results/s1-sync/hstore-4.json index b0156b0..756b7a3 100644 --- a/benchmarks/report/results/s1-sync/hstore-4.json +++ b/benchmarks/report/results/s1-sync/hstore-4.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4097310720,"retainedHeap":190186912,"bytesWritten":107090393,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1816.809042,"checksum":50000,"gcCount":1,"gcMillis":14,"allocated":977119888,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5935.150875,"checksum":362757,"gcCount":3,"gcMillis":312,"allocated":3734784552,"bytesWritten":39553833,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":27.3045,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":145416,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":39.163875,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029648,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":74.361958,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":80612608,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":48.767625,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":101.404292,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":31945288},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":55.866166,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":26902160,"latency":{"p50":0.458,"p99":1.208,"p99.9":3.333,"max":49.541}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":496.422708,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":434984184,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19778.965792,"checksum":5000,"gcCount":1,"gcMillis":14,"allocated":661424720,"latency":{"p50":3987.083,"p99":5964.417,"p99.9":10669.125,"max":15880.209},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":16339501,"millis":10000.196417,"checksum":0,"gcCount":2,"gcMillis":66,"allocated":2072087328,"latency":{"p50":2.125,"p99":19.458,"p99.9":58.292,"max":65105.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.196417,"checksum":0,"latency":{"p50":5465.084,"p99":53406.459,"p99.9":79125.459,"max":79125.459},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":196.462958,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":105237944,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":9.582667,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":198584,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":10.386208,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16048,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":145.675042,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26932224},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":47.678,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":26599968,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":97235752,"millis":1000.0,"checksum":97235752},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1899.511459,"checksum":20000,"gcCount":1,"gcMillis":63,"allocated":1056943632,"bytesWritten":19001205,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":889.410417,"checksum":20000,"gcCount":1,"gcMillis":32,"allocated":592110472,"bytesWritten":9593712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":21.133167,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":139528,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":127546486,"millis":1000.0,"checksum":127546486},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.773917,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":85287784,"latency":{"p50":6009.292,"p99":9048.084,"p99.9":27899.209,"max":27899.209},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9988.194209,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":81664856,"latency":{"p50":6084.917,"p99":9233.542,"p99.9":11269.167,"max":11269.167},"checked":false,"bytesWritten":10296846,"logicalWritten":16000,"compactionMillis":178,"reclaimed":34181786},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95192484,"millis":1000.0,"checksum":95192484},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":156.7,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":120390992,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4098359296,"retainedHeap":200650792,"bytesWritten":103453603,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1952.196916,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":983271008,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3832.685709,"checksum":362757,"gcCount":3,"gcMillis":99,"allocated":3728308888,"bytesWritten":38465386,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":14.462667,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":146688,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":38.452625,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029672,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":35.879916,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":81183832,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":28.645792,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729904,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":23.602708,"checksum":5852224,"gcCount":1,"gcMillis":1,"allocated":1521880},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":52.980833,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":34898568,"latency":{"p50":0.5,"p99":0.792,"p99.9":3.584,"max":46.375}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":373.525667,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":473216760,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19355.302791,"checksum":5000,"gcCount":1,"gcMillis":16,"allocated":656892424,"latency":{"p50":3978.958,"p99":5751.125,"p99.9":7948.167,"max":20066.959},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":20675600,"millis":10000.182375,"checksum":0,"gcCount":2,"gcMillis":33,"allocated":2186305688,"latency":{"p50":1.708,"p99":13.458,"p99.9":59.917,"max":37079.583},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.182375,"checksum":0,"latency":{"p50":5179.5,"p99":88267.792,"p99.9":119202.584,"max":119202.584},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":115.390917,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":116816728,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":4.74625,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":185808,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":8.430791,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15976,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":144.253875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26924888},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":34.138833,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28739064,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":96587514,"millis":1000.0,"checksum":96587514},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1151.622333,"checksum":20000,"gcCount":1,"gcMillis":37,"allocated":1145573896,"bytesWritten":15159583,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":844.053791,"checksum":20000,"gcCount":1,"gcMillis":58,"allocated":636999664,"bytesWritten":9940313,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":11.846208,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":133368,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":123372456,"millis":1000.0,"checksum":123372456},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.84525,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105436776,"latency":{"p50":6084.416,"p99":8932.333,"p99.9":11384.583,"max":11384.583},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.767,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":102045536,"latency":{"p50":6595.084,"p99":12540.042,"p99.9":17732.334,"max":17732.334},"checked":false,"bytesWritten":10763883,"logicalWritten":16000,"compactionMillis":197,"reclaimed":29571034},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95658134,"millis":1000.0,"checksum":95658134},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":107.126458,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":126674232,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hstore-5.json b/benchmarks/report/results/s1-sync/hstore-5.json index 89363f2..09aaa0a 100644 --- a/benchmarks/report/results/s1-sync/hstore-5.json +++ b/benchmarks/report/results/s1-sync/hstore-5.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4094689280,"retainedHeap":194565360,"bytesWritten":109086299,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1589.057958,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":987956336,"bytesWritten":12404341,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5029.174416,"checksum":362757,"gcCount":3,"gcMillis":136,"allocated":3653788048,"bytesWritten":37391021,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":22.715542,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":118384,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":77.907333,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029648,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":59.107625,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":84040912,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":57.344875,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3729856,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":103.247167,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":30516600},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":44.033917,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":29314552,"latency":{"p50":0.375,"p99":0.75,"p99.9":1.291,"max":64.291}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":393.656709,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":424344504,"bytesWritten":4420453,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":19570.959,"checksum":5000,"gcCount":1,"gcMillis":17,"allocated":659141824,"latency":{"p50":3987.709,"p99":5314.375,"p99.9":8022.083,"max":20905.166},"bytesWritten":3243937,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":15064843,"millis":10000.116875,"checksum":0,"gcCount":2,"gcMillis":156,"allocated":2007090792,"latency":{"p50":1.875,"p99":18.375,"p99.9":154.583,"max":85901.917},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.116875,"checksum":0,"latency":{"p50":6782.916,"p99":352760.416,"p99.9":380877.208,"max":380877.208},"checked":false,"bytesWritten":3495619,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":143.381375,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":104145136,"bytesWritten":3319691,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":14.683958,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186336,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":9.676459,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16480,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":164.833667,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":26949144},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":48.270959,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":30582896,"bytesRead":2650766,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":95056585,"millis":1000.0,"checksum":95056585},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2015.0605,"checksum":20000,"gcCount":1,"gcMillis":77,"allocated":1127074040,"bytesWritten":23159923,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":752.009333,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":550679384,"bytesWritten":9593712,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":23.931292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":4131520,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":129555766,"millis":1000.0,"checksum":129555766},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.91275,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":84446208,"latency":{"p50":6688.792,"p99":8058.667,"p99.9":9903.458,"max":9903.458},"checked":false,"bytesWritten":1760064,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.957084,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":80637592,"latency":{"p50":6762.459,"p99":21285.042,"p99.9":41092.709,"max":41092.709},"checked":false,"bytesWritten":10296846,"logicalWritten":16000,"compactionMillis":188,"reclaimed":36191066},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95192484,"millis":1000.0,"checksum":95192484},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":190.936667,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":118746368,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4084203520,"retainedHeap":199777848,"bytesWritten":113722342,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1327.796959,"checksum":50000,"gcCount":1,"gcMillis":12,"allocated":994631752,"bytesWritten":12608282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5923.996209,"checksum":362757,"gcCount":3,"gcMillis":122,"allocated":3661655760,"bytesWritten":40034088,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":12.613958,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":131672,"bytesRead":0,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":42.62075,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4029792,"bytesRead":0,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":88.511083,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":75835672,"bytesRead":0,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":47.112333,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":3331504,"bytesRead":0,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":56.045083,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":1378536},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":61.910083,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10887184,"latency":{"p50":0.5,"p99":1.292,"p99.9":12.583,"max":143.084}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":470.962542,"checksum":20000,"gcCount":1,"gcMillis":11,"allocated":456226896,"bytesWritten":4471900,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":20460.727959,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":663921464,"latency":{"p50":4004.958,"p99":6844.875,"p99.9":10235.208,"max":12704.709},"bytesWritten":3295384,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":19363448,"millis":10000.208666,"checksum":0,"gcCount":3,"gcMillis":70,"allocated":2269785552,"latency":{"p50":1.708,"p99":14.125,"p99.9":98.167,"max":51888.375},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.208666,"checksum":0,"latency":{"p50":5572.0,"p99":41438.458,"p99.9":49934.375,"max":49934.375},"checked":false,"bytesWritten":3547066,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":151.191959,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":117723624,"bytesWritten":3411588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":18.762792,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":186408,"bytesRead":0,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":11.163084,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15688,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":147.849042,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":25519592},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":44.666125,"checksum":831528,"gcCount":0,"gcMillis":0,"allocated":28618688,"bytesRead":2737618,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":98165599,"millis":1000.0,"checksum":98165599},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":1992.046416,"checksum":20000,"gcCount":1,"gcMillis":78,"allocated":1202385264,"bytesWritten":23866858,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":944.214041,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":591887680,"bytesWritten":9940313,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":14.089458,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":1733152,"bytesRead":0,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":133720132,"millis":1000.0,"checksum":133720132},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.304541,"checksum":0,"gcCount":1,"gcMillis":122,"allocated":104294040,"latency":{"p50":6830.125,"p99":51483.208,"p99.9":115646.833,"max":115646.833},"checked":false,"bytesWritten":1789526,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.086959,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":101066088,"latency":{"p50":6337.334,"p99":10259.709,"p99.9":11115.542,"max":11115.542},"checked":false,"bytesWritten":10756645,"logicalWritten":16000,"compactionMillis":159,"reclaimed":39925914},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":95650930,"millis":1000.0,"checksum":95650930},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":117.823042,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":131745504,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-1.json b/benchmarks/report/results/s1-sync/hypergraphdb-1.json index 8e73ce7..87a9bdc 100644 --- a/benchmarks/report/results/s1-sync/hypergraphdb-1.json +++ b/benchmarks/report/results/s1-sync/hypergraphdb-1.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4178051072,"retainedHeap":124149304,"bytesWritten":3721992323,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":4127.234459,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":912910584,"bytesWritten":53091245,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":8821.297625,"checksum":362757,"gcCount":3,"gcMillis":99,"allocated":2847746912,"bytesWritten":227888947,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":493.808041,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10684864,"bytesRead":17398199,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":63.76525,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":10669897,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":2158.935041,"checksum":20287,"gcCount":1,"gcMillis":120,"allocated":167434432,"bytesRead":132535742,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":160.400417,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417104,"bytesRead":21139619,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":792.564834,"checksum":5852224,"gcCount":1,"gcMillis":23,"allocated":436286784},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":399.47825,"checksum":731528,"gcCount":1,"gcMillis":9,"allocated":412433664,"latency":{"p50":1.708,"p99":30.167,"p99.9":190.958,"max":8866.875}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":6225.191584,"checksum":20000,"gcCount":5,"gcMillis":80,"allocated":5860493288,"bytesWritten":244623265,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":24668.409041,"checksum":5000,"gcCount":19,"gcMillis":261,"allocated":21760953008,"latency":{"p50":4074.417,"p99":13843.208,"p99.9":43537.958,"max":108990.292},"bytesWritten":1075985379,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":6679351,"millis":10006.044958,"checksum":0,"gcCount":11,"gcMillis":174,"allocated":14606706320,"latency":{"p50":3.292,"p99":74.291,"p99.9":1278.958,"max":86781.083},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9980,"millis":10006.044958,"checksum":0,"latency":{"p50":66256.083,"p99":391806.833,"p99.9":403035.458,"max":403035.458},"checked":false,"bytesWritten":336464382,"logicalWritten":319360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":2686.526833,"checksum":50000,"gcCount":1,"gcMillis":45,"allocated":1393864360,"bytesWritten":82315214,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":40.345375,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":7456822,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":115.290542,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9787736,"bytesRead":34558049,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1407.67525,"checksum":1,"gcCount":1,"gcMillis":7,"allocated":1734765344},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":4518.622666,"checksum":831528,"gcCount":2,"gcMillis":111,"allocated":657544944,"bytesRead":435367118,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":357685282,"millis":1000.0,"checksum":357685282},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":5935.366917,"checksum":20000,"gcCount":5,"gcMillis":99,"allocated":5858058248,"bytesWritten":240183861,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3129.150792,"checksum":20000,"gcCount":3,"gcMillis":47,"allocated":3373053120,"bytesWritten":155539450,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":417.5785,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":10183880,"bytesRead":24452678,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":284485960,"millis":1000.0,"checksum":284485960},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.061916,"checksum":0,"gcCount":5,"gcMillis":181,"allocated":6475557040,"latency":{"p50":7037.041,"p99":32009.708,"p99.9":91170.875,"max":91170.875},"checked":false,"bytesWritten":388110834,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":10042.167958,"checksum":0,"gcCount":2,"gcMillis":257,"allocated":2516714280,"latency":{"p50":10012.0,"p99":156459.792,"p99.9":212641.333,"max":212641.333},"checked":false,"bytesWritten":136620760,"logicalWritten":16000,"compactionMillis":161,"reclaimed":54590518},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":261867149,"millis":1000.0,"checksum":261867149},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":139.37975,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83845352,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4173332480,"retainedHeap":124083808,"bytesWritten":5843023254,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1611.107917,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":931767352,"bytesWritten":53000138,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3287.637917,"checksum":362757,"gcCount":3,"gcMillis":61,"allocated":3021658144,"bytesWritten":239535989,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":118.032458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9856512,"bytesRead":25597167,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":39.525625,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":15732184,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":440.651666,"checksum":20287,"gcCount":1,"gcMillis":75,"allocated":160300016,"bytesRead":93683991,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":84.387084,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18416128,"bytesRead":39514746,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":392.140458,"checksum":5852224,"gcCount":1,"gcMillis":9,"allocated":834795128},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":143.873375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":480944144,"latency":{"p50":0.75,"p99":12.208,"p99.9":20.667,"max":47.916}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1895.590708,"checksum":20000,"gcCount":4,"gcMillis":23,"allocated":4166934296,"bytesWritten":184624117,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":21069.598625,"checksum":5000,"gcCount":39,"gcMillis":164,"allocated":48429967296,"latency":{"p50":4003.167,"p99":8875.459,"p99.9":12157.084,"max":17925.792},"bytesWritten":2805376671,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10483879,"millis":10000.161834,"checksum":0,"gcCount":20,"gcMillis":131,"allocated":25854422160,"latency":{"p50":3.791,"p99":65.125,"p99.9":308.208,"max":25439.5},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.161834,"checksum":0,"latency":{"p50":5977.459,"p99":38597.959,"p99.9":46267.334,"max":46267.334},"checked":false,"bytesWritten":783444513,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":459.684584,"checksum":50000,"gcCount":1,"gcMillis":6,"allocated":721318128,"bytesWritten":58334709,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":21.700459,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":10403146,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":79.004667,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9793680,"bytesRead":24838390,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":858.051083,"checksum":1,"gcCount":1,"gcMillis":5,"allocated":1777046168},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":701.3615,"checksum":831528,"gcCount":1,"gcMillis":14,"allocated":522982280,"bytesRead":242907734,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":320410243,"millis":1000.0,"checksum":320410243},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2689.505,"checksum":20000,"gcCount":4,"gcMillis":30,"allocated":4769745968,"bytesWritten":208673642,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1406.442167,"checksum":20000,"gcCount":2,"gcMillis":15,"allocated":2813421432,"bytesWritten":136785669,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":86.205708,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9782496,"bytesRead":18268909,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":209038719,"millis":1000.0,"checksum":209038719},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.308083,"checksum":0,"gcCount":2,"gcMillis":34,"allocated":1484053552,"latency":{"p50":6196.75,"p99":9871.792,"p99.9":21853.958,"max":21853.958},"checked":false,"bytesWritten":88973909,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9985.339917,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":39293224,"latency":{"p50":6410.333,"p99":8564.417,"p99.9":10871.125,"max":10871.125},"checked":false,"bytesWritten":1018724,"logicalWritten":16000,"compactionMillis":10,"reclaimed":-1047258},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":199131475,"millis":1000.0,"checksum":199131475},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":77.740208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":85397392,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-2.json b/benchmarks/report/results/s1-sync/hypergraphdb-2.json index 4e2ff93..3913475 100644 --- a/benchmarks/report/results/s1-sync/hypergraphdb-2.json +++ b/benchmarks/report/results/s1-sync/hypergraphdb-2.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4179099648,"retainedHeap":123900568,"bytesWritten":3361664429,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":5399.477,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":920040832,"bytesWritten":53206111,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5920.123667,"checksum":362757,"gcCount":3,"gcMillis":137,"allocated":2845827968,"bytesWritten":226333084,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":308.664375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10130000,"bytesRead":15773386,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":162.7305,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12556954,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":2834.346458,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":166875176,"bytesRead":82741054,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":677.819,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18417040,"bytesRead":38074806,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":1276.558584,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":364782576},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":586.876958,"checksum":731528,"gcCount":1,"gcMillis":39,"allocated":387117352,"latency":{"p50":2.209,"p99":35.125,"p99.9":242.417,"max":39118.833}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":4465.588166,"checksum":20000,"gcCount":4,"gcMillis":38,"allocated":4839636456,"bytesWritten":214314234,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":31266.329791,"checksum":5000,"gcCount":17,"gcMillis":357,"allocated":19087441664,"latency":{"p50":4785.083,"p99":30396.166,"p99.9":75227.5,"max":100950.959},"bytesWritten":892492454,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5516860,"millis":10005.178041,"checksum":0,"gcCount":10,"gcMillis":162,"allocated":12711236552,"latency":{"p50":3.375,"p99":87.916,"p99.9":1651.875,"max":67143.75},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9730,"millis":10005.178041,"checksum":0,"latency":{"p50":817072.25,"p99":1257877.916,"p99.9":1263879.25,"max":1263879.25},"checked":false,"bytesWritten":286260937,"logicalWritten":311360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1356.406083,"checksum":50000,"gcCount":1,"gcMillis":12,"allocated":1208085328,"bytesWritten":71177557,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":45.87625,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":9264526,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":735.610292,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10487520,"bytesRead":38266217,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1015.809125,"checksum":1,"gcCount":1,"gcMillis":31,"allocated":807138512},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2798.175834,"checksum":831528,"gcCount":2,"gcMillis":72,"allocated":620955584,"bytesRead":522904025,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":450024057,"millis":1000.0,"checksum":450024057},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":9777.907333,"checksum":20000,"gcCount":6,"gcMillis":219,"allocated":6918673096,"bytesWritten":273954577,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":4317.834166,"checksum":20000,"gcCount":3,"gcMillis":102,"allocated":3596239760,"bytesWritten":162437921,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":108.886167,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9736048,"bytesRead":24532079,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":314665827,"millis":1000.0,"checksum":314665827},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.752208,"checksum":0,"gcCount":6,"gcMillis":82,"allocated":6788321800,"latency":{"p50":6701.0,"p99":16290.041,"p99.9":28686.25,"max":28686.25},"checked":false,"bytesWritten":397587794,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.349625,"checksum":0,"gcCount":3,"gcMillis":66,"allocated":3270673584,"latency":{"p50":6662.917,"p99":58786.083,"p99.9":73496.417,"max":73496.417},"checked":false,"bytesWritten":197036643,"logicalWritten":16000,"compactionMillis":30,"reclaimed":2923095},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":244562374,"millis":1000.0,"checksum":244562374},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":122.928417,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":79288360,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4143448064,"retainedHeap":131246128,"bytesWritten":5604949183,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1637.017667,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":930132088,"bytesWritten":53164783,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3117.694583,"checksum":362757,"gcCount":3,"gcMillis":55,"allocated":3049905048,"bytesWritten":240547277,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":100.285458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9815448,"bytesRead":22571555,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":29.83025,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12584405,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":261.533417,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":158105056,"bytesRead":83782811,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":95.108125,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418232,"bytesRead":24841349,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":408.254541,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":636724216},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":228.41525,"checksum":731528,"gcCount":1,"gcMillis":4,"allocated":640615240,"latency":{"p50":1.0,"p99":14.333,"p99.9":24.25,"max":4625.75}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":2289.272834,"checksum":20000,"gcCount":3,"gcMillis":21,"allocated":3476316000,"bytesWritten":150782263,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":21069.064875,"checksum":5000,"gcCount":37,"gcMillis":192,"allocated":45094370816,"latency":{"p50":4003.292,"p99":8984.042,"p99.9":12141.875,"max":21161.375},"bytesWritten":2542330948,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10661237,"millis":10000.139625,"checksum":0,"gcCount":19,"gcMillis":139,"allocated":24806961160,"latency":{"p50":3.375,"p99":62.375,"p99.9":397.333,"max":37101.958},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.139625,"checksum":0,"latency":{"p50":5921.583,"p99":122491.0,"p99.9":146777.083,"max":146777.083},"checked":false,"bytesWritten":708890089,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":729.91575,"checksum":50000,"gcCount":1,"gcMillis":11,"allocated":1272894208,"bytesWritten":81947024,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":18.505,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":9425898,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":81.322167,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9856680,"bytesRead":30003149,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1157.898958,"checksum":1,"gcCount":2,"gcMillis":10,"allocated":2376406072},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":915.863042,"checksum":831528,"gcCount":1,"gcMillis":14,"allocated":576764784,"bytesRead":268033158,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":332966565,"millis":1000.0,"checksum":332966565},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3646.381584,"checksum":20000,"gcCount":4,"gcMillis":161,"allocated":4501732784,"bytesWritten":195375543,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1532.368708,"checksum":20000,"gcCount":2,"gcMillis":15,"allocated":2844304112,"bytesWritten":138327141,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":55.567292,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9732856,"bytesRead":10216789,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":204901044,"millis":1000.0,"checksum":204901044},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9988.001041,"checksum":0,"gcCount":1,"gcMillis":12,"allocated":1547996376,"latency":{"p50":6108.208,"p99":9968.0,"p99.9":16371.0,"max":16371.0},"checked":false,"bytesWritten":96081361,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.023125,"checksum":0,"gcCount":2,"gcMillis":139,"allocated":1485615784,"latency":{"p50":6038.833,"p99":45948.166,"p99.9":109892.375,"max":109892.375},"checked":false,"bytesWritten":90326662,"logicalWritten":16000,"compactionMillis":33,"reclaimed":9641282},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":191402299,"millis":1000.0,"checksum":191402299},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":75.823625,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":80541912,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-3.json b/benchmarks/report/results/s1-sync/hypergraphdb-3.json index ec51482..aab1087 100644 --- a/benchmarks/report/results/s1-sync/hypergraphdb-3.json +++ b/benchmarks/report/results/s1-sync/hypergraphdb-3.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4164419584,"retainedHeap":124775744,"bytesWritten":3792405607,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3205.885459,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":925252392,"bytesWritten":52965684,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":6106.04425,"checksum":362757,"gcCount":4,"gcMillis":118,"allocated":3791586888,"bytesWritten":277856986,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":259.985375,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9931608,"bytesRead":17624965,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":85.026959,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":11011048,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":894.31725,"checksum":20287,"gcCount":1,"gcMillis":62,"allocated":166443376,"bytesRead":87311033,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":252.771083,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18418104,"bytesRead":29824905,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":471.117917,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":547307088},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":559.138458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":509628824,"latency":{"p50":2.042,"p99":33.209,"p99.9":503.541,"max":10947.5}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":5555.180709,"checksum":20000,"gcCount":6,"gcMillis":56,"allocated":6001118568,"bytesWritten":250737386,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":23620.532542,"checksum":5000,"gcCount":21,"gcMillis":268,"allocated":25742855080,"latency":{"p50":4039.417,"p99":13783.875,"p99.9":30767.375,"max":48036.166},"bytesWritten":1307157472,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":7311865,"millis":10000.232916,"checksum":0,"gcCount":13,"gcMillis":139,"allocated":16498292384,"latency":{"p50":3.541,"p99":82.5,"p99.9":900.875,"max":62958.041},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.232916,"checksum":0,"latency":{"p50":8346.166,"p99":135697.125,"p99.9":143285.708,"max":143285.708},"checked":false,"bytesWritten":398848855,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":843.102375,"checksum":50000,"gcCount":1,"gcMillis":8,"allocated":902454120,"bytesWritten":61828793,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":49.8525,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":4126551,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":812.016958,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10302104,"bytesRead":67938337,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1274.371084,"checksum":1,"gcCount":1,"gcMillis":17,"allocated":1736868296},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2125.078583,"checksum":831528,"gcCount":2,"gcMillis":42,"allocated":642139664,"bytesRead":364929202,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":365128112,"millis":1000.0,"checksum":365128112},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":5724.072542,"checksum":20000,"gcCount":5,"gcMillis":57,"allocated":5635580304,"bytesWritten":232822030,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2248.780208,"checksum":20000,"gcCount":3,"gcMillis":35,"allocated":3097843392,"bytesWritten":143495084,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":120.140625,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9755336,"bytesRead":18870870,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":271608003,"millis":1000.0,"checksum":271608003},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9987.054375,"checksum":0,"gcCount":3,"gcMillis":119,"allocated":3953286408,"latency":{"p50":6565.583,"p99":24932.666,"p99.9":56979.791,"max":56979.791},"checked":false,"bytesWritten":235966333,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.270709,"checksum":0,"gcCount":2,"gcMillis":149,"allocated":1656624560,"latency":{"p50":6431.584,"p99":56125.084,"p99.9":102672.25,"max":102672.25},"checked":false,"bytesWritten":102411753,"logicalWritten":16000,"compactionMillis":182,"reclaimed":-34558608},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":281092045,"millis":1000.0,"checksum":281092045},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":149.130833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":81760656,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4195352576,"retainedHeap":124260504,"bytesWritten":5664299953,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1980.428541,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":928481096,"bytesWritten":53148212,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":3381.191041,"checksum":362757,"gcCount":4,"gcMillis":82,"allocated":3074066272,"bytesWritten":242587226,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":71.240541,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":9771304,"bytesRead":19157916,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":42.043417,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4944168,"bytesRead":16130583,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":494.216542,"checksum":20287,"gcCount":1,"gcMillis":58,"allocated":160700984,"bytesRead":62746633,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":95.605,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18494144,"bytesRead":38329000,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":398.353375,"checksum":5852224,"gcCount":1,"gcMillis":5,"allocated":837833872},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":180.239041,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":558816984,"latency":{"p50":0.875,"p99":13.542,"p99.9":22.25,"max":86.334}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":2057.038792,"checksum":20000,"gcCount":4,"gcMillis":25,"allocated":4307838448,"bytesWritten":195273842,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":21157.946792,"checksum":5000,"gcCount":37,"gcMillis":185,"allocated":45957411656,"latency":{"p50":4005.625,"p99":8932.542,"p99.9":11872.084,"max":18823.042},"bytesWritten":2640916229,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10887597,"millis":10000.133,"checksum":0,"gcCount":20,"gcMillis":150,"allocated":26006911240,"latency":{"p50":3.458,"p99":59.625,"p99.9":351.5,"max":19932.542},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.133,"checksum":0,"latency":{"p50":5959.625,"p99":29097.708,"p99.9":38100.5,"max":38100.5},"checked":false,"bytesWritten":754545668,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":400.427166,"checksum":50000,"gcCount":1,"gcMillis":7,"allocated":686016848,"bytesWritten":55028588,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":21.67575,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6764654,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":55.117791,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9759672,"bytesRead":15492035,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1244.882083,"checksum":1,"gcCount":2,"gcMillis":8,"allocated":2564070496},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":955.035125,"checksum":831528,"gcCount":1,"gcMillis":15,"allocated":545697736,"bytesRead":275903795,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":367889011,"millis":1000.0,"checksum":367889011},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":2411.24025,"checksum":20000,"gcCount":4,"gcMillis":46,"allocated":4162176824,"bytesWritten":172217816,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2184.195542,"checksum":20000,"gcCount":2,"gcMillis":21,"allocated":2988443464,"bytesWritten":147364880,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":101.792209,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9853624,"bytesRead":7920897,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":284700691,"millis":1000.0,"checksum":284700691},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9985.149125,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":614642768,"latency":{"p50":6235.208,"p99":61457.375,"p99.9":78476.333,"max":78476.333},"checked":false,"bytesWritten":25214301,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.603208,"checksum":0,"gcCount":2,"gcMillis":23,"allocated":2427023088,"latency":{"p50":6392.167,"p99":11262.083,"p99.9":16176.042,"max":16176.042},"checked":false,"bytesWritten":146046512,"logicalWritten":16000,"compactionMillis":996,"reclaimed":53919081},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":290573969,"millis":1000.0,"checksum":290573969},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":78.183083,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":82029712,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-4.json b/benchmarks/report/results/s1-sync/hypergraphdb-4.json index 2d418c4..3f2c736 100644 --- a/benchmarks/report/results/s1-sync/hypergraphdb-4.json +++ b/benchmarks/report/results/s1-sync/hypergraphdb-4.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4196401152,"retainedHeap":125914864,"bytesWritten":3506102332,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3208.0455,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":920927880,"bytesWritten":53080818,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":5160.279334,"checksum":362757,"gcCount":2,"gcMillis":63,"allocated":2848071208,"bytesWritten":228591659,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":184.510708,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10168512,"bytesRead":13308143,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":62.2635,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":12565149,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":3029.223333,"checksum":20287,"gcCount":2,"gcMillis":111,"allocated":157990736,"bytesRead":361886067,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":300.565417,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18419672,"bytesRead":21435228,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":998.99625,"checksum":5852224,"gcCount":1,"gcMillis":14,"allocated":363220656},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":398.188458,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":433179320,"latency":{"p50":1.75,"p99":21.666,"p99.9":202.875,"max":10247.75}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":4390.158375,"checksum":20000,"gcCount":4,"gcMillis":53,"allocated":3746926304,"bytesWritten":158936406,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":23152.664417,"checksum":5000,"gcCount":21,"gcMillis":262,"allocated":24679586104,"latency":{"p50":4032.791,"p99":12173.75,"p99.9":36008.083,"max":105156.75},"bytesWritten":1246827170,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":6239112,"millis":10003.701959,"checksum":0,"gcCount":10,"gcMillis":153,"allocated":12821412632,"latency":{"p50":2.917,"p99":55.875,"p99.9":1624.708,"max":55830.458},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9370,"millis":10003.701959,"checksum":0,"latency":{"p50":370695.959,"p99":824950.542,"p99.9":829667.042,"max":829667.042},"checked":false,"bytesWritten":267052906,"logicalWritten":299840},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1729.385833,"checksum":50000,"gcCount":1,"gcMillis":36,"allocated":1396199008,"bytesWritten":83393033,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":77.614042,"checksum":20000000,"gcCount":1,"gcMillis":21,"allocated":462728,"bytesRead":12603978,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":84.377959,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9777528,"bytesRead":26232269,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1042.155209,"checksum":1,"gcCount":1,"gcMillis":17,"allocated":1275720936},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":2741.553083,"checksum":831528,"gcCount":2,"gcMillis":64,"allocated":628399480,"bytesRead":470702220,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":367973208,"millis":1000.0,"checksum":367973208},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":9280.499125,"checksum":20000,"gcCount":4,"gcMillis":236,"allocated":5866752496,"bytesWritten":242667288,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3553.806834,"checksum":20000,"gcCount":3,"gcMillis":142,"allocated":3354680400,"bytesWritten":153022677,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":77.936417,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9734632,"bytesRead":14816208,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":291649892,"millis":1000.0,"checksum":291649892},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.709,"checksum":0,"gcCount":3,"gcMillis":42,"allocated":3853409536,"latency":{"p50":6567.625,"p99":11249.5,"p99.9":24734.084,"max":24734.084},"checked":false,"bytesWritten":225190177,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.316791,"checksum":0,"gcCount":1,"gcMillis":70,"allocated":903971184,"latency":{"p50":6147.791,"p99":25107.958,"p99.9":89146.791,"max":89146.791},"checked":false,"bytesWritten":43499386,"logicalWritten":16000,"compactionMillis":56,"reclaimed":56467263},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":241531057,"millis":1000.0,"checksum":241531057},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":171.249583,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":82247984,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4200071168,"retainedHeap":124240384,"bytesWritten":4508841127,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1514.999292,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":931101296,"bytesWritten":53230968,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":4563.83675,"checksum":362757,"gcCount":3,"gcMillis":70,"allocated":2988679440,"bytesWritten":236233395,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":97.526666,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10115448,"bytesRead":12640053,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":64.798583,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":25768880,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":709.099,"checksum":20287,"gcCount":1,"gcMillis":58,"allocated":161295696,"bytesRead":75142619,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":95.960709,"checksum":281832,"gcCount":0,"gcMillis":0,"allocated":18420304,"bytesRead":23968693,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":590.482916,"checksum":5852224,"gcCount":1,"gcMillis":11,"allocated":707353888},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":271.11675,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":521740272,"latency":{"p50":1.458,"p99":17.25,"p99.9":27.792,"max":144.917}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":3683.232208,"checksum":20000,"gcCount":4,"gcMillis":28,"allocated":4942358672,"bytesWritten":217514885,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":21806.690417,"checksum":5000,"gcCount":25,"gcMillis":220,"allocated":31094988664,"latency":{"p50":4016.916,"p99":9778.208,"p99.9":23644.625,"max":42342.334},"bytesWritten":1667756432,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":9089227,"millis":10000.28275,"checksum":0,"gcCount":16,"gcMillis":136,"allocated":20932176944,"latency":{"p50":3.958,"p99":73.75,"p99.9":511.459,"max":22901.542},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.28275,"checksum":0,"latency":{"p50":6574.084,"p99":43133.709,"p99.9":52943.584,"max":52943.584},"checked":false,"bytesWritten":555010636,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":1131.124875,"checksum":50000,"gcCount":1,"gcMillis":8,"allocated":1247143888,"bytesWritten":78987294,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":25.315334,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":5301433,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":121.969458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9898032,"bytesRead":26231059,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1077.218209,"checksum":1,"gcCount":2,"gcMillis":11,"allocated":1855613792},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":1054.273791,"checksum":831528,"gcCount":1,"gcMillis":23,"allocated":584746192,"bytesRead":290957827,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":337342459,"millis":1000.0,"checksum":337342459},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":4719.779125,"checksum":20000,"gcCount":4,"gcMillis":125,"allocated":5545591992,"bytesWritten":237642523,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1889.849417,"checksum":20000,"gcCount":3,"gcMillis":33,"allocated":3118268568,"bytesWritten":149596372,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":88.516917,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9923760,"bytesRead":12305001,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":268941493,"millis":1000.0,"checksum":268941493},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.837167,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":772855192,"latency":{"p50":6661.208,"p99":13726.417,"p99.9":22484.583,"max":22484.583},"checked":false,"bytesWritten":37142132,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.568291,"checksum":0,"gcCount":3,"gcMillis":135,"allocated":3100629968,"latency":{"p50":6400.0,"p99":52300.583,"p99.9":100411.375,"max":100411.375},"checked":false,"bytesWritten":193367531,"logicalWritten":16000,"compactionMillis":14,"reclaimed":6594030},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":199632560,"millis":1000.0,"checksum":199632560},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":78.119625,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":80308136,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s1-sync/hypergraphdb-5.json b/benchmarks/report/results/s1-sync/hypergraphdb-5.json index 03e4b1a..074a3b5 100644 --- a/benchmarks/report/results/s1-sync/hypergraphdb-5.json +++ b/benchmarks/report/results/s1-sync/hypergraphdb-5.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4201644032,"retainedHeap":124035896,"bytesWritten":3443793787,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":3959.679042,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":922205312,"bytesWritten":52927282,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":6442.541166,"checksum":362757,"gcCount":2,"gcMillis":132,"allocated":2856328288,"bytesWritten":228130562,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":250.215708,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":10678864,"bytesRead":15567314,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":165.229416,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":5256430,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1818.046875,"checksum":20287,"gcCount":0,"gcMillis":0,"allocated":161413360,"bytesRead":57651866,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":262.058666,"checksum":281832,"gcCount":1,"gcMillis":23,"allocated":18418528,"bytesRead":38419940,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":849.201,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":555938152},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":449.981667,"checksum":731528,"gcCount":0,"gcMillis":0,"allocated":434484448,"latency":{"p50":2.292,"p99":30.0,"p99.9":130.667,"max":1550.875}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":7296.0895,"checksum":20000,"gcCount":5,"gcMillis":140,"allocated":5000578776,"bytesWritten":217542144,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":26592.354542,"checksum":5000,"gcCount":17,"gcMillis":450,"allocated":19239384536,"latency":{"p50":4215.334,"p99":16758.083,"p99.9":42098.75,"max":54472.042},"bytesWritten":919281207,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5288494,"millis":10000.821625,"checksum":0,"gcCount":9,"gcMillis":222,"allocated":11400995808,"latency":{"p50":3.125,"p99":90.709,"p99.9":1738.958,"max":95120.375},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9730,"millis":10000.821625,"checksum":0,"latency":{"p50":270856.75,"p99":763312.875,"p99.9":769138.792,"max":769138.792},"checked":false,"bytesWritten":245555854,"logicalWritten":311360},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":2160.875084,"checksum":50000,"gcCount":1,"gcMillis":112,"allocated":1338932792,"bytesWritten":77293879,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":95.659458,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":6481351,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":115.117458,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9836624,"bytesRead":29672665,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":2417.289,"checksum":1,"gcCount":1,"gcMillis":70,"allocated":701707056},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":5094.897209,"checksum":831528,"gcCount":2,"gcMillis":214,"allocated":656914328,"bytesRead":585203170,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":442025040,"millis":1000.0,"checksum":442025040},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":8597.474959,"checksum":20000,"gcCount":6,"gcMillis":278,"allocated":7081182144,"bytesWritten":282932853,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2498.250125,"checksum":20000,"gcCount":3,"gcMillis":28,"allocated":3435295024,"bytesWritten":154861906,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":269.39825,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":10562744,"bytesRead":22896880,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":377008317,"millis":1000.0,"checksum":377008317},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.061208,"checksum":0,"gcCount":8,"gcMillis":247,"allocated":8978248976,"latency":{"p50":6484.125,"p99":64086.541,"p99.9":109474.833,"max":109474.833},"checked":false,"bytesWritten":523390727,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9986.711666,"checksum":0,"gcCount":2,"gcMillis":55,"allocated":2402045056,"latency":{"p50":6689.666,"p99":16471.541,"p99.9":39469.166,"max":39469.166},"checked":false,"bytesWritten":134989297,"logicalWritten":16000,"compactionMillis":42,"reclaimed":64973344},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":246865658,"millis":1000.0,"checksum":246865658},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":128.002167,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83804848,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":1,"durability":"sync","threads":8,"nodes":50000,"edges":100000,"incidences":362757,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4183818240,"retainedHeap":124251176,"bytesWritten":4275301887,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":50000,"millis":1618.216916,"checksum":50000,"gcCount":0,"gcMillis":0,"allocated":928801576,"bytesWritten":53260519,"logicalWritten":888890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":100000,"millis":7313.246834,"checksum":362757,"gcCount":3,"gcMillis":111,"allocated":2814194176,"bytesWritten":225843227,"logicalWritten":3702056},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":219.768625,"checksum":731528,"gcCount":1,"gcMillis":31,"allocated":10539320,"bytesRead":13273192,"logicalRead":5852224},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":87.75975,"checksum":363372,"gcCount":0,"gcMillis":0,"allocated":4947368,"bytesRead":5287587,"logicalRead":2906976},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":548.059083,"checksum":20287,"gcCount":1,"gcMillis":50,"allocated":162097120,"bytesRead":89759480,"logicalRead":162296},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":145.075291,"checksum":281832,"gcCount":1,"gcMillis":8,"allocated":18415272,"bytesRead":36865572,"logicalRead":2254656},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":512.3125,"checksum":5852224,"gcCount":0,"gcMillis":0,"allocated":736109528},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":364.543875,"checksum":731528,"gcCount":1,"gcMillis":9,"allocated":540918504,"latency":{"p50":1.625,"p99":24.625,"p99.9":150.5,"max":9637.833}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":5494.844708,"checksum":20000,"gcCount":5,"gcMillis":99,"allocated":5539507792,"bytesWritten":236955272,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":21881.645875,"checksum":5000,"gcCount":27,"gcMillis":228,"allocated":32914925640,"latency":{"p50":4018.375,"p99":10078.625,"p99.9":15869.083,"max":27534.083},"bytesWritten":1730196892,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":8851443,"millis":10000.232833,"checksum":0,"gcCount":16,"gcMillis":204,"allocated":21558216296,"latency":{"p50":3.917,"p99":76.834,"p99.9":476.875,"max":78756.208},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.232833,"checksum":0,"latency":{"p50":6231.083,"p99":122757.167,"p99.9":146349.375,"max":146349.375},"checked":false,"bytesWritten":607097729,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 50,000 members","operations":50000,"millis":798.846458,"checksum":50000,"gcCount":1,"gcMillis":13,"allocated":1310287728,"bytesWritten":84039384,"logicalWritten":400008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":20000000,"millis":29.709834,"checksum":20000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":10285876,"logicalRead":160000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":111.011958,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9837640,"bytesRead":22198472,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":911.994958,"checksum":1,"gcCount":1,"gcMillis":6,"allocated":1642087664},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":962.867542,"checksum":831528,"gcCount":1,"gcMillis":27,"allocated":590126184,"bytesRead":316876571,"logicalRead":6652224},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":353471074,"millis":1000.0,"checksum":353471074},{"workload":"churn.delete","description":"delete 20,000 hyperedges (20%) in transactions of 1,000","operations":20000,"millis":3679.395334,"checksum":20000,"gcCount":5,"gcMillis":50,"allocated":5413251248,"bytesWritten":229210698,"logicalWritten":739496},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":1682.314666,"checksum":20000,"gcCount":2,"gcMillis":14,"allocated":2945369728,"bytesWritten":136555871,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":97.760708,"checksum":645087,"gcCount":0,"gcMillis":0,"allocated":9742000,"bytesRead":15068226,"logicalRead":5160696},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":289531017,"millis":1000.0,"checksum":289531017},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9986.970625,"checksum":0,"gcCount":1,"gcMillis":30,"allocated":616638424,"latency":{"p50":6603.667,"p99":12838.25,"p99.9":37809.625,"max":37809.625},"checked":false,"bytesWritten":23944169,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9984.95275,"checksum":0,"gcCount":1,"gcMillis":87,"allocated":1501120216,"latency":{"p50":6600.666,"p99":29746.291,"p99.9":93964.541,"max":93964.541},"checked":false,"bytesWritten":90756307,"logicalWritten":16000,"compactionMillis":21,"reclaimed":9207161},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":282340517,"millis":1000.0,"checksum":282340517},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":53.853166,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":83348424,"checked":false,"acknowledged":25000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-1.json b/benchmarks/report/results/s4-async/hstore-1.json index 4806426..d1e6c13 100644 --- a/benchmarks/report/results/s4-async/hstore-1.json +++ b/benchmarks/report/results/s4-async/hstore-1.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4146069504,"retainedHeap":444737256,"bytesWritten":2305078324,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":8655.863708,"checksum":200000,"gcCount":5,"gcMillis":330,"allocated":5291253128,"bytesWritten":65930681,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":47689.885916,"checksum":1453427,"gcCount":46,"gcMillis":3493,"allocated":47557562320,"bytesWritten":570188101,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":138.624875,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":2716864,"bytesRead":4398210,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":608.273375,"checksum":361142,"gcCount":1,"gcMillis":20,"allocated":904828352,"bytesRead":77805459,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":51.244584,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":81941832,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":666.304792,"checksum":280131,"gcCount":1,"gcMillis":4,"allocated":1338919752,"bytesRead":124689877,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":154.431709,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":72612368},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":86.842,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29316240,"latency":{"p50":0.792,"p99":1.666,"p99.9":6.667,"max":98.625}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":1707.468709,"checksum":20000,"gcCount":1,"gcMillis":195,"allocated":1027740192,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":3327.388292,"checksum":5000,"gcCount":1,"gcMillis":360,"allocated":795936576,"latency":{"p50":198.75,"p99":6682.458,"p99.9":18924.167,"max":366540.167},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":13504877,"millis":10000.27925,"checksum":0,"gcCount":2,"gcMillis":497,"allocated":2123568936,"latency":{"p50":1.75,"p99":17.792,"p99.9":271.084,"max":286433.875},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.27925,"checksum":0,"latency":{"p50":4189.0,"p99":280138.958,"p99.9":315096.75,"max":315096.75},"checked":false,"bytesWritten":53149590,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":1344.662916,"checksum":200000,"gcCount":0,"gcMillis":0,"allocated":513540200,"bytesWritten":14196352,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":85.668625,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":198736,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":42.536333,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":2429024,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":312.757208,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":66246576},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":348.826125,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102190024,"bytesRead":11044181,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":173181483,"millis":1000.0,"checksum":173181483},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":38268.782666,"checksum":80000,"gcCount":24,"gcMillis":1640,"allocated":26403249120,"bytesWritten":1275527774,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":8165.195375,"checksum":20000,"gcCount":3,"gcMillis":289,"allocated":3186031680,"bytesWritten":267859019,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":28.980625,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":142240,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1196213949,"millis":1000.0,"checksum":1196213949},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.09525,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120849064,"latency":{"p50":3378.542,"p99":6834.25,"p99.9":9502.334,"max":9502.334},"checked":false,"bytesWritten":36369585,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.747083,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":105921192,"latency":{"p50":3117.667,"p99":5708.208,"p99.9":11465.542,"max":11465.542},"checked":false,"bytesWritten":3224983,"logicalWritten":16000,"compactionMillis":82,"reclaimed":-3298395},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170535360,"millis":1000.0,"checksum":170535360},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":402.569333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":245826960,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4145545216,"retainedHeap":450434464,"bytesWritten":2230887641,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":4998.879333,"checksum":200000,"gcCount":5,"gcMillis":192,"allocated":5227238040,"bytesWritten":60160379,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":18928.860959,"checksum":1453427,"gcCount":40,"gcMillis":1253,"allocated":40921218088,"bytesWritten":392641408,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":14.153708,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":136008,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":21.939042,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":15760,"bytesRead":0,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":65.265166,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":82510968,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":44.068458,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":11826896,"bytesRead":2777116,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":82.389,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":105147832},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":39.483042,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29322024,"latency":{"p50":0.375,"p99":0.625,"p99.9":1.5,"max":18.708}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":385.0815,"checksum":20000,"gcCount":1,"gcMillis":33,"allocated":1035827808,"bytesWritten":10155587,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":410.067833,"checksum":5000,"gcCount":1,"gcMillis":30,"allocated":697280968,"latency":{"p50":54.792,"p99":232.125,"p99.9":475.417,"max":30450.917},"bytesWritten":8872968,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":19294469,"millis":10000.121041,"checksum":0,"gcCount":2,"gcMillis":203,"allocated":2284548048,"latency":{"p50":1.833,"p99":13.542,"p99.9":68.458,"max":114355.875},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.121041,"checksum":0,"latency":{"p50":2636.583,"p99":65751.458,"p99.9":112247.125,"max":112247.125},"checked":false,"bytesWritten":56823499,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":408.784042,"checksum":200000,"gcCount":1,"gcMillis":26,"allocated":830358672,"bytesWritten":14573475,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":21.988875,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":176424,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":8.538333,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":6416528,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":206.307125,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":64680264},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":85.982333,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":59528632,"bytesRead":11309990,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":173736544,"millis":1000.0,"checksum":173736544},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":23612.875375,"checksum":80000,"gcCount":32,"gcMillis":693,"allocated":35567794840,"bytesWritten":1349057776,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3465.6795,"checksum":20000,"gcCount":4,"gcMillis":162,"allocated":3987761888,"bytesWritten":297020440,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":17.613708,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":141760,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1299803251,"millis":1000.0,"checksum":1299803251},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.790625,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":157883608,"latency":{"p50":3464.459,"p99":6182.167,"p99.9":9970.959,"max":9970.959},"checked":false,"bytesWritten":38352017,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9981.146792,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":139764344,"latency":{"p50":3379.084,"p99":6337.75,"p99.9":12463.875,"max":12463.875},"checked":false,"bytesWritten":3229400,"logicalWritten":16000,"compactionMillis":80,"reclaimed":-3302111},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":172512739,"millis":1000.0,"checksum":172512739},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":188.662458,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":175432480,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-2.json b/benchmarks/report/results/s4-async/hstore-2.json index 96ae9b9..3526df3 100644 --- a/benchmarks/report/results/s4-async/hstore-2.json +++ b/benchmarks/report/results/s4-async/hstore-2.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4126146560,"retainedHeap":447346784,"bytesWritten":2216335391,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":7392.157625,"checksum":200000,"gcCount":5,"gcMillis":181,"allocated":5400270224,"bytesWritten":67052769,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":34201.176084,"checksum":1453427,"gcCount":45,"gcMillis":1813,"allocated":46420723280,"bytesWritten":476761893,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":37.612,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":150232,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":457.941125,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":903735504,"bytesRead":77793593,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":81.39825,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":82288608,"bytesRead":38939,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":1117.638417,"checksum":280131,"gcCount":1,"gcMillis":8,"allocated":1337340688,"bytesRead":124526520,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":197.334417,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":70085304},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":97.475667,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":15707192,"latency":{"p50":0.709,"p99":1.416,"p99.9":14.458,"max":8473.708}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":961.842417,"checksum":20000,"gcCount":1,"gcMillis":47,"allocated":1028033152,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":959.628791,"checksum":5000,"gcCount":1,"gcMillis":68,"allocated":812547208,"latency":{"p50":118.292,"p99":1337.625,"p99.9":3960.334,"max":68550.667},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":17624033,"millis":10000.2005,"checksum":0,"gcCount":2,"gcMillis":103,"allocated":2997405104,"latency":{"p50":1.5,"p99":11.667,"p99.9":113.541,"max":76018.459},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.2005,"checksum":0,"latency":{"p50":3906.0,"p99":52770.416,"p99.9":77999.0,"max":77999.0},"checked":false,"bytesWritten":56591276,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":940.494333,"checksum":200000,"gcCount":0,"gcMillis":0,"allocated":548690504,"bytesWritten":14196353,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":50.034667,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":176328,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":14.664667,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":18448,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":237.630916,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":66119376},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":289.004625,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102371608,"bytesRead":11044182,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":173146597,"millis":1000.0,"checksum":173146597},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":52162.505333,"checksum":80000,"gcCount":23,"gcMillis":2246,"allocated":26318688400,"bytesWritten":1275567405,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":7073.17775,"checksum":20000,"gcCount":3,"gcMillis":341,"allocated":3142927688,"bytesWritten":267899102,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":34.880458,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":135360,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1196249345,"millis":1000.0,"checksum":1196249345},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.732542,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120685768,"latency":{"p50":5429.167,"p99":19718.0,"p99.9":41085.208,"max":41085.208},"checked":false,"bytesWritten":36409371,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9988.75425,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":104978256,"latency":{"p50":4891.667,"p99":10273.834,"p99.9":13555.292,"max":13555.292},"checked":false,"bytesWritten":3224983,"logicalWritten":16000,"compactionMillis":77,"reclaimed":-3298395},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170574023,"millis":1000.0,"checksum":170574023},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":202.587958,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":129709408,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4128243712,"retainedHeap":456592896,"bytesWritten":2249237515,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":3472.183917,"checksum":200000,"gcCount":5,"gcMillis":121,"allocated":5240988616,"bytesWritten":63063899,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":14646.238667,"checksum":1453427,"gcCount":41,"gcMillis":685,"allocated":41621087616,"bytesWritten":423052778,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":15.225708,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":144152,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":17.934416,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":16216,"bytesRead":0,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":52.56125,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":82510944,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":25.187958,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":9172032,"bytesRead":2037134,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":53.877875,"checksum":5757544,"gcCount":1,"gcMillis":3,"allocated":111664280},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":42.401792,"checksum":719693,"gcCount":1,"gcMillis":10,"allocated":26908872,"latency":{"p50":0.292,"p99":0.584,"p99.9":0.958,"max":9545.917}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":295.91425,"checksum":20000,"gcCount":0,"gcMillis":0,"allocated":1047528656,"bytesWritten":10155587,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":298.2695,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":712618752,"latency":{"p50":40.708,"p99":204.75,"p99.9":280.542,"max":538.125},"bytesWritten":8872968,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":19847931,"millis":10000.111,"checksum":0,"gcCount":2,"gcMillis":62,"allocated":2269043088,"latency":{"p50":2.291,"p99":13.208,"p99.9":26.5,"max":42060.916},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.111,"checksum":0,"latency":{"p50":3068.041,"p99":9869.666,"p99.9":38905.375,"max":38905.375},"checked":false,"bytesWritten":55998670,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":377.108208,"checksum":200000,"gcCount":1,"gcMillis":9,"allocated":832478512,"bytesWritten":14573264,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":21.458791,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":177072,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":6.477208,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16360,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":129.629167,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":66225992},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":49.846875,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":59532680,"bytesRead":11309826,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":172907434,"millis":1000.0,"checksum":172907434},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":18625.098375,"checksum":80000,"gcCount":34,"gcMillis":251,"allocated":36070217104,"bytesWritten":1334071335,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":3330.6515,"checksum":20000,"gcCount":4,"gcMillis":100,"allocated":4014354152,"bytesWritten":297847837,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":15.853,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":141736,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1284740622,"millis":1000.0,"checksum":1284740622},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.586417,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":157723904,"latency":{"p50":5326.333,"p99":9800.792,"p99.9":10772.083,"max":10772.083},"checked":false,"bytesWritten":38358787,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9987.999208,"checksum":0,"gcCount":1,"gcMillis":15,"allocated":139723648,"latency":{"p50":5153.083,"p99":9946.166,"p99.9":21664.083,"max":21664.083},"checked":false,"bytesWritten":3241698,"logicalWritten":16000,"compactionMillis":86,"reclaimed":-3314463},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":172528768,"millis":1000.0,"checksum":172528768},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":218.654667,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":269927496,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hstore-3.json b/benchmarks/report/results/s4-async/hstore-3.json index e07a8f9..f78414d 100644 --- a/benchmarks/report/results/s4-async/hstore-3.json +++ b/benchmarks/report/results/s4-async/hstore-3.json @@ -1 +1 @@ -{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4126670848,"retainedHeap":445128760,"bytesWritten":2086996896,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":3677.721292,"checksum":200000,"gcCount":5,"gcMillis":130,"allocated":5243410448,"bytesWritten":63535075,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":13731.081458,"checksum":1453427,"gcCount":45,"gcMillis":598,"allocated":46626547048,"bytesWritten":348992566,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":28.499417,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":138792,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":226.332708,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":903785744,"bytesRead":77798877,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":29.181375,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":83861472,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":444.102042,"checksum":280131,"gcCount":2,"gcMillis":7,"allocated":1338476896,"bytesRead":124641114,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":32.808583,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":75015952},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":35.78575,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29301576,"latency":{"p50":0.333,"p99":0.667,"p99.9":2.333,"max":24.333}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":308.013375,"checksum":20000,"gcCount":1,"gcMillis":8,"allocated":1038687376,"bytesWritten":9950206,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":410.589208,"checksum":5000,"gcCount":1,"gcMillis":7,"allocated":804793784,"latency":{"p50":72.709,"p99":230.167,"p99.9":337.875,"max":7560.209},"bytesWritten":8681341,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":20985931,"millis":10000.125333,"checksum":0,"gcCount":2,"gcMillis":41,"allocated":2440713152,"latency":{"p50":2.084,"p99":12.417,"p99.9":31.833,"max":53075.833},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.125333,"checksum":0,"latency":{"p50":3069.041,"p99":15425.375,"p99.9":33146.083,"max":33146.083},"checked":false,"bytesWritten":54563977,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":316.438667,"checksum":200000,"gcCount":1,"gcMillis":15,"allocated":768284560,"bytesWritten":14196352,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":28.257958,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":166848,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":5.875125,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":15544,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":126.578833,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":68417632},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":54.780625,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":102378224,"bytesRead":11044181,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":171110120,"millis":1000.0,"checksum":171110120},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":15947.684458,"checksum":80000,"gcCount":22,"gcMillis":156,"allocated":25761462736,"bytesWritten":1277587609,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":2644.125916,"checksum":20000,"gcCount":2,"gcMillis":54,"allocated":2955254488,"bytesWritten":269901425,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":15.06525,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":128592,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1198273724,"millis":1000.0,"checksum":1198273724},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.666291,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":120894504,"latency":{"p50":5381.125,"p99":10045.0,"p99.9":11065.25,"max":11065.25},"checked":false,"bytesWritten":36374959,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.794791,"checksum":0,"gcCount":1,"gcMillis":17,"allocated":105956464,"latency":{"p50":5371.833,"p99":9954.208,"p99.9":27274.458,"max":27274.458},"checked":false,"bytesWritten":3212694,"logicalWritten":16000,"compactionMillis":75,"reclaimed":-3286043},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":170527468,"millis":1000.0,"checksum":170527468},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":176.874333,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":231893792,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HStore","version":"0.1.0","cache":"node cache of 64 MiB and retains 64 generations of history","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4131913728,"retainedHeap":429779056,"bytesWritten":2250060131,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":5885.500417,"checksum":200000,"gcCount":5,"gcMillis":171,"allocated":5255854256,"bytesWritten":63571046,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":20013.312917,"checksum":1453427,"gcCount":42,"gcMillis":1001,"allocated":42609173200,"bytesWritten":409098707,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":15.696334,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":120176,"bytesRead":0,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":16.858083,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":13216,"bytesRead":0,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":55.878375,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":82510584,"bytesRead":0,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":32.124083,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":12634120,"bytesRead":2975456,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":44.61675,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":72663304},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":66.215084,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":29315376,"latency":{"p50":0.583,"p99":1.125,"p99.9":10.75,"max":61.834}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":911.148625,"checksum":20000,"gcCount":1,"gcMillis":37,"allocated":1043121608,"bytesWritten":10155587,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":596.46925,"checksum":5000,"gcCount":0,"gcMillis":0,"allocated":728025664,"latency":{"p50":103.75,"p99":285.208,"p99.9":696.417,"max":3354.125},"bytesWritten":8872968,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":21775522,"millis":10000.1335,"checksum":0,"gcCount":3,"gcMillis":79,"allocated":2870011880,"latency":{"p50":1.375,"p99":10.833,"p99.9":83.084,"max":50178.791},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.1335,"checksum":0,"latency":{"p50":3669.375,"p99":23526.459,"p99.9":58245.75,"max":58245.75},"checked":false,"bytesWritten":57545323,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":559.101417,"checksum":200000,"gcCount":1,"gcMillis":25,"allocated":832851552,"bytesWritten":14573475,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":32.370834,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":198560,"bytesRead":0,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":14.675333,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":16432,"bytesRead":0,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":241.590875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":64689896},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":101.567459,"checksum":819693,"gcCount":0,"gcMillis":0,"allocated":59549800,"bytesRead":11309990,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":174461755,"millis":1000.0,"checksum":174461755},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":35953.531375,"checksum":80000,"gcCount":30,"gcMillis":836,"allocated":33967982240,"bytesWritten":1348345415,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":5701.093167,"checksum":20000,"gcCount":4,"gcMillis":272,"allocated":3806376392,"bytesWritten":296308600,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":28.842916,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":139168,"bytesRead":0,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1299076699,"millis":1000.0,"checksum":1299076699},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.429416,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":153798120,"latency":{"p50":5132.666,"p99":10192.291,"p99.9":24199.0,"max":24199.0},"checked":false,"bytesWritten":38358918,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":500,"millis":9983.895166,"checksum":0,"gcCount":0,"gcMillis":0,"allocated":135812184,"latency":{"p50":4779.791,"p99":18456.708,"p99.9":39319.333,"max":39319.333},"checked":false,"bytesWritten":3229400,"logicalWritten":16000,"compactionMillis":79,"reclaimed":-3302111},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":172519023,"millis":1000.0,"checksum":172519023},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":257.284417,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":261124184,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-1.json b/benchmarks/report/results/s4-async/hypergraphdb-1.json index 0b199fb..4fddad0 100644 --- a/benchmarks/report/results/s4-async/hypergraphdb-1.json +++ b/benchmarks/report/results/s4-async/hypergraphdb-1.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4223139840,"retainedHeap":317396936,"bytesWritten":98493414902,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":11057.100209,"checksum":200000,"gcCount":5,"gcMillis":182,"allocated":5139836256,"bytesWritten":307491704,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":291965.123916,"checksum":1453427,"gcCount":278,"gcMillis":4408,"allocated":358790172056,"bytesWritten":11617313382,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":214.517209,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":10137496,"bytesRead":78679830,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":58.161417,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":30210646,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":7994.725958,"checksum":20109,"gcCount":5,"gcMillis":192,"allocated":160384888,"bytesRead":1590410086,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":466.921334,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18446304,"bytesRead":67146161,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":765.611167,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":657443512},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":831.992708,"checksum":719693,"gcCount":1,"gcMillis":89,"allocated":604232664,"latency":{"p50":2.333,"p99":86.083,"p99.9":648.792,"max":89946.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":19127.642041,"checksum":20000,"gcCount":19,"gcMillis":428,"allocated":25474380080,"bytesWritten":797167664,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":3613.52825,"checksum":5000,"gcCount":3,"gcMillis":34,"allocated":4623201920,"latency":{"p50":396.458,"p99":4146.833,"p99.9":13039.25,"max":160510.833},"bytesWritten":146984560,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":5398244,"millis":10083.284916,"checksum":0,"gcCount":12,"gcMillis":283,"allocated":15186521936,"latency":{"p50":2.708,"p99":87.417,"p99.9":1962.708,"max":96660.083},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9040,"millis":10083.284916,"checksum":0,"latency":{"p50":700593.291,"p99":1300881.041,"p99.9":1315928.333,"max":1315928.333},"checked":false,"bytesWritten":259471056,"logicalWritten":289280},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":26327.465167,"checksum":200000,"gcCount":27,"gcMillis":624,"allocated":35281086688,"bytesWritten":1249081034,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":898.590167,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":71577263,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":104.974625,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9805952,"bytesRead":60150621,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1569.177458,"checksum":1,"gcCount":2,"gcMillis":19,"allocated":2449472560},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":16533.038792,"checksum":819693,"gcCount":13,"gcMillis":694,"allocated":2484526792,"bytesRead":4444818269,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2480960849,"millis":1000.0,"checksum":2480960849},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":90354.933333,"checksum":80000,"gcCount":88,"gcMillis":2702,"allocated":115903851040,"bytesWritten":3472506444,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":11562.92975,"checksum":20000,"gcCount":12,"gcMillis":318,"allocated":15122690880,"bytesWritten":458759646,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":130.256542,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9912840,"bytesRead":55071034,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1960416709,"millis":1000.0,"checksum":1960416709},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.419542,"checksum":0,"gcCount":11,"gcMillis":267,"allocated":14444349584,"latency":{"p50":5539.917,"p99":73360.25,"p99.9":124204.334,"max":124204.334},"checked":false,"bytesWritten":466353601,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":71832,"millis":1436624.336417,"checksum":0,"gcCount":1464,"gcMillis":42727,"allocated":1935856795184,"latency":{"p50":5620.833,"p99":92771.458,"p99.9":210770.958,"max":429520.458},"checked":false,"bytesWritten":77624298288,"logicalWritten":2298624,"compactionMillis":1435611,"reclaimed":689296502},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1234929428,"millis":1000.0,"checksum":1234929428},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":1213.614166,"checksum":1,"gcCount":1,"gcMillis":240,"allocated":343651200,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4221566976,"retainedHeap":316767568,"bytesWritten":106157943532,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":6965.681916,"checksum":200000,"gcCount":5,"gcMillis":122,"allocated":5276596808,"bytesWritten":317722874,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":195087.434583,"checksum":1453427,"gcCount":276,"gcMillis":2088,"allocated":356263188104,"bytesWritten":11540492649,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":173.934375,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":10170160,"bytesRead":77434111,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":57.747791,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":36639461,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1103.276042,"checksum":20109,"gcCount":1,"gcMillis":17,"allocated":167650432,"bytesRead":483182793,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":139.707542,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18445144,"bytesRead":61895311,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":590.262958,"checksum":5757544,"gcCount":1,"gcMillis":13,"allocated":1272430704},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":285.169667,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":717408944,"latency":{"p50":1.334,"p99":21.583,"p99.9":35.041,"max":383.25}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":12745.708,"checksum":20000,"gcCount":20,"gcMillis":132,"allocated":25427847160,"bytesWritten":796687276,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":2805.004375,"checksum":5000,"gcCount":3,"gcMillis":16,"allocated":4197489992,"latency":{"p50":311.667,"p99":4169.959,"p99.9":8250.458,"max":18843.667},"bytesWritten":134046895,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":8429776,"millis":10035.263,"checksum":0,"gcCount":21,"gcMillis":194,"allocated":26267116576,"latency":{"p50":2.75,"p99":60.125,"p99.9":967.875,"max":42132.875},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":9980,"millis":10035.263,"checksum":0,"latency":{"p50":5622.125,"p99":74746.167,"p99.9":88035.125,"max":88035.125},"checked":false,"bytesWritten":498190094,"logicalWritten":319360},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":16561.517709,"checksum":200000,"gcCount":26,"gcMillis":179,"allocated":35079751176,"bytesWritten":1248364054,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":529.778709,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":111581793,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":89.675375,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9843168,"bytesRead":56214988,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1157.123209,"checksum":1,"gcCount":2,"gcMillis":15,"allocated":2111114152},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":7644.840083,"checksum":819693,"gcCount":12,"gcMillis":117,"allocated":2443740896,"bytesRead":4243629361,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2482239070,"millis":1000.0,"checksum":2482239070},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":54844.208167,"checksum":80000,"gcCount":86,"gcMillis":714,"allocated":113074291336,"bytesWritten":3356311725,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":7346.485958,"checksum":20000,"gcCount":11,"gcMillis":81,"allocated":14715324824,"bytesWritten":450490233,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":76.773875,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9778504,"bytesRead":45166530,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1936326696,"millis":1000.0,"checksum":1936326696},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9984.558166,"checksum":0,"gcCount":16,"gcMillis":113,"allocated":20633429664,"latency":{"p50":3962.958,"p99":13119.208,"p99.9":25515.666,"max":25515.666},"checked":false,"bytesWritten":660196465,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":49106,"millis":982103.178917,"checksum":0,"gcCount":1575,"gcMillis":13087,"allocated":2081160235440,"latency":{"p50":4881.0,"p99":15368.375,"p99.9":83169.958,"max":283871.5},"checked":false,"bytesWritten":84952960325,"logicalWritten":1571392,"compactionMillis":981089,"reclaimed":849393714},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1215413605,"millis":1000.0,"checksum":1215413605},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":454.755416,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":338847792,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-2.json b/benchmarks/report/results/s4-async/hypergraphdb-2.json index 0a3a956..8a1b21e 100644 --- a/benchmarks/report/results/s4-async/hypergraphdb-2.json +++ b/benchmarks/report/results/s4-async/hypergraphdb-2.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4170186752,"retainedHeap":317886456,"bytesWritten":99483620350,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":9354.516083,"checksum":200000,"gcCount":5,"gcMillis":163,"allocated":5241088080,"bytesWritten":314110025,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":210238.717334,"checksum":1453427,"gcCount":277,"gcMillis":2567,"allocated":358952196728,"bytesWritten":11644932808,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":97.999791,"checksum":719693,"gcCount":1,"gcMillis":9,"allocated":9843232,"bytesRead":58452171,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":91.977833,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":31063693,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":441.72525,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":167257840,"bytesRead":236427141,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":128.05025,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18446648,"bytesRead":75287848,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":448.334792,"checksum":5757544,"gcCount":1,"gcMillis":15,"allocated":893363464},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":274.669041,"checksum":719693,"gcCount":1,"gcMillis":8,"allocated":632283720,"latency":{"p50":1.292,"p99":20.833,"p99.9":46.667,"max":7856.167}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":16131.895166,"checksum":20000,"gcCount":22,"gcMillis":313,"allocated":28541910200,"bytesWritten":902893446,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":2478.90275,"checksum":5000,"gcCount":3,"gcMillis":17,"allocated":4185081376,"latency":{"p50":297.042,"p99":3189.333,"p99.9":7892.333,"max":9908.208},"bytesWritten":134830026,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":8837808,"millis":10000.448333,"checksum":0,"gcCount":20,"gcMillis":179,"allocated":26323825472,"latency":{"p50":2.416,"p99":46.417,"p99.9":1036.416,"max":48023.833},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.448333,"checksum":0,"latency":{"p50":5951.167,"p99":74803.208,"p99.9":101465.083,"max":101465.083},"checked":false,"bytesWritten":488935039,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":15836.561167,"checksum":200000,"gcCount":26,"gcMillis":167,"allocated":35064748408,"bytesWritten":1247239413,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":141.75925,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":83940881,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":100.89425,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9893088,"bytesRead":58454078,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1209.942709,"checksum":1,"gcCount":2,"gcMillis":37,"allocated":2259939472},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":8451.375417,"checksum":819693,"gcCount":12,"gcMillis":130,"allocated":2468126504,"bytesRead":4372803781,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2493223082,"millis":1000.0,"checksum":2493223082},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":53795.552833,"checksum":80000,"gcCount":88,"gcMillis":893,"allocated":114865595624,"bytesWritten":3438056447,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":7452.05175,"checksum":20000,"gcCount":12,"gcMillis":103,"allocated":15289880776,"bytesWritten":466543950,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":128.685041,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9964680,"bytesRead":70474330,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1966539494,"millis":1000.0,"checksum":1966539494},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9982.674708,"checksum":0,"gcCount":13,"gcMillis":289,"allocated":16689911416,"latency":{"p50":4202.792,"p99":36697.833,"p99.9":114472.042,"max":114472.042},"checked":false,"bytesWritten":532128041,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":47407,"millis":948123.622917,"checksum":0,"gcCount":1469,"gcMillis":14892,"allocated":1942519563720,"latency":{"p50":5771.084,"p99":17430.084,"p99.9":102915.75,"max":278112.5},"checked":false,"bytesWritten":78160852933,"logicalWritten":1517024,"compactionMillis":947107,"reclaimed":753717251},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1275875206,"millis":1000.0,"checksum":1275875206},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":362.422708,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":337465688,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4171759616,"retainedHeap":313575576,"bytesWritten":101787400268,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":5136.364208,"checksum":200000,"gcCount":5,"gcMillis":108,"allocated":5276666400,"bytesWritten":317087397,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":146224.391,"checksum":1453427,"gcCount":276,"gcMillis":1259,"allocated":356795714352,"bytesWritten":11506451261,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":77.491334,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":9830736,"bytesRead":46855525,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":68.992125,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":32332603,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":371.469041,"checksum":20109,"gcCount":1,"gcMillis":23,"allocated":164469176,"bytesRead":208933349,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":103.382166,"checksum":280131,"gcCount":1,"gcMillis":4,"allocated":18443544,"bytesRead":61131629,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":435.857917,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":830831320},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":276.53875,"checksum":719693,"gcCount":1,"gcMillis":6,"allocated":736545848,"latency":{"p50":1.333,"p99":19.459,"p99.9":31.167,"max":6301.292}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":8765.17425,"checksum":20000,"gcCount":15,"gcMillis":91,"allocated":19275782704,"bytesWritten":603315874,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":1962.9915,"checksum":5000,"gcCount":3,"gcMillis":14,"allocated":4575113488,"latency":{"p50":272.625,"p99":1246.458,"p99.9":5384.333,"max":8377.375},"bytesWritten":146365552,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10075117,"millis":10000.121292,"checksum":0,"gcCount":23,"gcMillis":176,"allocated":29638746696,"latency":{"p50":2.333,"p99":35.542,"p99.9":858.917,"max":34782.833},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.121292,"checksum":0,"latency":{"p50":6161.042,"p99":209658.958,"p99.9":237605.208,"max":237605.208},"checked":false,"bytesWritten":535203488,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":16221.591541,"checksum":200000,"gcCount":27,"gcMillis":176,"allocated":35662019424,"bytesWritten":1262840788,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":176.634542,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":102009152,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":275.421709,"checksum":100000,"gcCount":1,"gcMillis":10,"allocated":10222312,"bytesRead":65287601,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1028.163541,"checksum":1,"gcCount":1,"gcMillis":12,"allocated":1783529560},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":7511.055333,"checksum":819693,"gcCount":13,"gcMillis":106,"allocated":2491962672,"bytesRead":4431426387,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2406302427,"millis":1000.0,"checksum":2406302427},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":56082.352625,"checksum":80000,"gcCount":85,"gcMillis":804,"allocated":111925493856,"bytesWritten":3339335231,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":10422.682667,"checksum":20000,"gcCount":12,"gcMillis":243,"allocated":15296077344,"bytesWritten":465636463,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":98.48175,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9848632,"bytesRead":48705299,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":2061649543,"millis":1000.0,"checksum":2061649543},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9983.557667,"checksum":0,"gcCount":15,"gcMillis":112,"allocated":19193614832,"latency":{"p50":3810.458,"p99":17737.375,"p99.9":41022.667,"max":41022.667},"checked":false,"bytesWritten":598897553,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":78533,"millis":1570647.540417,"checksum":0,"gcCount":1514,"gcMillis":61477,"allocated":2001405932808,"latency":{"p50":4079.0,"p99":118598.459,"p99.9":444591.292,"max":1643320.834},"checked":false,"bytesWritten":80773865951,"logicalWritten":2513056,"compactionMillis":1569623,"reclaimed":751137008},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1234072654,"millis":1000.0,"checksum":1234072654},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":527.096375,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":340595816,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/results/s4-async/hypergraphdb-3.json b/benchmarks/report/results/s4-async/hypergraphdb-3.json index b8532cd..56748c3 100644 --- a/benchmarks/report/results/s4-async/hypergraphdb-3.json +++ b/benchmarks/report/results/s4-async/hypergraphdb-3.json @@ -1 +1 @@ -{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4169138176,"retainedHeap":305030464,"bytesWritten":106782146000,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":5024.575625,"checksum":200000,"gcCount":5,"gcMillis":106,"allocated":5257543920,"bytesWritten":316023919,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":139094.984666,"checksum":1453427,"gcCount":277,"gcMillis":1192,"allocated":358425474016,"bytesWritten":11587526285,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":99.443583,"checksum":719693,"gcCount":1,"gcMillis":8,"allocated":9805784,"bytesRead":66184417,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":36.820875,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":27447190,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":307.779583,"checksum":20109,"gcCount":0,"gcMillis":0,"allocated":166558888,"bytesRead":200114972,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":101.352375,"checksum":280131,"gcCount":1,"gcMillis":3,"allocated":18444464,"bytesRead":65406449,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":431.065208,"checksum":5757544,"gcCount":0,"gcMillis":0,"allocated":1046213184},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":271.2015,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":721272384,"latency":{"p50":1.375,"p99":18.666,"p99.9":54.417,"max":626.791}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":8045.253625,"checksum":20000,"gcCount":16,"gcMillis":92,"allocated":19600540272,"bytesWritten":612246688,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":1968.186292,"checksum":5000,"gcCount":3,"gcMillis":16,"allocated":4405458272,"latency":{"p50":261.125,"p99":1573.041,"p99.9":6972.333,"max":8054.416},"bytesWritten":144655492,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":10191914,"millis":10000.155167,"checksum":0,"gcCount":25,"gcMillis":213,"allocated":32079015144,"latency":{"p50":2.25,"p99":53.791,"p99.9":782.208,"max":112488.209},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.155167,"checksum":0,"latency":{"p50":5669.5,"p99":54757.25,"p99.9":83585.542,"max":83585.542},"checked":false,"bytesWritten":615489544,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":14302.202,"checksum":200000,"gcCount":27,"gcMillis":158,"allocated":35601961664,"bytesWritten":1263499717,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":124.419625,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":462728,"bytesRead":83219392,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":87.879542,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":9807776,"bytesRead":60364092,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":913.640083,"checksum":1,"gcCount":2,"gcMillis":12,"allocated":2445781312},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":6536.718458,"checksum":819693,"gcCount":12,"gcMillis":88,"allocated":2420011144,"bytesRead":4319194352,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2342039349,"millis":1000.0,"checksum":2342039349},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":43088.326916,"checksum":80000,"gcCount":82,"gcMillis":457,"allocated":107445732936,"bytesWritten":3206428310,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":5599.065542,"checksum":20000,"gcCount":10,"gcMillis":58,"allocated":13513654672,"bytesWritten":411703805,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":87.076334,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":9811896,"bytesRead":58593087,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":2039882038,"millis":1000.0,"checksum":2039882038},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9988.087875,"checksum":0,"gcCount":19,"gcMillis":92,"allocated":24494565800,"latency":{"p50":6399.333,"p99":13578.125,"p99.9":20564.083,"max":20564.083},"checked":false,"bytesWritten":779504093,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":56286,"millis":1125704.643708,"checksum":0,"gcCount":1585,"gcMillis":16343,"allocated":2094168474232,"latency":{"p50":4561.833,"p99":2853102.167,"p99.9":4832843.833,"max":5221838.708},"checked":false,"bytesWritten":85603391546,"logicalWritten":1801152,"compactionMillis":1124686,"reclaimed":743592743},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1225504132,"millis":1000.0,"checksum":1225504132},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":545.431875,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":338776104,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file +{"store":"HyperGraphDB","version":"1.4 @ 99485a1 (Berkeley DB JE 5.0.73)","cache":"Berkeley DB JE cache of 64 MiB plus the HyperGraphDB atom cache","scale":4,"durability":"async","threads":8,"nodes":200000,"edges":400000,"incidences":1453427,"java":"Java HotSpot(TM) 64-Bit Server VM 25.0.4+7-LTS-jvmci-b01","os":"Mac OS X 26.5.1 aarch64","processors":10,"maxHeap":4172808192,"retainedHeap":310427632,"bytesWritten":102654245945,"results":[{"workload":"ingest.nodes","description":"insert nodes in transactions of 1,000","operations":200000,"millis":7488.549875,"checksum":200000,"gcCount":5,"gcMillis":123,"allocated":5261005400,"bytesWritten":315998366,"logicalWritten":3688890},{"workload":"ingest.edges","description":"insert hyperedges (mean cardinality 3.6) in transactions of 1,000","operations":400000,"millis":231836.232667,"checksum":1453427,"gcCount":279,"gcMillis":3441,"allocated":360288635392,"bytesWritten":11762878841,"logicalWritten":14827416},{"workload":"read.incidence","description":"enumerate the incidence set of a node","operations":100000,"millis":182.057916,"checksum":719693,"gcCount":0,"gcMillis":0,"allocated":9938464,"bytesRead":56549561,"logicalRead":5757544},{"workload":"read.members","description":"enumerate the members of a hyperedge","operations":100000,"millis":54.522,"checksum":361142,"gcCount":0,"gcMillis":0,"allocated":4958696,"bytesRead":32897178,"logicalRead":2889136},{"workload":"read.comembership","description":"count hyperedges containing both atoms of a pair","operations":20000,"millis":1158.479166,"checksum":20109,"gcCount":2,"gcMillis":58,"allocated":167660432,"bytesRead":455648974,"logicalRead":160872},{"workload":"read.twohop","description":"count the distinct nodes that share a hyperedge with a node","operations":10000,"millis":213.6865,"checksum":280131,"gcCount":0,"gcMillis":0,"allocated":18449512,"bytesRead":67373708,"logicalRead":2241048},{"workload":"read.incidence.parallel","description":"incidence sets from 8 threads, 100,000 probes each","operations":800000,"millis":548.306958,"checksum":5757544,"gcCount":1,"gcMillis":17,"allocated":586078368},{"workload":"latency.read","description":"one incidence lookup per read transaction","operations":100000,"millis":487.627375,"checksum":719693,"gcCount":1,"gcMillis":15,"allocated":515349416,"latency":{"p50":1.5,"p99":26.708,"p99.9":603.583,"max":18205.375}},{"workload":"write.update","description":"replace a node value in transactions of 1,000","operations":20000,"millis":13943.861375,"checksum":20000,"gcCount":20,"gcMillis":174,"allocated":25900231680,"bytesWritten":820501441,"logicalWritten":640000},{"workload":"latency.commit","description":"one node update per write transaction","operations":5000,"millis":2211.539959,"checksum":5000,"gcCount":3,"gcMillis":24,"allocated":4168570376,"latency":{"p50":259.125,"p99":3178.041,"p99.9":10615.75,"max":14490.291},"bytesWritten":132749010,"logicalWritten":160000},{"workload":"mixed.read","description":"single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s","operations":8559360,"millis":10000.462666,"checksum":0,"gcCount":19,"gcMillis":174,"allocated":25290518992,"latency":{"p50":2.459,"p99":45.209,"p99.9":1073.666,"max":47706.375},"checked":false},{"workload":"mixed.write","description":"updates committed by the writer alongside the readers","operations":10000,"millis":10000.462666,"checksum":0,"latency":{"p50":6442.083,"p99":63126.916,"p99.9":99384.083,"max":99384.083},"checked":false,"bytesWritten":461204952,"logicalWritten":320000},{"workload":"large.ingest","description":"create one hyperedge with 200,000 members","operations":200000,"millis":16721.71525,"checksum":200000,"gcCount":27,"gcMillis":214,"allocated":35346139424,"bytesWritten":1254512032,"logicalWritten":1600008},{"workload":"large.scan","description":"enumerate all members of the large hyperedge (x400, after a warm-up)","operations":80000000,"millis":185.412666,"checksum":80000000,"gcCount":0,"gcMillis":0,"allocated":456328,"bytesRead":97425939,"logicalRead":640000000},{"workload":"large.probe","description":"test whether a node belongs to the large hyperedge","operations":100000,"millis":141.435584,"checksum":100000,"gcCount":0,"gcMillis":0,"allocated":10046384,"bytesRead":77361579,"logicalRead":800000},{"workload":"reopen","description":"close and reopen the database, including recovery","operations":1,"millis":1269.647333,"checksum":1,"gcCount":2,"gcMillis":13,"allocated":2744458344},{"workload":"read.incidence.cold","description":"incidence sets immediately after reopening","operations":100000,"millis":7983.663833,"checksum":819693,"gcCount":12,"gcMillis":138,"allocated":2445187336,"bytesRead":4315470754,"logicalRead":6557544},{"workload":"disk","description":"bytes on disk after a clean shutdown","operations":2319139933,"millis":1000.0,"checksum":2319139933},{"workload":"churn.delete","description":"delete 80,000 hyperedges (20%) in transactions of 1,000","operations":80000,"millis":61163.378917,"checksum":80000,"gcCount":89,"gcMillis":1060,"allocated":116239091360,"bytesWritten":3513481862,"logicalWritten":2970424},{"workload":"churn.remove","description":"remove one member from each of 20,000 other hyperedges, 1,000 per transaction","operations":20000,"millis":11924.960291,"checksum":20000,"gcCount":12,"gcMillis":345,"allocated":15298981224,"bytesWritten":472275606,"logicalWritten":320000},{"workload":"read.incidence.churned","description":"enumerate incidence sets after the deletes","operations":100000,"millis":188.185292,"checksum":665234,"gcCount":0,"gcMillis":0,"allocated":10012840,"bytesRead":88543854,"logicalRead":5321872},{"workload":"disk.churned","description":"bytes on disk after the deletes and a clean shutdown","operations":1970718945,"millis":1000.0,"checksum":1970718945},{"workload":"compaction.baseline","description":"single-update commits at 50/s with no compaction, for comparison","operations":500,"millis":9993.151167,"checksum":0,"gcCount":6,"gcMillis":419,"allocated":7169557776,"latency":{"p50":6690.125,"p99":126903.5,"p99.9":189999.542,"max":189999.542},"checked":false,"bytesWritten":234079729,"logicalWritten":16000},{"workload":"compaction","description":"single-update commits at 50/s while a full compaction runs","operations":54754,"millis":1095068.539791,"checksum":0,"gcCount":1519,"gcMillis":22624,"allocated":2007351654448,"latency":{"p50":5713.166,"p99":64288.041,"p99.9":181833.041,"max":393391.875},"checked":false,"bytesWritten":81434049393,"logicalWritten":1752128,"compactionMillis":1094052,"reclaimed":755082982},{"workload":"disk.compacted","description":"bytes on disk after a full compaction and a clean shutdown","operations":1173354262,"millis":1000.0,"checksum":1173354262},{"workload":"recover","description":"open the store after killing the ingest process with SIGKILL","operations":1,"millis":327.261334,"checksum":1,"gcCount":0,"gcMillis":0,"allocated":336987400,"checked":false,"acknowledged":100000,"lost":0}]} \ No newline at end of file diff --git a/benchmarks/report/sections/abstract.tex b/benchmarks/report/sections/abstract.tex index a870720..39974e3 100644 --- a/benchmarks/report/sections/abstract.tex +++ b/benchmarks/report/sections/abstract.tex @@ -1 +1 @@ -We compare HStore, a persistent hypergraph storage engine built on copy-on-write counted B+trees, with HyperGraphDB~1.4 on Berkeley DB Java Edition. Both systems run 26 storage-level workloads on the same skewed synthetic hypergraph, with up to 1.45 million incidences and equal 64\,MiB caches, in three configurations: two data sizes and two commit modes. Every read result is cross-checked between the systems. At scale 1 with asynchronous commits, HStore enumerates incidence sets \M{ratioAreadincidence} faster, counts shared hyperedges \M{ratioAreadcomembership} faster, applies batched updates \M{ratioAwriteupdate} faster, and needs \M{ratioAdisk} less disk. These leads come from storing incidence sets as small sorted trees, and from commits that log only the keys they changed while a background checkpoint writes pages. When the data is four times larger than the cache, HStore keeps most of these leads but falls to \M{sumCreadmembers} of HyperGraphDB's speed on member enumeration, because its member lookups miss the node cache. With synchronous commits, both systems are bound by the cost of one forced write per commit. We also report latency percentiles, bytes written, heap use, space reclamation and crash recovery, the variation over runs, and a durability setting in HyperGraphDB that, left unchecked, makes its synchronous commits unsynced. +We compare HStore, a persistent hypergraph storage engine built on copy-on-write counted B+trees, with HyperGraphDB~1.4 on Berkeley DB Java Edition. Both systems run 26 storage-level workloads on the same skewed synthetic hypergraph, with up to 1.45 million incidences and equal 64\,MiB caches, in three configurations: two data sizes and two commit modes. Every read result is cross-checked between the systems. At scale 1 with asynchronous commits, HStore enumerates incidence sets \M{ratioAreadincidence} faster, counts shared hyperedges \M{ratioAreadcomembership} faster, applies batched updates \M{ratioAwriteupdate} faster, and needs \M{ratioAdisk} less disk. These leads come from storing incidence sets as small sorted trees, and from commits that log only the keys they changed while a background checkpoint writes pages. At scale 4, where both systems' files outgrow their caches, these leads hold or widen, and HStore enumerates hyperedge members \M{sumCreadmembers} faster, because its cached catalog pages decode only the records that are read. HyperGraphDB recovers from a crash faster at scale 1. With synchronous commits, both systems are bound by the cost of one forced write per commit. We also report latency percentiles, bytes written, heap use, space reclamation and crash recovery, the variation over runs, and a durability setting in HyperGraphDB that, left unchecked, makes its synchronous commits unsynced. diff --git a/benchmarks/report/sections/conclusion.tex b/benchmarks/report/sections/conclusion.tex index 561ea6f..7773851 100644 --- a/benchmarks/report/sections/conclusion.tex +++ b/benchmarks/report/sections/conclusion.tex @@ -1,5 +1,5 @@ \section{Conclusion} -At the storage level, HStore is faster than HyperGraphDB on the operations that hypergraph workloads repeat most: finding the hyperedges that contain a node, intersecting those sets, and updating values. It writes and stores far fewer bytes doing so. Its design gives a clear reason for each of these results, and the same design explains its main weakness. When the catalog does not fit in the node cache, member enumeration falls well behind HyperGraphDB, whose atom cache keeps links as arrays outside any byte budget. Synchronous commits are bound by the device in both systems. Crash recovery lost no acknowledged commit in either system. +At the storage level, HStore is faster than HyperGraphDB on the operations that hypergraph workloads repeat most: finding the hyperedges that contain a node, intersecting those sets, and updating values. It writes and stores far fewer bytes doing so. Its design gives a clear reason for each of these results. Keeping cached pages compact matters as much as the layout on disk: decoding only the catalog records that are read is what lets HStore's catalog stay within a 64\,MiB cache at scale 4. HStore pays for its results with more retained heap, and at scale 1 it opens a crashed store more slowly than HyperGraphDB. Synchronous commits are bound by the device in both systems. Crash recovery lost no acknowledged commit in either system. The numbers come from one laptop under load, and \cref{sec:validity} lists what that limits. The harness, the adapters, the raw results and the script that produced every table in this report are published with HStore, so each result can be checked or repeated on other hardware. diff --git a/benchmarks/report/sections/discussion.tex b/benchmarks/report/sections/discussion.tex index 689e733..0de919e 100644 --- a/benchmarks/report/sections/discussion.tex +++ b/benchmarks/report/sections/discussion.tex @@ -3,16 +3,14 @@ \section{Discussion} \paragraph{Incidence and intersection.} An incidence lookup in HStore reads one entry of the reverse index. For most atoms the whole incidence set is stored inline in that entry, and for hubs it is the root of a small tree. The visitor API hands each hyperedge id to the caller without allocating, which is why \wl{read.incidence} allocates about one byte per lookup. HyperGraphDB opens a JE cursor on a duplicate set, decodes 16-byte handles, and builds a result object per lookup. With a 64\,MiB JE cache, part of each lookup also misses the cache and reads from the log files (\cref{app:full}). Co-membership adds a second effect. HStore intersects two sorted incidence trees and skips every subtree whose key range cannot match, so the cost follows the smaller set. HyperGraphDB plans \texttt{hg.and} as a general conjunctive query over two result sets. The two-hop workload combines both lookups, so it inherits both advantages. -\paragraph{Member enumeration, and where HyperGraphDB leads.} HyperGraphDB keeps a link's targets as a Java array in its atom cache, so enumerating the members of a cached link is an array loop with almost no allocation. HStore reads a hyperedge's members from its record in the catalog tree: from the leaf stored inline in the record for a small edge, or from the edge's own membership tree. At scale 1 the catalog fits in HStore's node cache, and HStore is faster at the median, though the run ranges overlap. At scale 4 it does not. Member lookups then miss the cache and decode catalog pages again, which costs \M{allocChstorereadmembers} of allocation per lookup on average, while HyperGraphDB's atom cache, which the 64\,MiB budget does not limit, still holds the links. This is the clearest weakness this study found in HStore. Two directions would address it: smaller catalog pages, or a cache of decoded edge records separate from the node cache. Both trade memory for this access pattern, and neither is in the version measured here. +\paragraph{Member enumeration.} HyperGraphDB keeps a link's targets as a Java array in its atom cache, so enumerating the members of a cached link is an array loop with almost no allocation. HStore reads a hyperedge's members from its record in the catalog tree: from the leaf stored inline in the record for a small edge, or from the edge's own membership tree. What decides the result at scale 4 is how much heap a cached catalog leaf takes. A leaf holds about 80 records, each with its inline member list. Decoded in full, it takes roughly 11 times the bytes of its page image, and at scale 4 only about half of the catalog then fits in a 64\,MiB node cache. An earlier HStore version (commit \texttt{459b8be}) decoded leaves in full, and its member enumeration at scale 4 ran at 0.13 times HyperGraphDB's rate, with most of the time spent decoding records that the lookup did not need. The version measured here frames each record by its length and decodes a record only when it is read, so a cached leaf costs its page bytes plus the records actually used. The catalog leaves that the lookups reach then stay in the cache, and member lookups read nothing from the files. -\paragraph{Small writes.} A single-update commit in HStore appends a log record of a few hundred bytes and publishes new roots in memory. The tree pages are written later, by a checkpoint that writes each changed node once for all the commits since the previous one. A JE commit appends to its log a new version of each changed record and of the B+tree internal nodes above it, in every JE database the change touches. The difference shows directly in bytes written per update (\cref{fig:writes}). Under asynchronous commits, the per-commit cost that remains in HStore is the commit itself: copying the changed tree paths in memory and publishing a generation. That cost is close to JE's at the median, and the gap opens in the tail. - -\paragraph{Tail latency.} HStore readers pin an immutable generation and never wait for a writer, a checkpoint or a compaction. Writers wait only for the commit lock, which a checkpoint takes just to swap stored references into the head. HyperGraphDB reads run inside JE transactions, which take record locks, and JE's cleaner and checkpointer compete for the same latches and log. In 15 of the 18 latency measurements (six workloads in three configurations), the gap between the two systems is wider at p99 and p99.9 than at the median. The three exceptions are the \wl{mixed} writer under synchronous commits, where HyperGraphDB fell behind its schedule and the gap is largest at the median, and single-update commits and the \wl{mixed} writer at scale 4, where HStore's advantage stays between 1.3 and 2.5 times across percentiles. The single slowest commit at scale 4 was HStore's. +\paragraph{Tail latency.} HStore readers pin an immutable generation and never wait for a writer, a checkpoint or a compaction. Writers wait only for the commit lock, which a checkpoint takes just to swap stored references into the head. HyperGraphDB reads run inside JE transactions, which take record locks, and JE's cleaner and checkpointer compete for the same latches and log. In 16 of the 18 latency measurements (six workloads in three configurations), the gap between the two systems is wider at p99 and p99.9 than at the median. The two exceptions are the \wl{mixed} writer under synchronous commits and at scale 4. There the gap is widest at p99 and smaller at p99.9 than at the median, and since the writer makes 1,000 commits per run, its p99.9 is its slowest commit. \paragraph{Giant hyperedges.} HStore bulk-builds a large membership tree bottom-up in one pass, and a membership probe is one descent of that tree. HyperGraphDB stores the giant link as one record holding 50,000 handles (200,000 at scale 4). A membership probe goes through the node's incidence set instead. \paragraph{Footprint and reclamation.} Several layout choices keep HStore's files small: 8-byte ids stored as varint gaps instead of 16-byte handles, node images packed into 64-byte units, and small member trees and incidence sets stored inline in their parent record. HStore's log is also truncated at each checkpoint. A JE log keeps every superseded version until the cleaner copies the live entries out of a file and deletes it, and at scale 4 cleaning dominated HyperGraphDB's bytes written. HStore's weak point is the period right after a large delete. Pages replaced by the deletes stay on disk while any of the 64 retained generations can reach them, so the files grow first and shrink only after the history moves on, here at the restart that follows. An application that deletes heavily and needs its disk space back quickly would set a smaller \texttt{history\_limit}. -\paragraph{Opening and recovery.} After a clean shutdown, HStore reads one catalog image and finds nothing in the log to replay. HyperGraphDB's open takes longer and allocates far more heap: \M{allocAhgdbreopen} per reopen at scale 1, against \M{allocAhstorereopen} for HStore. After a crash, HStore replays the logged key changes of every commit since its last checkpoint, and JE replays its log from its last checkpoint. HStore's replay cost grows with the number of commits since the last checkpoint, which the write buffer and the checkpoint thresholds bound. The recovery times of the two systems overlap in our runs, so we do not claim a difference. +\paragraph{Opening and recovery.} After a clean shutdown, HStore reads one catalog image and finds nothing in the log to replay. HyperGraphDB's open takes longer and allocates far more heap: \M{allocAhgdbreopen} per reopen at scale 1, against \M{allocAhstorereopen} for HStore. After a crash, HStore replays the logged key changes of every commit since its last checkpoint, and JE replays its log from its last checkpoint. HStore's replay cost grows with the number of commits since the last checkpoint, which the write buffer and the checkpoint thresholds bound. At scale 1 HyperGraphDB opens the crashed store faster, \M{gArecover} against \M{hArecover}. HStore's time there is close to its clean reopen of the full dataset (\M{hAreopen}), and it allocates \M{allocAhstorerecover} while opening, against \M{allocAhgdbrecover} for HyperGraphDB. At scale 4 the order reverses: \M{hCrecover} for HStore against \M{gCrecover}. -\paragraph{What HStore spends.} HStore retains more heap after ingest than HyperGraphDB (\cref{tab:memory}). The retained heap includes nodes not yet written by a checkpoint, up to the 64\,MiB write buffer, and the node cache, whose 64\,MiB budget is an estimate that runs below the true heap size. HStore also allocates slightly more per inserted node at scale 1 and pays more GC time during ingest there, and more GC time during the deletes at scale 4. Its worst single commit at scale 4 was slower than HyperGraphDB's, even though its p99 and p99.9 were lower. +\paragraph{What HStore spends.} HStore retains more heap after ingest than HyperGraphDB (\cref{tab:memory}). The retained heap includes nodes not yet written by a checkpoint, up to the 64\,MiB write buffer, and the node cache, whose 64\,MiB budget counts estimated rather than measured node sizes. At scale 1 it also allocates more per inserted node and hyperedge, opens a crashed store more slowly, and with asynchronous commits its median single-update commit is slightly slower than HyperGraphDB's. Its slowest single commit at scale 4 was slower than HyperGraphDB's, even though its p99 and p99.9 were lower. diff --git a/benchmarks/report/sections/findings.tex b/benchmarks/report/sections/findings.tex index ac11265..3e87a32 100644 --- a/benchmarks/report/sections/findings.tex +++ b/benchmarks/report/sections/findings.tex @@ -1,8 +1,8 @@ \begin{itemize} -\item \textbf{Incidence-based reads} are faster in HStore in every configuration: \M{sumCreadincidence} to \M{sumAreadincidence} for incidence sets, \M{sumCreadcomembership} to \M{sumBreadcomembership} for co-membership counts, and \M{sumCreadincidencecold} to \M{sumAreadincidencecold} directly after a reopen. -\item \textbf{Writes} cost HStore far fewer bytes. Single-update commits write \M{bpoAhstorelatencycommit} against \M{bpoAhgdblatencycommit}, and batched updates run \M{sumBwriteupdate} to \M{sumCwriteupdate} faster. Bulk insertion is closer: between \M{sumBingestedges} and \M{sumCingestedges} for hyperedges. -\item \textbf{Member enumeration} is HStore's weakness once the data outgrows its node cache: \M{sumCreadmembers} at scale 4, and \M{sumCreadtwohop} for two-hop neighbourhoods, which depend on it. -\item \textbf{Tail latency.} HStore's p99 and p99.9 latencies are lower than HyperGraphDB's in every latency workload and configuration, by 1.3 to 69 times. The single slowest commit at scale 4 was HStore's. +\item \textbf{Incidence-based reads} are faster in HStore in every configuration: \M{sumAreadincidence} to \M{sumCreadincidence} for incidence sets, \M{sumAreadcomembership} to \M{sumCreadcomembership} for co-membership counts, and \M{sumBreadincidencecold} to \M{sumCreadincidencecold} directly after a reopen. +\item \textbf{Writes} cost HStore far fewer bytes. Single-update commits write \M{bpoAhstorelatencycommit} against \M{bpoAhgdblatencycommit}, and batched updates run \M{sumAwriteupdate} to \M{sumCwriteupdate} faster. Bulk insertion of hyperedges is close at scale 1 (\M{sumBingestedges} to \M{sumAingestedges}) and \M{sumCingestedges} faster at scale 4. +\item \textbf{Member enumeration} is \M{sumCreadmembers} faster in HStore at scale 4, where HStore's catalog stays in its node cache because cached leaves decode only the records that are read. At scale 1 the ranges overlap. Two-hop neighbourhoods are \M{sumBreadtwohop} to \M{sumAreadtwohop} faster. +\item \textbf{Tail latency.} HStore's p99 and p99.9 latencies are lower than HyperGraphDB's in every latency workload and configuration, by 1.1 to 35 times. \item \textbf{Disk.} HStore's files are \M{sumBdisk} to \M{sumCdisk} smaller after loading. After deletes they first grow while retained history still references the replaced pages. -\item \textbf{Recovery.} Neither system lost an acknowledged commit after SIGKILL. Recovery times overlap. +\item \textbf{Recovery.} Neither system lost an acknowledged commit after SIGKILL. HyperGraphDB opens a crashed store faster at scale 1, and HStore at scale 4. \end{itemize} diff --git a/benchmarks/report/sections/method.tex b/benchmarks/report/sections/method.tex index 669f91b..8a08b62 100644 --- a/benchmarks/report/sections/method.tex +++ b/benchmarks/report/sections/method.tex @@ -95,6 +95,6 @@ \subsection{Environment and statistics} \begin{itemize} \item \textbf{S1 async}: scale 1, asynchronous commits, \M{runsA} runs per system; \item \textbf{S1 sync}: scale 1, synchronous commits, \M{runsB} runs per system; -\item \textbf{S4 async}: scale 4, asynchronous commits, \M{runsC} runs per system. With 64\,MiB caches, the working set is several times larger than either cache. +\item \textbf{S4 async}: scale 4, asynchronous commits, \M{runsC} runs per system. With 64\,MiB caches, the files of both systems are larger than their caches. \end{itemize} Each table reports the median over runs. Ratios are taken between medians and oriented so that values above 1 favour HStore: throughput divides HStore by HyperGraphDB, while times, latencies and sizes divide HyperGraphDB by HStore. Where the ranges of the two systems' runs overlap, the ratio is marked with $\dagger$, and the difference should not be read as significant. \Cref{app:variability} gives every run and the coefficient of variation of each workload. diff --git a/benchmarks/report/sections/results-configs.tex b/benchmarks/report/sections/results-configs.tex index e963bcf..77d4dd5 100644 --- a/benchmarks/report/sections/results-configs.tex +++ b/benchmarks/report/sections/results-configs.tex @@ -1,12 +1,12 @@ \subsection{Larger data: scale 4} \label{sec:scale4} -At scale 4 the dataset has \M{incidencesC} incidences, and the files of both systems are several times larger than their 64\,MiB caches. Most of HStore's leads hold or grow. Hyperedge insertion becomes \M{sumCingestedges} faster (\M{hCingestedges} against \M{gCingestedges} per second), batched updates \M{sumCwriteupdate} and single-update commits \M{sumClatencycommit}, the last with overlapping ranges. Co-membership counts are \M{sumCreadcomembership} faster, incidence lookups \M{sumCreadincidence} (ranges overlapping), and cold lookups after a reopen \M{sumCreadincidencecold}. +At scale 4 the dataset has \M{incidencesC} incidences. After loading, HStore's files take \M{hCdisk} and HyperGraphDB's \M{gCdisk}, so both are larger than their 64\,MiB caches, HyperGraphDB's by a much wider margin. HStore's leads hold or grow. Hyperedge insertion becomes \M{sumCingestedges} faster (\M{hCingestedges} against \M{gCingestedges} per second), batched updates \M{sumCwriteupdate} and single-update commits \M{sumClatencycommit}. Co-membership counts are \M{sumCreadcomembership} faster, incidence lookups \M{sumCreadincidence}, and cold lookups after a reopen \M{sumCreadincidencecold}. -Two read workloads reverse. HStore enumerates the members of \M{hCreadmembers} hyperedges per second against HyperGraphDB's \M{gCreadmembers}, a ratio of \M{sumCreadmembers}. Two-hop neighbourhoods, which enumerate the members of every incident hyperedge, follow at \M{sumCreadtwohop}. The resource counters show why. At scale 4 HStore's member lookups miss its node cache: each one reads about 778 bytes from the files and allocates \M{allocChstorereadmembers} while decoding the catalog page that holds the edge record, and a two-hop query allocates \M{allocChstorereadtwohop}. HyperGraphDB serves the same lookups from its atom cache, which holds each link's targets as a Java array and is not limited by the 64\,MiB budget: it allocates \M{allocChgdbreadmembers} per member lookup. \Cref{sec:discussion} returns to this. +Member enumeration is \M{sumCreadmembers} faster: \M{hCreadmembers} hyperedges per second against HyperGraphDB's \M{gCreadmembers}. Two-hop neighbourhoods, which enumerate the members of every incident hyperedge, are \M{sumCreadtwohop} faster. During \wl{read.members}, HStore read nothing from its files and allocated \M{allocChstorereadmembers} per lookup, so every catalog leaf the probes reached was already in its node cache. A cached catalog leaf holds its page bytes and only the records that have been read (\cref{sec:systems}), which keeps the catalog within the 64\,MiB budget. HyperGraphDB serves the same lookups from its atom cache, which holds each link's targets as a Java array and is not limited by the 64\,MiB budget. It allocates \M{allocChgdbreadmembers} per member lookup. A two-hop query allocates \M{allocChstorereadtwohop} in HStore and \M{allocChgdbreadtwohop} in HyperGraphDB. -On disk, HyperGraphDB's files after loading are \M{gCdisk} against HStore's \M{hCdisk}. After the deletes, HStore's files grow to \M{hCdiskchurned}. The deletes rewrite a large part of the incidence index, and the replaced pages remain reachable from the 64 retained generations until the next restart releases that history. The benchmark restarts the store after the deletes, and during the 10\,s \wl{compaction.baseline} window that follows, HStore's background maintenance released and compacted that space. When the measured compaction window began, the explicit compaction took \M{compmsChstore} and found almost nothing left to reclaim, and the final size was \M{hCdiskcompacted}. JE's cleaner ran for \M{compmsChgdb} in the same window and brought HyperGraphDB from \M{gCdiskchurned} to \M{gCdiskcompacted}. +After the deletes, HStore's files grow to \M{hCdiskchurned}. The deletes rewrite a large part of the incidence index, and the replaced pages remain reachable from the 64 retained generations until the next restart releases that history. The benchmark restarts the store after the deletes, and during the 10\,s \wl{compaction.baseline} window that follows, HStore's background maintenance released and compacted that space. When the measured compaction window began, the explicit compaction took \M{compmsChstore} and found almost nothing left to reclaim, and the final size was \M{hCdiskcompacted}. JE's cleaner ran for \M{compmsChgdb} in the same window and brought HyperGraphDB from \M{gCdiskchurned} to \M{gCdiskcompacted}. -A clean reopen is \M{sumCreopen} faster in HStore, and opening after a crash takes \M{hCrecover} against \M{gCrecover}. Over the whole run, HStore wrote \M{totalwrittenChstore} and HyperGraphDB \M{totalwrittenChgdb}, most of it during cleaning. HStore's single-update commits have a lower p99 (\M{latChstorelatencycommitptwo} against \M{latChgdblatencycommitptwo}) and p99.9, but a higher maximum: \M{latChstorelatencycommitmax} against \M{latChgdblatencycommitmax}. +A clean reopen is \M{sumCreopen} faster in HStore, and opening after a crash takes \M{hCrecover} against \M{gCrecover}. Over the whole run, HStore wrote \M{totalwrittenChstore} and HyperGraphDB \M{totalwrittenChgdb}, most of it during cleaning. HStore's single-update commits are faster at every percentile: \M{latChstorelatencycommitptwo} against \M{latChgdblatencycommitptwo} at p99, and \M{latChstorelatencycommitpthree} against \M{latChgdblatencycommitpthree} at p99.9. The median of the per-run maxima is also lower in HStore, \M{latChstorelatencycommitmax} against \M{latChgdblatencycommitmax}, but the slowest single commit across all runs was HStore's: \M{latChstorelatencycommitworst}, against \M{latChgdblatencycommitworst} for HyperGraphDB. \input{sections/results-sync} diff --git a/benchmarks/report/sections/results-sync.tex b/benchmarks/report/sections/results-sync.tex index 3a503f8..fe23efd 100644 --- a/benchmarks/report/sections/results-sync.tex +++ b/benchmarks/report/sections/results-sync.tex @@ -3,8 +3,8 @@ \subsection{Synchronous commits} With synchronous commits, every acknowledged commit has been forced to stable storage. On this machine a forced write costs about 4\,ms, and it dominates single-update transactions in both systems. HStore commits \M{hBlatencycommit} of them per second and HyperGraphDB \M{gBlatencycommit} (\M{sumBlatencycommit}). The medians are \M{latBhstorelatencycommitmed} and \M{latBhgdblatencycommitmed}, one forced write each, and HStore's lead comes from the tail: \M{latBhstorelatencycommitpthree} against \M{latBhgdblatencycommitpthree} at p99.9. -Batched work spreads the forced write over 1,000 operations. HStore inserts nodes \M{sumBingestnodes} as fast as HyperGraphDB and hyperedges \M{sumBingestedges} as fast (ranges overlapping), and updates values \M{sumBwriteupdate} as fast. The open-loop writer of \wl{mixed} commits 10 updates every 10\,ms. HStore held that schedule, with a median commit latency of \M{latBhstoremixedwritemed}. HyperGraphDB fell behind it: its commits waited \M{latBhgdbmixedwritemed} at the median, and its slowest run reached \M{mixminBhgdb} updates per second. +Batched work spreads the forced write over 1,000 operations. HStore inserts nodes \M{sumBingestnodes} as fast as HyperGraphDB and hyperedges \M{sumBingestedges} as fast (ranges overlapping), and updates values \M{sumBwriteupdate} as fast. The open-loop writer of \wl{mixed} commits 10 updates every 10\,ms. Both systems held that schedule in every run. Measured from their scheduled start, HStore's commits took \M{latBhstoremixedwritemed} at the median and \M{latBhstoremixedwriteptwo} at p99, and HyperGraphDB's \M{latBhgdbmixedwritemed} and \M{latBhgdbmixedwriteptwo}. -Under synchronous commits, JE reports about 210\,KiB written per single-update commit, against about 9\,KiB with asynchronous commits. The extra bytes come with the log flush that the adapter adds (\cref{sec:durability}). We have not established what JE writes in that flush, so the bytes-written figures for synchronous single commits in \cref{app:full} should not be read as a comparison. +Under synchronous commits, JE reports \M{bpoBhgdblatencycommit} written per single-update commit, against \M{bpoAhgdblatencycommit} with asynchronous commits. The extra bytes come with the log flush that the adapter adds (\cref{sec:durability}). We have not established what JE writes in that flush, so the bytes-written figures for synchronous single commits in \cref{app:full} should not be read as a comparison. -Reads do not commit, and HStore leads every read workload in this configuration as well (\cref{tab:summary}). Recovery after SIGKILL takes \M{hBrecover} in HStore and \M{gBrecover} in HyperGraphDB at the median. The ranges overlap, and one HStore run took 1.6\,s. As in every other configuration, no run of either system lost an acknowledged commit. +Reads do not commit, and HStore leads every read workload in this configuration as well at the median, with overlapping ranges for member enumeration and giant-edge scans (\cref{tab:summary}). Recovery after SIGKILL takes \M{hBrecover} in HStore and \M{gBrecover} in HyperGraphDB at the median, and the ranges do not overlap. As in every other configuration, no run of either system lost an acknowledged commit. diff --git a/benchmarks/report/sections/results-text.tex b/benchmarks/report/sections/results-text.tex index a54e01f..cdb8463 100644 --- a/benchmarks/report/sections/results-text.tex +++ b/benchmarks/report/sections/results-text.tex @@ -1,12 +1,12 @@ \subsection{Loading, updating and deleting} -At scale 1 with asynchronous commits, HStore inserts nodes \M{ratioAingestnodes} and hyperedges \M{ratioAingestedges} as fast as HyperGraphDB. Both ratios carry the overlap mark: the slowest HStore runs were slower than the fastest HyperGraphDB runs, so bulk insertion should be read as roughly even with a lead for HStore. Updates differ more. Replacing node values 1,000 at a time runs \M{ratioAwriteupdate} faster in HStore (\M{hAwriteupdate} against \M{gAwriteupdate} updates per second). Deleting hyperedges is \M{ratioAchurndelete} faster and removing single members \M{ratioAchurnremove} faster, both with overlapping ranges. Building one hyperedge over all 50,000 nodes takes HStore a single bulk load, \M{ratioAlargeingest} faster than HyperGraphDB. +At scale 1 with asynchronous commits, HStore inserts nodes \M{ratioAingestnodes} and hyperedges \M{ratioAingestedges} as fast as HyperGraphDB. Updates differ more. Replacing node values 1,000 at a time runs \M{ratioAwriteupdate} faster in HStore (\M{hAwriteupdate} against \M{gAwriteupdate} updates per second). Deleting hyperedges is \M{ratioAchurndelete} faster and removing single members \M{ratioAchurnremove} faster. Building one hyperedge over all 50,000 nodes takes HStore a single bulk load, \M{ratioAlargeingest} faster than HyperGraphDB. -Single-update transactions show the smallest write difference: \M{hAlatencycommit} against \M{gAlatencycommit} commits per second (\M{ratioAlatencycommit}, ranges overlapping). With asynchronous commits, neither system waits for the disk, and the cost of one commit is dominated by work in memory. \Cref{sec:latency} shows that the two medians are close and that the difference lies in the tail. +Single-update transactions are the one write workload where HStore does not lead: \M{hAlatencycommit} against \M{gAlatencycommit} commits per second (\M{ratioAlatencycommit}, ranges overlapping). With asynchronous commits, neither system waits for the disk, and the cost of one commit is dominated by work in memory. \Cref{sec:latency} shows that the two medians are close and that the difference lies in the tail. \subsection{Reads} -HStore's largest margins are on reads that go through incidence sets. It enumerates a node's incidence set \M{ratioAreadincidence} faster than HyperGraphDB, counts the hyperedges shared by two nodes \M{ratioAreadcomembership} faster, and computes two-hop neighbourhoods \M{ratioAreadtwohop} faster. With 8 threads, incidence lookups reach \M{hAreadincidenceparallel} per second in HStore against \M{gAreadincidenceparallel} (\M{ratioAreadincidenceparallel}). When every lookup is its own read transaction (\wl{latency.read}), the ratio is \M{ratioAlatencyread}. Enumerating the members of a hyperedge, the read where HyperGraphDB's atom cache helps most, is \M{ratioAreadmembers} faster in HStore at the median, with overlapping ranges. +HStore's largest margins are on reads that go through incidence sets. It enumerates a node's incidence set \M{ratioAreadincidence} faster than HyperGraphDB, counts the hyperedges shared by two nodes \M{ratioAreadcomembership} faster, and computes two-hop neighbourhoods \M{ratioAreadtwohop} faster. With 8 threads, incidence lookups reach \M{hAreadincidenceparallel} per second in HStore against \M{gAreadincidenceparallel} (\M{ratioAreadincidenceparallel}). When every lookup is its own read transaction (\wl{latency.read}), the ratio is \M{ratioAlatencyread}. Enumerating the members of a hyperedge, the read where HyperGraphDB's atom cache helps most, is \M{ratioAreadmembers} faster in HStore at the median, with overlapping ranges: one of the five HStore runs was slower than HyperGraphDB's median. Two read workloads run under special conditions. Directly after a reopen, before any warm-up, HStore serves incidence lookups at \M{hAreadincidencecold} per second against \M{gAreadincidencecold} (\M{ratioAreadincidencecold}). HStore read \M{coldreadAhstore} per lookup from its files in that phase, against \M{coldreadAhgdb} for JE. After the deletes, incidence lookups are \M{ratioAreadincidencechurned} faster. @@ -31,7 +31,7 @@ \subsection{Latency} \input{gen/latency-s1-async} \end{table*} -\Cref{fig:latency} and \cref{tab:latency} give the latency distributions. For single-update commits with asynchronous commits, the medians are close: \M{latAhstorelatencycommitmed} for HStore and \M{latAhgdblatencycommitmed} for HyperGraphDB. The tails are not. At p99, HStore needs \M{latAhstorelatencycommitptwo} and HyperGraphDB \M{latAhgdblatencycommitptwo}. At p99.9 the figures are \M{latAhstorelatencycommitpthree} and \M{latAhgdblatencycommitpthree}. Read transactions take \M{latAhstorelatencyreadmed} at the median in HStore and \M{latAhgdblatencyreadmed} in HyperGraphDB, and \M{latAhstorelatencyreadpthree} against \M{latAhgdblatencyreadpthree} at p99.9. +\Cref{fig:latency} and \cref{tab:latency} give the latency distributions. For single-update commits with asynchronous commits, the medians are close, with HyperGraphDB slightly ahead: \M{latAhstorelatencycommitmed} for HStore and \M{latAhgdblatencycommitmed} for HyperGraphDB. The tails favour HStore. At p99, HStore needs \M{latAhstorelatencycommitptwo} and HyperGraphDB \M{latAhgdblatencycommitptwo}. At p99.9 the figures are \M{latAhstorelatencycommitpthree} and \M{latAhgdblatencycommitpthree}. Read transactions take \M{latAhstorelatencyreadmed} at the median in HStore and \M{latAhgdblatencyreadmed} in HyperGraphDB, and \M{latAhstorelatencyreadpthree} against \M{latAhgdblatencyreadpthree} at p99.9. \paragraph{Reads beside a writer.} In \wl{mixed}, the writer's median rate was 1,000 updates per second in both systems; its slowest run reached \M{mixminAhstore} updates per second in HStore and \M{mixminAhgdb} in HyperGraphDB. Meanwhile the 7 readers completed \M{hAmixedread} lookups per second in HStore and \M{gAmixedread} in HyperGraphDB (\M{ratioAmixedread}). Reader latency at p99.9 was \M{latAhstoremixedreadpthree} against \M{latAhgdbmixedreadpthree}. The writer's commits, measured from their scheduled start, took \M{latAhstoremixedwriteptwo} against \M{latAhgdbmixedwriteptwo} at p99. @@ -50,7 +50,7 @@ \subsection{Disk footprint, deletes and reclamation} \subsection{Reopen and recovery} -A clean close and reopen takes \M{hAreopen} in HStore and \M{gAreopen} in HyperGraphDB (\M{ratioAreopen}). After the ingest process is killed with SIGKILL, opening the store takes \M{hArecover} and \M{gArecover} respectively. The ranges overlap, so we read recovery time as even. In every run of both systems, every node acknowledged before the kill was present after recovery. +A clean close and reopen takes \M{hAreopen} in HStore and \M{gAreopen} in HyperGraphDB (\M{ratioAreopen}). After the ingest process is killed with SIGKILL, opening the store takes \M{hArecover} and \M{gArecover} respectively, and the ranges do not overlap. HyperGraphDB opens a crashed store faster at scale 1. In every run of both systems, every node acknowledged before the kill was present after recovery. \subsection{Resources} \label{sec:resources} @@ -71,6 +71,6 @@ \subsection{Resources} HStore writes fewer bytes per operation than HyperGraphDB in every write workload of every configuration (\cref{fig:writes} and \cref{app:full}). A single-update commit writes \M{bpoAhstorelatencycommit} in HStore and \M{bpoAhgdblatencycommit} in HyperGraphDB. A batched update writes \M{bpoAhstorewriteupdate} against \M{bpoAhgdbwriteupdate}. Over a whole run at scale 1, HStore wrote \M{totalwrittenAhstore} and HyperGraphDB \M{totalwrittenAhgdb}. -\Cref{tab:memory} summarises memory and reclamation. HStore retained \M{retainedAhstore} of heap after ingest at scale 1, against \M{retainedAhgdb} for HyperGraphDB. Per-workload allocation and GC time are in \cref{app:full}. At scale 1, HStore allocates less per operation in every read workload and slightly more per inserted node and hyperedge, and it allocates more while recovering. At scale 4 it allocates more per member lookup and per two-hop query, the cache misses discussed in \cref{sec:scale4}. +\Cref{tab:memory} summarises memory and reclamation. HStore retained \M{retainedAhstore} of heap after ingest at scale 1, against \M{retainedAhgdb} for HyperGraphDB. Per-workload allocation and GC time are in \cref{app:full}. HStore allocates less per operation than HyperGraphDB in every read workload of every configuration. At scale 1 it allocates more per inserted node and hyperedge, and more while recovering after a crash. At scale 4 it allocates less in those workloads as well. \input{sections/results-configs} diff --git a/benchmarks/report/sections/results.tex b/benchmarks/report/sections/results.tex index 3467295..1f9064a 100644 --- a/benchmarks/report/sections/results.tex +++ b/benchmarks/report/sections/results.tex @@ -4,11 +4,11 @@ \section{Results} \begin{figure*}[t] \centering \input{gen/fig-ratios} -\caption{HStore's advantage over HyperGraphDB for every workload and configuration (log scale). Bars above the dashed line favour HStore. Throughput workloads divide HStore by HyperGraphDB; \wl{reopen}, \wl{recover} and the disk sizes divide HyperGraphDB by HStore.} +\caption{Ratio of HStore to HyperGraphDB for every workload and configuration (log scale). Bars above the dashed line favour HStore. Throughput workloads divide HStore by HyperGraphDB; \wl{reopen}, \wl{recover} and the disk sizes divide HyperGraphDB by HStore.} \label{fig:ratios} \end{figure*} -\Cref{fig:ratios} shows HStore's advantage on every workload in all three configurations, and \cref{tab:summary} gives the same ratios with the run-range test from \cref{sec:environment}. \Cref{tab:main} gives the absolute numbers for the S1 async configuration. The other configurations are tabulated in \cref{app:full}. +\Cref{fig:ratios} shows the ratio for every workload in all three configurations. HStore leads in all but three of them: single-update commits in S1 async, where the ranges overlap, and opening the store after a crash at scale 1, in both commit modes. \Cref{tab:summary} gives the same ratios with the run-range test from \cref{sec:environment}. \Cref{tab:main} gives the absolute numbers for the S1 async configuration. The other configurations are tabulated in \cref{app:full}. \begin{table}[t] \centering\footnotesize diff --git a/benchmarks/report/sections/systems.tex b/benchmarks/report/sections/systems.tex index f33774c..2a923e1 100644 --- a/benchmarks/report/sections/systems.tex +++ b/benchmarks/report/sections/systems.tex @@ -15,7 +15,7 @@ \subsection{HStore} \end{itemize} Enumerating an incidence set is a cursor walk over one inline list or one small tree. Counting the hyperedges that contain two given atoms is a leapfrog intersection~\cite{leapfrog} of their two incidence trees. The intersection skips whole subtrees whose key range cannot contain a match. -\paragraph{Pages and files.} A tree node is written as a self-describing image: an 80-byte header with a CRC32C checksum, followed by the payload. Images are packed into append-only segment files of 256\,MiB, aligned to 64-byte units. A page directory maps each page number to the image's current address, so compaction can move an image without rewriting the trees that refer to it. Reads go through a memory map of each segment. Decoded nodes are kept in a node cache whose budget is set in bytes. +\paragraph{Pages and files.} A tree node is written as a self-describing image: an 80-byte header with a CRC32C checksum, followed by the payload. Images are packed into append-only segment files of 256\,MiB, aligned to 64-byte units. A page directory maps each page number to the image's current address, so compaction can move an image without rewriting the trees that refer to it. Reads go through a memory map of each segment. Decoded nodes are kept in a node cache whose budget is set in bytes. In catalog and incidence index leaves, each record is preceded by its length. A leaf read from a page keeps the page bytes and the offset of every record, and decodes a record the first time it is read. A decoded record is then kept in the leaf and charged to the node cache, so a lookup that misses the cache decodes only the record it needs. \paragraph{Generations and readers.} A committed state is a \emph{generation}: an immutable vector of tree roots. A reader pins a generation and reads it without locks. Writers never modify a node that a published generation can reach, as in other copy-on-write B+tree stores~\cite{lmdb}. The last 64 generations are retained for time travel. @@ -80,7 +80,7 @@ \subsection{Versions and configuration} \toprule & \hstore & \hgdb \\ \midrule -Version & 0.1.0, commit \texttt{459b8be}, storage format 7 & 1.4, commit \texttt{99485a1}; JE 5.0.73 \\ +Version & 0.1.0, commit \texttt{9291e18}, storage format 8 & 1.4, commit \texttt{99485a1}; JE 5.0.73 \\ Cache & node cache, 64\,MiB & JE cache 64\,MiB, plus atom cache \\ Async commits & no fsync, ordered publication & JE \texttt{WRITE\_NO\_SYNC} (default) \\ Sync commits & fsync per commit group & JE log forced after each commit (\cref{sec:durability}) \\ diff --git a/benchmarks/report/sections/validity.tex b/benchmarks/report/sections/validity.tex index 67feacc..4b13114 100644 --- a/benchmarks/report/sections/validity.tex +++ b/benchmarks/report/sections/validity.tex @@ -7,7 +7,7 @@ \section{Threats to validity} \paragraph{Memory fits in RAM.} At both scales the files fit in the operating system's page cache. Reads that miss a system's own 64\,MiB cache are served from memory, not from the SSD. The cold-read numbers therefore measure decoding and cache admission, not device latency. -\paragraph{Cache budgets.} HStore counts its 64\,MiB node cache against an estimate of the heap each decoded node takes, which runs 10 to 40\% below the true size. JE's 64\,MiB budget is exact, but HyperGraphDB's atom and incidence caches are not capped in bytes. Neither side therefore holds exactly 64\,MiB. The retained heap in \cref{tab:memory} shows what each kept after ingest. +\paragraph{Cache budgets.} HStore counts its 64\,MiB node cache against an estimate of the heap each cached node takes, not a measured size. JE's 64\,MiB budget is exact, but HyperGraphDB's atom and incidence caches are not capped in bytes. Neither side therefore holds exactly 64\,MiB. The retained heap in \cref{tab:memory} shows what each kept after ingest. \paragraph{Workload coverage.} The dataset is synthetic. It uses set hyperedges without roles, properties or ordered members, and node values of a few bytes. Queries are single storage operations, not HQL or HyperGraphDB queries, so query planning is not measured. Workloads run in a fixed order within one process, so each one starts from the state the previous one left: cache contents, garbage, and for HStore, unwritten nodes waiting for a checkpoint. diff --git a/docs/benchmark-report.pdf b/docs/benchmark-report.pdf index 10d6ae0c8606d0f6593d0fb7c9761a2907fbbb9f..5221467f0c0b4de2c2c154015b693bf60e37749c 100644 GIT binary patch delta 108618 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 From 2434396f149a7ec94ff978b59ae501cb0ecdc04c Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Tue, 13 Oct 2026 06:42:38 +0530 Subject: [PATCH 14/15] docs(bench): publish results with leaf records decoded on demand The results tables come from the same runs as the report. The analysis now reflects scale 4, where HStore leads on member enumeration, and scale 1, where HyperGraphDB recovers from a crash faster. A new section compares allocation and bytes read per lookup before and after the change. --- docs/benchmarks.md | 283 +++++++++++++++++++++++++-------------------- 1 file changed, 156 insertions(+), 127 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 0bcc672..e95645a 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -166,10 +166,10 @@ configured (see *Setup*). ## Results -These results come from HStore at commit `459b8be`, on the machine described in *Setup*, with both caches set to +These results come from HStore at commit `9291e18`, on the machine described in *Setup*, with both caches set to 64 MiB (`CACHE_MB=64`) and `THREADS=8`. Scale 1 ran 5 times per store in each durability mode, and scale 4 ran 3 times. Each cell is the median, with the slowest and fastest run in brackets. The machine was running other -work: sampled before each run, the one-minute load average was between 4.4 and 16.7 on 10 cores. Where the +work: sampled before each run, the one-minute load average was between 3.4 and 18.3 on 10 cores. Where the bracketed ranges of the two stores overlap, treat the ratio as a tendency rather than a measured difference. The [benchmark report](benchmark-report.pdf) has the full method, every table for all three configurations @@ -185,59 +185,59 @@ Dataset: 50,000 nodes, 100,000 hyperedges, 362,757 incidences; durability async; | Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes`: insert nodes in transactions of 1,000 | 45,124 ops/s (32,919 to 66,106) | 24,930 ops/s (11,229 to 33,032) | **1.81×** | yes | -| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 28,198 ops/s (22,543 to 37,283) | 22,500 ops/s (11,053 to 35,992) | **1.25×** | yes | -| `read.incidence`: enumerate the incidence set of a node | 7,472,143 ops/s (3,997,182 to 7,630,311) | 739,484 ops/s (221,944 to 927,800) | **10.10×** | yes | -| `read.members`: enumerate the members of a hyperedge | 3,694,075 ops/s (1,529,065 to 5,202,587) | 1,702,172 ops/s (482,769 to 2,220,779) | **2.17×** | yes | -| `read.comembership`: count hyperedges containing both atoms of a pair | 447,789 ops/s (127,231 to 651,937) | 32,538 ops/s (18,486 to 54,813) | **13.76×** | yes | -| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 301,232 ops/s (240,766 to 347,622) | 74,579 ops/s (18,598 to 130,603) | **4.04×** | yes | -| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 24,439,172 ops/s (17,134,244 to 34,798,935) | 1,691,919 ops/s (637,312 to 2,175,513) | **14.44×** | yes | -| `latency.read`: one incidence lookup per read transaction | 2,809,235 ops/s (1,970,735 to 3,462,394) | 269,377 ops/s (106,860 to 337,642) | **10.43×** | yes | -| `write.update`: replace a node value in transactions of 1,000 | 78,091 ops/s (53,208 to 82,882) | 5,111 ops/s (2,905 to 8,739) | **15.28×** | yes | -| `latency.commit`: one node update per write transaction | 7,865 ops/s (5,340 to 13,622) | 5,798 ops/s (4,560 to 9,591) | **1.36×** | yes | -| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,872,082 ops/s (1,782,768 to 2,529,519) | 761,958 ops/s (316,836 to 1,021,256) | **2.46×** | n/a | -| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (996 to 1,000) | 1,000 ops/s (795 to 1,000) | **1.00×** | n/a | -| `large.ingest`: create one hyperedge with 50,000 members | 439,642 ops/s (261,929 to 480,878) | 35,654 ops/s (13,771 to 54,078) | **12.33×** | yes | -| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 2,259,599,768 ops/s (1,700,065,878 to 3,729,893,079) | 610,672,274 ops/s (308,735,077 to 1,170,263,579) | **3.70×** | yes | -| `large.probe`: test whether a node belongs to the large hyperedge | 15,623,882 ops/s (2,535,875 to 19,290,897) | 771,891 ops/s (251,952 to 1,073,301) | **20.24×** | yes | -| `reopen`: close and reopen the database, including recovery | 121 ms (116 to 373) | 1,369 ms (940 to 2,088) | **11.31×** | yes | -| `read.incidence.cold`: incidence sets immediately after reopening | 2,761,318 ops/s (277,983 to 3,026,066) | 41,898 ops/s (15,486 to 55,249) | **65.91×** | yes | -| `disk`: bytes on disk after a clean shutdown | 91.2 MiB (91.1 to 93.2) | 432.2 MiB (374.2 to 535.2) | **4.74×** | yes | -| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 16,291 ops/s (6,055 to 20,524) | 2,986 ops/s (1,565 to 6,388) | **5.46×** | yes | -| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 24,365 ops/s (14,469 to 33,208) | 8,119 ops/s (5,676 to 15,599) | **3.00×** | yes | -| `read.incidence.churned`: enumerate incidence sets after the deletes | 6,888,001 ops/s (2,626,571 to 7,491,198) | 1,107,018 ops/s (785,085 to 2,403,497) | **6.22×** | yes | -| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 119.8 MiB (115.9 to 129.7) | 293.3 MiB (261.2 to 348.9) | **2.45×** | yes | -| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 90.8 MiB (90.8 to 90.8) | 273.5 MiB (204.0 to 290.7) | **3.01×** | yes | -| `recover`: open the store after killing the ingest process with SIGKILL | 128 ms (107 to 356) | 132 ms (82 to 157) | **1.03×** | n/a | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 67,741 ops/s (51,067 to 72,960) | 37,664 ops/s (32,804 to 40,002) | **1.80×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 50,042 ops/s (42,339 to 54,792) | 34,971 ops/s (33,281 to 39,454) | **1.43×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 6,263,882 ops/s (3,838,956 to 8,193,140) | 1,489,214 ops/s (1,399,430 to 1,585,233) | **4.21×** | yes | +| `read.members`: enumerate the members of a hyperedge | 6,114,743 ops/s (2,146,691 to 6,408,134) | 2,663,119 ops/s (1,620,982 to 3,486,067) | **2.30×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 610,291 ops/s (560,312 to 788,357) | 53,899 ops/s (2,236 to 76,650) | **11.32×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 543,317 ops/s (335,820 to 574,014) | 98,274 ops/s (71,007 to 148,759) | **5.53×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 36,557,085 ops/s (20,950,132 to 40,593,168) | 2,246,677 ops/s (1,947,200 to 2,675,350) | **16.27×** | yes | +| `latency.read`: one incidence lookup per read transaction | 3,833,633 ops/s (2,414,108 to 4,720,358) | 456,998 ops/s (372,257 to 558,321) | **8.39×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 69,879 ops/s (50,256 to 108,002) | 12,986 ops/s (10,565 to 19,117) | **5.38×** | yes | +| `latency.commit`: one node update per write transaction | 12,387 ops/s (10,410 to 14,893) | 14,088 ops/s (7,229 to 24,612) | **0.88×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 2,457,466 ops/s (2,368,477 to 2,542,025) | 1,131,996 ops/s (1,059,891 to 1,199,589) | **2.17×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (1,000 to 1,000) | 1,000 ops/s (999 to 1,000) | **1.00×** | n/a | +| `large.ingest`: create one hyperedge with 50,000 members | 610,120 ops/s (286,715 to 755,104) | 116,290 ops/s (83,926 to 136,066) | **5.25×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 4,048,958,385 ops/s (3,507,028,260 to 4,912,948,690) | 1,122,938,397 ops/s (590,299,171 to 1,296,824,104) | **3.61×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 17,214,296 ops/s (9,625,064 to 19,639,451) | 1,824,713 ops/s (865,555 to 2,056,360) | **9.43×** | yes | +| `reopen`: close and reopen the database, including recovery | 111 ms (107 to 131) | 924 ms (674 to 1,361) | **8.29×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 4,044,844 ops/s (3,458,866 to 4,142,345) | 95,895 ops/s (89,040 to 136,966) | **42.18×** | yes | +| `disk`: bytes on disk after a clean shutdown | 92.4 MiB (90.4 to 92.6) | 346.4 MiB (289.6 to 371.8) | **3.75×** | yes | +| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 24,490 ops/s (20,169 to 29,917) | 7,904 ops/s (5,925 to 9,130) | **3.10×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 43,155 ops/s (39,001 to 49,663) | 17,477 ops/s (16,078 to 19,754) | **2.47×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 8,398,538 ops/s (7,852,427 to 9,263,154) | 2,104,833 ops/s (932,051 to 2,516,987) | **3.99×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 117.1 MiB (114.1 to 117.9) | 219.8 MiB (203.2 to 302.1) | **1.88×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 91.2 MiB (91.2 to 91.2) | 198.1 MiB (188.1 to 265.7) | **2.17×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 128 ms (106 to 153) | 76 ms (61 to 88) | **0.59×** | n/a | Latency of single operations, median of each percentile across runs: | Workload | Percentile | HStore | HyperGraphDB | Ratio | |---|---|---:|---:|---:| -| `latency.read` | p50 | 0.3 µs | 1.6 µs | **4.76×** | -| `latency.read` | p99 | 1.2 µs | 26.3 µs | **22.53×** | -| `latency.read` | p99.9 | 5.2 µs | 207.0 µs | **40.05×** | -| `latency.read` | max | 57.8 µs | 7.29 ms | **126.09×** | -| `latency.commit` | p50 | 85.1 µs | 91.5 µs | **1.07×** | -| `latency.commit` | p99 | 429.6 µs | 1.08 ms | **2.52×** | -| `latency.commit` | p99.9 | 1.62 ms | 5.02 ms | **3.09×** | -| `latency.commit` | max | 8.79 ms | 25.56 ms | **2.91×** | -| `mixed.read` | p50 | 1.6 µs | 2.9 µs | **1.77×** | -| `mixed.read` | p99 | 12.7 µs | 73.6 µs | **5.81×** | -| `mixed.read` | p99.9 | 57.4 µs | 1.03 ms | **17.93×** | -| `mixed.read` | max | 51.62 ms | 87.42 ms | **1.69×** | -| `mixed.write` | p50 | 3.09 ms | 4.85 ms | **1.57×** | -| `mixed.write` | p99 | 16.70 ms | 80.12 ms | **4.80×** | -| `mixed.write` | p99.9 | 46.96 ms | 110.43 ms | **2.35×** | -| `mixed.write` | max | 46.96 ms | 110.43 ms | **2.35×** | -| `compaction.baseline` | p50 | 5.06 ms | 4.87 ms | **0.96×** | -| `compaction.baseline` | p99 | 9.63 ms | 27.99 ms | **2.91×** | -| `compaction.baseline` | p99.9 | 10.60 ms | 52.86 ms | **4.99×** | -| `compaction.baseline` | max | 10.60 ms | 52.86 ms | **4.99×** | -| `compaction` | p50 | 5.15 ms | 5.11 ms | **0.99×** | -| `compaction` | p99 | 9.39 ms | 13.32 ms | **1.42×** | -| `compaction` | p99.9 | 16.03 ms | 33.70 ms | **2.10×** | -| `compaction` | max | 16.03 ms | 33.70 ms | **2.10×** | +| `latency.read` | p50 | 0.2 µs | 0.9 µs | **4.39×** | +| `latency.read` | p99 | 0.5 µs | 12.7 µs | **25.33×** | +| `latency.read` | p99.9 | 1.5 µs | 22.6 µs | **15.06×** | +| `latency.read` | max | 44.0 µs | 1.06 ms | **24.16×** | +| `latency.commit` | p50 | 52.0 µs | 45.5 µs | **0.87×** | +| `latency.commit` | p99 | 295.7 µs | 465.5 µs | **1.57×** | +| `latency.commit` | p99.9 | 441.0 µs | 936.1 µs | **2.12×** | +| `latency.commit` | max | 1.85 ms | 7.36 ms | **3.98×** | +| `mixed.read` | p50 | 1.8 µs | 3.3 µs | **1.86×** | +| `mixed.read` | p99 | 10.5 µs | 54.5 µs | **5.21×** | +| `mixed.read` | p99.9 | 25.7 µs | 305.5 µs | **11.90×** | +| `mixed.read` | max | 28.51 ms | 28.50 ms | **1.00×** | +| `mixed.write` | p50 | 2.33 ms | 2.90 ms | **1.25×** | +| `mixed.write` | p99 | 9.65 ms | 16.94 ms | **1.75×** | +| `mixed.write` | p99.9 | 22.02 ms | 34.41 ms | **1.56×** | +| `mixed.write` | max | 22.02 ms | 34.41 ms | **1.56×** | +| `compaction.baseline` | p50 | 3.31 ms | 3.36 ms | **1.02×** | +| `compaction.baseline` | p99 | 5.43 ms | 7.50 ms | **1.38×** | +| `compaction.baseline` | p99.9 | 8.80 ms | 10.52 ms | **1.20×** | +| `compaction.baseline` | max | 8.80 ms | 10.52 ms | **1.20×** | +| `compaction` | p50 | 3.36 ms | 3.24 ms | **0.97×** | +| `compaction` | p99 | 5.44 ms | 11.59 ms | **2.13×** | +| `compaction` | p99.9 | 6.17 ms | 84.26 ms | **13.66×** | +| `compaction` | max | 6.17 ms | 84.26 ms | **13.66×** | ### Scale 1, `sync` durability @@ -245,30 +245,30 @@ Dataset: 50,000 nodes, 100,000 hyperedges, 362,757 incidences; durability sync; | Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes`: insert nodes in transactions of 1,000 | 29,660 ops/s (22,199 to 31,465) | 12,627 ops/s (9,260 to 15,596) | **2.35×** | yes | -| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 19,884 ops/s (5,121 to 25,199) | 16,377 ops/s (11,336 to 19,379) | **1.21×** | yes | -| `read.incidence`: enumerate the incidence set of a node | 3,662,400 ops/s (722,841 to 4,402,272) | 384,637 ops/s (202,508 to 541,974) | **9.52×** | yes | -| `read.members`: enumerate the members of a hyperedge | 2,553,373 ops/s (1,283,576 to 2,708,706) | 1,176,098 ops/s (605,219 to 1,606,077) | **2.17×** | yes | -| `read.comembership`: count hyperedges containing both atoms of a pair | 268,955 ops/s (73,128 to 359,769) | 9,264 ops/s (6,602 to 22,363) | **29.03×** | yes | -| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 174,383 ops/s (112,165 to 239,400) | 38,159 ops/s (14,753 to 62,344) | **4.57×** | yes | -| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 7,889,212 ops/s (3,865,167 to 17,032,178) | 942,062 ops/s (626,685 to 1,698,089) | **8.37×** | yes | -| `latency.read`: one incidence lookup per read transaction | 1,807,100 ops/s (601,842 to 2,270,977) | 222,231 ops/s (170,393 to 251,137) | **8.13×** | yes | -| `write.update`: replace a node value in transactions of 1,000 | 40,288 ops/s (6,028 to 50,806) | 3,600 ops/s (2,741 to 4,556) | **11.19×** | yes | -| `latency.commit`: one node update per write transaction | 250 ops/s (238 to 255) | 203 ops/s (160 to 216) | **1.23×** | yes | -| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,583,174 ops/s (1,248,493 to 1,633,918) | 623,680 ops/s (528,806 to 731,169) | **2.54×** | n/a | -| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (999 to 1,000) | 973 ops/s (937 to 1,000) | **1.03×** | n/a | -| `large.ingest`: create one hyperedge with 50,000 members | 264,836 ops/s (100,131 to 348,720) | 28,912 ops/s (18,611 to 59,305) | **9.16×** | yes | -| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 1,793,205,884 ops/s (1,362,030,591 to 2,533,074,993) | 401,183,491 ops/s (209,074,988 to 495,719,770) | **4.47×** | yes | -| `large.probe`: test whether a node belongs to the large hyperedge | 9,596,123 ops/s (7,711,041 to 10,334,359) | 867,374 ops/s (123,150 to 1,185,144) | **11.06×** | yes | -| `reopen`: close and reopen the database, including recovery | 165 ms (146 to 166) | 1,274 ms (1,016 to 2,417) | **7.73×** | yes | -| `read.incidence.cold`: incidence sets immediately after reopening | 1,920,287 ops/s (1,498,046 to 2,097,403) | 35,738 ops/s (19,627 to 47,057) | **53.73×** | yes | -| `disk`: bytes on disk after a clean shutdown | 91.2 MiB (90.7 to 92.7) | 350.9 MiB (341.1 to 429.2) | **3.85×** | yes | -| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 10,529 ops/s (7,705 to 14,811) | 2,326 ops/s (2,045 to 3,494) | **4.53×** | yes | -| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 21,722 ops/s (7,640 to 26,595) | 6,392 ops/s (4,632 to 8,894) | **3.40×** | yes | -| `read.incidence.churned`: enumerate incidence sets after the deletes | 4,178,629 ops/s (3,906,256 to 4,731,898) | 832,358 ops/s (239,476 to 1,283,097) | **5.02×** | yes | -| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 123.6 MiB (121.6 to 128.4) | 278.1 MiB (259.0 to 359.5) | **2.25×** | yes | -| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 90.8 MiB (90.8 to 90.8) | 235.4 MiB (230.3 to 268.1) | **2.59×** | yes | -| `recover`: open the store after killing the ingest process with SIGKILL | 191 ms (156 to 1,646) | 139 ms (123 to 171) | **0.73×** | n/a | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 55,831 ops/s (25,612 to 60,845) | 30,898 ops/s (25,247 to 33,003) | **1.81×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 37,061 ops/s (16,880 to 43,465) | 29,575 ops/s (13,674 to 32,075) | **1.25×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 6,914,354 ops/s (2,181,969 to 8,485,453) | 997,154 ops/s (455,024 to 1,403,695) | **6.93×** | yes | +| `read.members`: enumerate the members of a hyperedge | 5,423,092 ops/s (2,346,275 to 6,910,830) | 2,378,494 ops/s (1,139,475 to 3,352,302) | **2.28×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 623,412 ops/s (225,960 to 750,270) | 40,468 ops/s (28,205 to 76,472) | **15.41×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 349,091 ops/s (212,259 to 575,284) | 104,597 ops/s (68,930 to 118,502) | **3.34×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 26,820,024 ops/s (8,343,759 to 41,000,847) | 1,959,562 ops/s (1,354,823 to 2,040,085) | **13.69×** | yes | +| `latency.read`: one incidence lookup per read transaction | 3,592,293 ops/s (1,615,246 to 4,602,083) | 437,799 ops/s (274,315 to 695,056) | **8.21×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 64,088 ops/s (42,466 to 77,527) | 8,736 ops/s (3,640 to 10,551) | **7.34×** | yes | +| `latency.commit`: one node update per write transaction | 258 ops/s (244 to 260) | 236 ops/s (229 to 237) | **1.09×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 2,283,312 ops/s (1,936,304 to 2,346,786) | 1,048,371 ops/s (885,124 to 1,088,745) | **2.18×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (1,000 to 1,000) | 1,000 ops/s (1,000 to 1,000) | **1.00×** | n/a | +| `large.ingest`: create one hyperedge with 50,000 members | 611,622 ops/s (330,705 to 701,290) | 68,501 ops/s (44,204 to 124,867) | **8.93×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 3,856,784,479 ops/s (1,065,939,440 to 4,213,853,042) | 921,639,492 ops/s (673,177,777 to 1,080,788,976) | **4.18×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 11,861,283 ops/s (8,958,098 to 17,241,257) | 1,229,677 ops/s (819,877 to 1,814,296) | **9.65×** | yes | +| `reopen`: close and reopen the database, including recovery | 128 ms (119 to 148) | 1,077 ms (858 to 1,245) | **8.44×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 2,929,216 ops/s (2,168,586 to 4,181,768) | 104,708 ops/s (94,852 to 142,580) | **27.98×** | yes | +| `disk`: bytes on disk after a clean shutdown | 93.3 MiB (91.8 to 93.7) | 321.7 MiB (305.6 to 350.8) | **3.45×** | yes | +| `churn.delete`: delete 20,000 hyperedges (20%) in transactions of 1,000 | 26,928 ops/s (10,040 to 28,095) | 5,485 ops/s (4,237 to 8,294) | **4.91×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 36,834 ops/s (21,182 to 40,925) | 11,888 ops/s (9,157 to 14,220) | **3.10×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 8,441,520 ops/s (6,577,667 to 9,027,409) | 1,129,728 ops/s (982,393 to 1,799,620) | **7.47×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 118.5 MiB (117.1 to 127.5) | 256.5 MiB (195.4 to 276.1) | **2.17×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 91.2 MiB (91.2 to 91.2) | 190.4 MiB (182.5 to 277.1) | **2.09×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 113 ms (107 to 184) | 78 ms (54 to 78) | **0.69×** | n/a | ### Scale 4, `async` durability @@ -276,80 +276,109 @@ Dataset: 200,000 nodes, 400,000 hyperedges, 1,453,427 incidences; durability asy | Workload | HStore | HyperGraphDB | Ratio of medians | Results agree | |---|---:|---:|---:|:---:| -| `ingest.nodes`: insert nodes in transactions of 1,000 | 27,056 ops/s (23,106 to 54,381) | 21,380 ops/s (18,088 to 39,804) | **1.27×** | yes | -| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 11,696 ops/s (8,388 to 29,131) | 1,903 ops/s (1,370 to 2,876) | **6.15×** | yes | -| `read.incidence`: enumerate the incidence set of a node | 2,658,726 ops/s (721,371 to 3,508,844) | 1,005,595 ops/s (466,163 to 1,020,410) | **2.64×** | yes | -| `read.members`: enumerate the members of a hyperedge | 218,369 ops/s (164,400 to 441,827) | 1,719,353 ops/s (1,087,218 to 2,715,851) | **0.13×** | yes | -| `read.comembership`: count hyperedges containing both atoms of a pair | 390,285 ops/s (245,706 to 685,369) | 45,277 ops/s (2,502 to 64,982) | **8.62×** | yes | -| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 15,008 ops/s (8,947 to 22,517) | 78,094 ops/s (21,417 to 98,666) | **0.19×** | yes | -| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 5,180,283 ops/s (4,054,032 to 24,383,863) | 1,784,381 ops/s (1,044,917 to 1,855,868) | **2.90×** | yes | -| `latency.read`: one incidence lookup per read transaction | 1,151,517 ops/s (1,025,897 to 2,794,408) | 364,075 ops/s (120,193 to 368,730) | **3.16×** | yes | -| `write.update`: replace a node value in transactions of 1,000 | 20,793 ops/s (11,713 to 64,932) | 1,240 ops/s (1,046 to 2,486) | **16.77×** | yes | -| `latency.commit`: one node update per write transaction | 5,210 ops/s (1,503 to 12,178) | 2,017 ops/s (1,384 to 2,540) | **2.58×** | yes | -| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,762,368 ops/s (1,350,450 to 2,098,567) | 883,741 ops/s (535,366 to 1,019,176) | **1.99×** | n/a | -| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (1,000 to 1,000) | 1,000 ops/s (897 to 1,000) | **1.00×** | n/a | -| `large.ingest`: create one hyperedge with 200,000 members | 212,654 ops/s (148,736 to 632,034) | 12,629 ops/s (7,597 to 13,984) | **16.84×** | yes | -| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 1,598,891,425 ops/s (933,830,793 to 2,831,060,900) | 564,337,071 ops/s (89,028,350 to 642,985,381) | **2.83×** | yes | -| `large.probe`: test whether a node belongs to the large hyperedge | 6,819,112 ops/s (2,350,931 to 17,020,914) | 991,137 ops/s (952,611 to 1,137,921) | **6.88×** | yes | -| `reopen`: close and reopen the database, including recovery | 238 ms (127 to 313) | 1,210 ms (914 to 1,569) | **5.09×** | yes | -| `read.incidence.cold`: incidence sets immediately after reopening | 346,015 ops/s (286,676 to 1,825,463) | 11,832 ops/s (6,048 to 15,298) | **29.24×** | yes | -| `disk`: bytes on disk after a clean shutdown | 165.1 MiB (163.2 to 165.2) | 2366.0 MiB (2233.5 to 2377.7) | **14.33×** | yes | -| `churn.delete`: delete 80,000 hyperedges (20%) in transactions of 1,000 | 2,090 ops/s (1,534 to 5,016) | 1,487 ops/s (885 to 1,857) | **1.41×** | yes | -| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 2,828 ops/s (2,449 to 7,564) | 2,684 ops/s (1,730 to 3,572) | **1.05×** | yes | -| `read.incidence.churned`: enumerate incidence sets after the deletes | 3,450,581 ops/s (2,866,935 to 6,637,792) | 777,091 ops/s (767,716 to 1,148,418) | **4.44×** | yes | -| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 1140.8 MiB (1140.8 to 1142.8) | 1875.4 MiB (1869.6 to 1945.4) | **1.64×** | yes | -| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 162.6 MiB (162.6 to 162.7) | 1177.7 MiB (1168.7 to 1216.8) | **7.24×** | yes | -| `recover`: open the store after killing the ingest process with SIGKILL | 203 ms (177 to 403) | 545 ms (362 to 1,214) | **2.69×** | n/a | +| `ingest.nodes`: insert nodes in transactions of 1,000 | 40,009 ops/s (33,982 to 57,601) | 28,712 ops/s (26,707 to 38,938) | **1.39×** | yes | +| `ingest.edges`: insert hyperedges (mean cardinality 3.6) in transactions of 1,000 | 21,132 ops/s (19,987 to 27,311) | 2,050 ops/s (1,725 to 2,736) | **10.31×** | yes | +| `read.incidence`: enumerate the incidence set of a node | 6,567,839 ops/s (6,370,914 to 7,065,286) | 574,929 ops/s (549,276 to 1,290,467) | **11.42×** | yes | +| `read.members`: enumerate the members of a hyperedge | 5,575,872 ops/s (4,558,084 to 5,931,873) | 1,731,668 ops/s (1,449,441 to 1,834,122) | **3.22×** | yes | +| `read.comembership`: count hyperedges containing both atoms of a pair | 357,920 ops/s (306,442 to 380,508) | 18,128 ops/s (17,264 to 53,840) | **19.74×** | yes | +| `read.twohop`: count the distinct nodes that share a hyperedge with a node | 311,293 ops/s (226,920 to 397,015) | 71,578 ops/s (46,798 to 96,728) | **4.35×** | yes | +| `read.incidence.parallel`: incidence sets from 8 threads, 100,000 probes each | 14,848,396 ops/s (9,710,034 to 17,930,486) | 1,459,037 ops/s (1,355,328 to 1,835,461) | **10.18×** | yes | +| `latency.read`: one incidence lookup per read transaction | 2,358,391 ops/s (1,510,230 to 2,532,733) | 350,668 ops/s (205,075 to 361,613) | **6.73×** | yes | +| `write.update`: replace a node value in transactions of 1,000 | 51,937 ops/s (21,950 to 67,587) | 1,569 ops/s (1,434 to 2,282) | **33.10×** | yes | +| `latency.commit`: one node update per write transaction | 12,193 ops/s (8,383 to 16,763) | 2,261 ops/s (1,783 to 2,547) | **5.39×** | yes | +| `mixed.read`: single incidence lookups from 7 readers while one writer commits 10 updates at a time, targeting 1,000 updates/s | 1,984,771 ops/s (1,929,424 to 2,177,523) | 855,896 ops/s (840,015 to 1,007,499) | **2.32×** | n/a | +| `mixed.write`: updates committed by the writer alongside the readers | 1,000 ops/s (1,000 to 1,000) | 1,000 ops/s (994 to 1,000) | **1.00×** | n/a | +| `large.ingest`: create one hyperedge with 200,000 members | 489,256 ops/s (357,717 to 530,352) | 12,076 ops/s (11,960 to 12,329) | **40.51×** | yes | +| `large.scan`: enumerate all members of the large hyperedge (x400, after a warm-up) | 3,638,203,410 ops/s (2,471,360,485 to 3,728,075,827) | 431,469,984 ops/s (151,006,446 to 452,912,545) | **8.43×** | yes | +| `large.probe`: test whether a node belongs to the large hyperedge | 11,711,888 ops/s (6,814,155 to 15,438,751) | 707,036 ops/s (363,080 to 1,115,133) | **16.56×** | yes | +| `reopen`: close and reopen the database, including recovery | 206 ms (130 to 242) | 1,157 ms (1,028 to 1,270) | **5.61×** | yes | +| `read.incidence.cold`: incidence sets immediately after reopening | 1,163,030 ops/s (984,567 to 2,006,144) | 13,081 ops/s (12,526 to 13,314) | **88.91×** | yes | +| `disk`: bytes on disk after a clean shutdown | 165.7 MiB (164.9 to 166.4) | 2294.8 MiB (2211.7 to 2367.2) | **13.85×** | yes | +| `churn.delete`: delete 80,000 hyperedges (20%) in transactions of 1,000 | 3,388 ops/s (2,225 to 4,295) | 1,426 ops/s (1,308 to 1,459) | **2.38×** | yes | +| `churn.remove`: remove one member from each of 20,000 other hyperedges, 1,000 per transaction | 5,771 ops/s (3,508 to 6,005) | 1,919 ops/s (1,677 to 2,722) | **3.01×** | yes | +| `read.incidence.churned`: enumerate incidence sets after the deletes | 5,677,396 ops/s (3,467,056 to 6,307,954) | 1,015,417 ops/s (531,391 to 1,302,526) | **5.59×** | yes | +| `disk.churned`: bytes on disk after the deletes and a clean shutdown | 1238.9 MiB (1225.2 to 1239.6) | 1879.4 MiB (1846.6 to 1966.1) | **1.52×** | yes | +| `disk.compacted`: bytes on disk after a full compaction and a clean shutdown | 164.5 MiB (164.5 to 164.5) | 1159.1 MiB (1119.0 to 1176.9) | **7.05×** | yes | +| `recover`: open the store after killing the ingest process with SIGKILL | 219 ms (189 to 257) | 455 ms (327 to 527) | **2.08×** | n/a | ## Analysis **Where HStore leads, and why.** -* **Incidence enumeration (2.6–10×), co-membership (8.6–29×) and cold reads after a reopen (29–66×).** An +* **Incidence enumeration (4.2–11×), co-membership (11–20×) and cold reads after a reopen (28–89×).** An atom's incidence set is one entry of the reverse index: a short list stored inline for most atoms, and a counted B+tree for hubs. Enumeration hands each hyperedge id to the caller without allocating, and co-membership is a leapfrog intersection that skips whole subtrees by their summaries ([persistent-tree.md](storage/persistent-tree.md)). HyperGraphDB opens a JE cursor on a duplicate set for each lookup, and its `hg.and` query plans a general join. -* **Updates (11–17×) and bytes written.** A commit logs only the keys it changed, a few hundred bytes, and a +* **Updates (5.4–33×) and bytes written.** A commit logs only the keys it changed, a few hundred bytes, and a checkpoint writes each changed node once for all the commits since the previous one ([wal-and-recovery.md](transactions/wal-and-recovery.md)). A JE commit appends a new version of every changed - record and of the B+tree nodes above it. With `async` durability, a single-update commit writes 649 B in HStore against about 9 KiB in - HyperGraphDB. -* **Giant hyperedges (ingest 9–17×, probes 6.9–20×).** Large edges are bulk-built bottom-up, and a membership + record and of the B+tree nodes above it. With `async` durability, a single-update commit writes 659 B in + HStore against 6.9 KiB in HyperGraphDB. +* **Member enumeration at scale 4 (3.2×) and two-hop queries (3.3–5.5×).** A catalog leaf in the node cache + keeps its page bytes and decodes a record only when it is read + ([persistent-tree.md](storage/persistent-tree.md)), so the catalog stays within a 64 MiB cache: at scale 4 + a member lookup read nothing from the segment files and allocated nothing. At scale 1 HStore is also ahead + (2.3×), with overlapping ranges. +* **Giant hyperedges (ingest 5.3–41×, probes 9.4–17×).** Large edges are bulk-built bottom-up, and a membership probe is one descent of the edge's membership tree. -* **Disk (3.9–14× smaller after load).** Ids are 8-byte varint gaps instead of 16-byte handles, node images are +* **Disk (3.5–14× smaller after load).** Ids are 8-byte varint gaps instead of 16-byte handles, node images are packed into 64-byte units, small member trees and incidence sets live inside their parent record, and the WAL is truncated at every checkpoint. -* **Tail latency.** HStore's p99 and p99.9 are lower in every latency workload. Readers pin an immutable - generation and never wait for writers, checkpoints or compaction. +* **Tail latency.** HStore's p99 and p99.9 are lower in every latency workload, by 1.1–35×. Readers pin an + immutable generation and never wait for writers, checkpoints or compaction. **Where HyperGraphDB leads, and why.** -* **Member enumeration once the data outgrows the cache (`read.members` 0.13× and `read.twohop` 0.19× at - scale 4).** HyperGraphDB's atom cache keeps each link's targets as a Java array, and that cache isn't limited - by `CACHE_MB`. HStore reads a hyperedge's members from its catalog record, and at scale 4 the catalog no longer - fits in a 64 MiB node cache: each lookup reads about 780 bytes from the segment files and allocates about - 9 KiB decoding pages, against 50 bytes for HyperGraphDB. At scale 1 HStore is ahead (2.2×, ranges - overlapping). +* **Opening a crashed store at scale 1 (0.59× with `async`, 0.69× with `sync`).** HyperGraphDB opened the + crashed store in 76 ms against HStore's 128 ms, and the ranges don't overlap. HStore's time is close to its + clean reopen of the full dataset (111 ms), and it allocates 121 MiB while opening, against 78 MiB for + HyperGraphDB. At scale 4 the order reverses: 219 ms for HStore against 455 ms. **Where the two are close.** -* **Single-update commits under `sync` durability (1.2×).** Both stores pay about 4 ms for the forced write - of each commit on this machine. HStore's p99.9 is 4× lower. JE reports about 210 KiB written per commit - in this mode, against 9 KiB with `async`; the extra bytes come with the log flush the adapter adds (see - *Setup*), and we haven't established what JE writes, so don't read the `sync` bytes-written figures for - single commits as a comparison. -* **Bulk ingest at scale 1 (1.2–2.4×)** and **deletes at scale 4 (1.05–1.4×)**. All but one of these ratios - have overlapping ranges. -* **Crash recovery.** Neither store lost an acknowledged commit in any run. Recovery times overlap in every - configuration. - -**What HStore spends.** It retains more heap after ingest (189 MiB against 119 MiB at scale 1), because the -retained heap includes up to `write_buffer_mb` of nodes no checkpoint has written yet. After a large delete, its -files grow until the retained history stops referencing the replaced pages: at scale 4 they reached 1.1 GiB -before compaction brought them back to 163 MiB. Its slowest single commit at scale 4 was slower than -HyperGraphDB's, even though its p99 and p99.9 were lower. +* **Single-update commits at scale 1.** With `async` durability HyperGraphDB is slightly ahead at the median + (0.88×, 52 µs against 45 µs, ranges overlapping), and HStore is ahead at p99 and above. With `sync` + durability (1.09×) both stores pay about 4 ms for the forced write of each commit on this machine, and + HStore's p99.9 is 1.5× lower. JE reports 497 KiB written per commit in `sync` mode, against 6.9 KiB with + `async`; the extra bytes come with the log flush the adapter adds (see *Setup*), and we haven't established + what JE writes, so don't read the `sync` bytes-written figures for single commits as a comparison. +* **Bulk node and hyperedge ingest at scale 1 with `sync` (1.81× and 1.25×), and node ingest at scale 4 + (1.39×).** The ranges overlap in all three. +* **Crash safety.** Neither store lost an acknowledged commit in any run. + +**What HStore spends.** It retains more heap after ingest (190 MiB against 118 MiB at scale 1, 430 MiB against +299 MiB at scale 4), because the retained heap includes up to `write_buffer_mb` of nodes no checkpoint has +written yet. After a large delete, its files grow until the retained history stops referencing the replaced +pages: at scale 4 they reached 1.21 GiB before compaction brought them back to 164.5 MiB. At scale 4 its +single-update commits are faster at every percentile, but the slowest single commit across all runs was +HStore's: 30.5 ms against 18.8 ms. + +### Decoding leaf records on demand + +Storage format 8 writes each record of a catalog or incidence index leaf with its length in front, and a leaf +loaded from a page keeps the page bytes and decodes a record the first time it is read +([pages.md](storage/pages.md), [persistent-tree.md](storage/persistent-tree.md)). Before, a cache miss decoded +every record of the leaf, about 80 of them with their inline member lists, to return one. A fully decoded +catalog leaf took about 11 times the heap of its page image, and at scale 4 only about half the catalog fit in +the 64 MiB node cache. + +The table compares HStore at scale 4 (`async`, `CACHE_MB=64`, three runs each) at commit `459b8be`, which +decoded whole leaves, and at commit `9291e18`. The two sets of runs were made at different times with +different machine load, so treat the throughput columns as indicative. Allocation and bytes read per +operation are steady from run to run. + +| Workload | Before | After | Allocated per op | Read from storage per op | +|---|---:|---:|---:|---:| +| `read.members` | 218,369 ops/s | 5,575,872 ops/s | 9,038 B → 0 B | 778 B → 0 B | +| `read.twohop` | 15,008 ops/s | 311,293 ops/s | 133,848 B → 1,183 B | 12,464 B → 278 B | +| `read.incidence.cold` | 346,015 ops/s | 1,163,030 ops/s | 1,024 B → 595 B | 110 B → 113 B | + +The length in front of each record costs about one byte, so the files grow slightly: 165.1 MiB against +165.7 MiB at scale 4, and 91.3 MiB against 92.2 MiB at scale 1. At scale 1, three alternated runs of each +build moved no workload by more than the spread between runs, apart from incidence reads from 8 threads, +which were 1.28× faster. ### Inline member trees From e940c8cf9774959c92a30544a07637f25fb62654 Mon Sep 17 00:00:00 2001 From: venkat1701 Date: Tue, 13 Oct 2026 06:42:38 +0530 Subject: [PATCH 15/15] docs(readme): refresh performance numbers from the new runs --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b330277..940043b 100644 --- a/README.md +++ b/README.md @@ -51,15 +51,17 @@ hyperedges) with `async` durability. Each figure is the median of 5 runs on one | Workload | HStore | HyperGraphDB | | |---|---:|---:|---:| -| Incidence set of a node | 7.47 M/s | 740 k/s | 10.1× faster | -| Hyperedges shared by two nodes | 448 k/s | 32.5 k/s | 13.8× faster | -| Value updates, 1,000 per transaction | 78.1 k/s | 5.1 k/s | 15.3× faster | -| Bytes written per single-update commit | 649 B | 9.1 KiB | 14× fewer | -| Commit latency, p99 | 430 µs | 1.08 ms | 2.5× lower | -| Disk after loading | 91 MiB | 432 MiB | 4.7× smaller | - -HyperGraphDB is faster at listing a hyperedge's members once the data no longer fits in HStore's node cache: -0.13× at scale 4. With `sync` durability, single commits in both are bound by the cost of a forced write. The +| Incidence set of a node | 6.26 M/s | 1.49 M/s | 4.2× faster | +| Hyperedges shared by two nodes | 610 k/s | 53.9 k/s | 11.3× faster | +| Value updates, 1,000 per transaction | 69.9 k/s | 13.0 k/s | 5.4× faster | +| Bytes written per single-update commit | 659 B | 6.9 KiB | 10.7× fewer | +| Commit latency, p99 | 296 µs | 466 µs | 1.6× lower | +| Disk after loading | 92 MiB | 346 MiB | 3.7× smaller | + +At scale 4 (200,000 nodes, 400,000 hyperedges), where the files of both stores outgrow their caches, the +leads widen: incidence sets 11.4×, value updates 33×, and listing a hyperedge's members 3.2× faster. +HyperGraphDB opens a store after a crash faster at scale 1, 76 ms against 128 ms. With `sync` durability, +single commits in both are bound by the cost of a forced write. The [benchmark report](docs/benchmark-report.pdf) (PDF) covers every workload, configuration and caveat, and [docs/benchmarks.md](docs/benchmarks.md) explains how to rerun it.