Context
ArcadeDB only has a full backup, and on a large database it is both slow and disruptive to concurrent writers. The full analysis, including the lock-free and incremental phases, is in docs/optimize-backup.md.
This issue covers Phase 1 only: making the existing full backup substantially faster, with no archive-format change, no engine change and no correctness risk. Phases 2 (lock-free backup) and 3 (incremental backup and PITR) are separate follow-ups and must not be pulled into this one.
Where the time goes today
FullBackupFormat.backupDatabase() (integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java:87) does:
database.executeInReadLock(...), which blocks schema changes and new file creation but not transactions.
pageManager.suspendFlushAndExecute(...), which freezes the data files so they can be read raw.
- Streams every
ComponentFile into a ZIP entry at setLevel(9), single threaded.
Deflate at level 9 on one thread runs at roughly 20-40 MB/s, so the backup is CPU bound long before it is IO bound. A 100 GB database is an hour or more.
Note that the read lock is not the source of the disruption. The disruption comes from the flush suspension: while it holds, dirty pages accumulate in PageManagerFlushThread.deferredByDatabase bounded by FLUSH_SUSPEND_MAX_DEFERRED_RAM (engine/src/main/java/com/arcadedb/GlobalConfiguration.java:444, default 512 MB), and past that cap committing threads are throttled. Phase 1 does not remove the suspension, but by shortening the backup it shortens the throttling window proportionally.
Scope
-
Make the compression level configurable and stop defaulting to 9.
- Add a backup setting in
BackupSettings plus a GlobalConfiguration default.
Deflater.BEST_SPEED typically costs 10-15% of compression ratio for a 3-5x throughput gain. Pick the new default from measurement, not from intuition.
- The existing
-level-style CLI/SQL surface should expose it; keep the current behaviour reachable for anyone who wants maximum compression.
-
Compress files in parallel.
- ZIP entries are independent: build them on a worker pool into temp buffers or temp files and write the central directory serially.
- Follow the rules in the
engine-concurrency skill (.claude/skills/engine-concurrency/SKILL.md). In particular do not submit to ForkJoinPool.commonPool(); use or add a dedicated pool with documented sizing and saturation policy.
- Watch peak memory: a naive "buffer every entry in heap" implementation will OOM on large databases. Temp files or a bounded number of in-flight entries.
- Encryption path (
encryptFile) wraps the whole stream in a CipherOutputStream, so parallel work must happen before that serial write stage. Verify encrypted backups still restore.
-
Optional IO throttle (MB/s cap) so a backup cannot saturate production IO, as Cassandra offers. Off by default.
-
Zstd only if measurement justifies it. com.github.luben:zstd-jni is BSD licensed and therefore license-compatible, but it is a new native dependency and the project rule is not to add one unless strictly necessary. Level-1 Deflate plus parallelism may already close the gap. If it is added, ATTRIBUTIONS.md must be updated.
Out of scope
- Removing the flush suspension (Phase 2).
- Any change to the archive format, manifest or restore semantics (Phase 3).
- Incremental or differential backup, WAL archiving, PITR.
Correctness requirements
- Existing backups must still restore. Existing restore code must not need to change.
- Round-trip tests for both plain and encrypted backups, and for a database with many small files as well as few large ones.
- Backups taken under concurrent write load must restore to a consistent database. Reuse and extend the existing coverage in
integration/src/test/java/com/arcadedb/integration/backup/ (FullBackupIT, BackupDatabaseTest) and server/src/test/java/com/arcadedb/server/backup/.
- Follow the project TDD convention: state the verification plan, write the tests first, then implement.
Benchmark before and after (required)
Do not merge on the strength of a plausible-sounding change. Measure on the same hardware and the same datasets, before and after, and put the numbers in the PR description:
- Wall-clock backup duration, at several database sizes (for example 1 GB, 10 GB, 100 GB).
- Archive size and compression ratio, so a throughput gain traded away for ratio is visible and deliberate. Report the trade explicitly, for example "2.9x faster, 11% larger".
- CPU utilisation and peak heap during the backup. The parallel path must not blow up memory.
- Impact on concurrent writers: sustained insert/update throughput and commit latency percentiles (p50/p95/p99) with and without a backup running. This is the number that matters most to users and the one most easily forgotten.
- Deferred-RAM high-water mark and whether the
FLUSH_SUSPEND_MAX_DEFERRED_RAM cap was hit, which is the direct signal that writers were throttled and by how much less than before.
- Restore duration, to confirm it did not regress.
Also report scaling of the parallel path with thread count (1, 2, 4, 8) so the default pool sizing is chosen from data.
Benchmarks must be tagged so they are excluded from normal CI runs: @Tag("benchmark") for pure throughput comparisons, @Tag("slow") for functional regression tests that take noticeably long. See CLAUDE.md for the tagging rules.
Acceptance criteria
- Full backup of a large database is measurably several times faster on a multi-core machine, with the trade-off in archive size stated explicitly.
- No change to the archive format; old backups restore, new backups restore with the old restore path.
- Concurrent-writer impact is measured and reported, not assumed.
- Compression level and parallelism are configurable, with defaults chosen from the benchmark results.
Context
ArcadeDB only has a full backup, and on a large database it is both slow and disruptive to concurrent writers. The full analysis, including the lock-free and incremental phases, is in
docs/optimize-backup.md.This issue covers Phase 1 only: making the existing full backup substantially faster, with no archive-format change, no engine change and no correctness risk. Phases 2 (lock-free backup) and 3 (incremental backup and PITR) are separate follow-ups and must not be pulled into this one.
Where the time goes today
FullBackupFormat.backupDatabase()(integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java:87) does:database.executeInReadLock(...), which blocks schema changes and new file creation but not transactions.pageManager.suspendFlushAndExecute(...), which freezes the data files so they can be read raw.ComponentFileinto a ZIP entry atsetLevel(9), single threaded.Deflate at level 9 on one thread runs at roughly 20-40 MB/s, so the backup is CPU bound long before it is IO bound. A 100 GB database is an hour or more.
Note that the read lock is not the source of the disruption. The disruption comes from the flush suspension: while it holds, dirty pages accumulate in
PageManagerFlushThread.deferredByDatabasebounded byFLUSH_SUSPEND_MAX_DEFERRED_RAM(engine/src/main/java/com/arcadedb/GlobalConfiguration.java:444, default 512 MB), and past that cap committing threads are throttled. Phase 1 does not remove the suspension, but by shortening the backup it shortens the throttling window proportionally.Scope
Make the compression level configurable and stop defaulting to 9.
BackupSettingsplus aGlobalConfigurationdefault.Deflater.BEST_SPEEDtypically costs 10-15% of compression ratio for a 3-5x throughput gain. Pick the new default from measurement, not from intuition.-level-style CLI/SQL surface should expose it; keep the current behaviour reachable for anyone who wants maximum compression.Compress files in parallel.
engine-concurrencyskill (.claude/skills/engine-concurrency/SKILL.md). In particular do not submit toForkJoinPool.commonPool(); use or add a dedicated pool with documented sizing and saturation policy.encryptFile) wraps the whole stream in aCipherOutputStream, so parallel work must happen before that serial write stage. Verify encrypted backups still restore.Optional IO throttle (MB/s cap) so a backup cannot saturate production IO, as Cassandra offers. Off by default.
Zstd only if measurement justifies it.
com.github.luben:zstd-jniis BSD licensed and therefore license-compatible, but it is a new native dependency and the project rule is not to add one unless strictly necessary. Level-1 Deflate plus parallelism may already close the gap. If it is added,ATTRIBUTIONS.mdmust be updated.Out of scope
Correctness requirements
integration/src/test/java/com/arcadedb/integration/backup/(FullBackupIT,BackupDatabaseTest) andserver/src/test/java/com/arcadedb/server/backup/.Benchmark before and after (required)
Do not merge on the strength of a plausible-sounding change. Measure on the same hardware and the same datasets, before and after, and put the numbers in the PR description:
FLUSH_SUSPEND_MAX_DEFERRED_RAMcap was hit, which is the direct signal that writers were throttled and by how much less than before.Also report scaling of the parallel path with thread count (1, 2, 4, 8) so the default pool sizing is chosen from data.
Benchmarks must be tagged so they are excluded from normal CI runs:
@Tag("benchmark")for pure throughput comparisons,@Tag("slow")for functional regression tests that take noticeably long. SeeCLAUDE.mdfor the tagging rules.Acceptance criteria