Date: 2026-05-30
Accepted
Islandr stores users, groups, networks, peers, ACL membership, activity samples, and an audit log. Working set size is small (≤ low thousands of peers per hub, a few million activity samples at 30s polling with 30-day retention). Concurrency is light — one admin or a handful, a poller writing samples on a fixed cadence, end-users reading their own data.
Three tensions:
quarkus dev plus a file-backed SQLite gives "clone, run, code" in seconds.A single-DB choice means picking one of:
Both are worse than letting the deployment pick.
The interesting sub-question raised after the first draft of this ADR: is SQLite actually the right embedded option, or is there a more Quarkus-native one? That question is answered in the Alternatives section below — the short version is that the other "embedded Java DBs" (H2, HSQLDB) are not safe for production use according to their own maintainers, and "embedded Postgres" isn't really embedded.
Support both with the same code path:
./data/islandr.db (or /var/lib/islandr/islandr.db in production).Implementation:
WITHOUT ROWID tricks.quarkus.datasource.db-kind at runtime — same binary, set sqlite or postgresql. JDBC URL via env var.(peer_id, sampled_at) and a periodic deletion job to enforce retention. Both DBs handle this fine at the scale we're at.Baseline: SQLite + PostgreSQL, single code path (the decision).
Two questions get scored separately:
| Criterion (weight) | SQLite + Postgres (baseline) | SQLite only | Postgres only | H2 dev + Postgres prod | H2 (file mode) instead of SQLite | HSQLDB instead of SQLite | Embedded Postgres (zonky) instead of SQLite |
|---|---|---|---|---|---|---|---|
| Dev setup friction (4) | 0 | +1 | -1 | 0 | 0 | 0 | -1 |
| Solo / small self-host fit (3) | 0 | +1 | -1 | -1 | -1 | -1 | -1 |
| Team self-host fit (4) | 0 | -1 | +1 | 0 | 0 | 0 | 0 |
| Operator backup/monitoring story (3) | 0 | -1 | +1 | 0 | -1 | -1 | 0 |
| One code path (4) | 0 | +1 | +1 | -1 | 0 | 0 | 0 |
| Activity-samples write rate (2) | 0 | 0 | +1 | 0 | 0 | 0 | +1 |
| Test parity dev↔prod (3) | 0 | n/a | n/a | -1 | 0 | 0 | +1 |
| Production durability reputation (5) | 0 | 0 | 0 | 0 | -1 | -1 | 0 |
| Quarkus extension / native-image friction (3) | 0 | 0 | 0 | 0 | +1 | +1 | -1 |
| Single-binary deployment fit (ADR-0001) (3) | 0 | 0 | 0 | 0 | 0 | 0 | -1 |
| Weighted total | 0 | −2 | +3 | −10 | −7 | −7 | −5 |
Honest reading:
quarkus-jdbc-h2), it's pure Java, and it works in native image without reflection-config gymnastics. We reject it on a single criterion that dominates: the H2 maintainers themselves do not recommend it for production, and the project has a long-documented history of corruption after unclean shutdowns. That −5 on "Production durability reputation" is the whole story.postgres subprocess. That breaks the single-binary deployment story (ADR-0001) and breaks native image. It's a fine integration-test tool. It is not a production-embedded answer.SQLite has no official Quarkus extension; we pull in org.xerial:sqlite-jdbc directly and configure an Agroal datasource manually. Native image needs a small piece of reflection config (xerial ships metadata for it but it isn't always seamless). That is real friction.
But the alternatives that don't have that friction either:
We accept the small Quarkus-side friction in exchange for SQLite's production reputation: the most-deployed, most-tested database engine in the world, formal-methods-tested file format, ~1 MB footprint, one-file-equals-one-database backup story. That's a trade worth making.
The baseline wins on the strength of "one code path, two deployments" — supporting both is cheaper than people think if you discipline yourself to portable SQL from day one, and the embedded option is genuinely production-safe rather than a footgun the operator will eventually trigger.
Positive
git clone && ./mvnw quarkus:dev works with no DB install.Risks created
JSONB column or a SQLite CHECK constraint and breaks the other backend. Mitigation: keep both CI jobs green; the rule is in the contributor guide.org.xerial:sqlite-jdbc directly and configure a datasource by hand. Native image needs reflection config (xerial provides metadata; it isn't always seamless). Mitigation: validate the native build with SQLite at the first backend spike (PRD OQ-2 territory); if it turns out to be a sustained drag, the comparison in this ADR shifts and either Postgres-only or a documented hybrid (file-mode SQLite on JVM, Postgres on native) gets considered. Tracked as the first thing to test when the Quarkus project gets stood up.Accepted trade-offs
LISTEN/NOTIFY). If we genuinely need them later, this ADR gets superseded.