diff --git a/docker-compose.yml b/docker-compose.yml index 3f64075..3ade3ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,15 @@ services: # concurrent with writes are a suspected corruption trigger upstream # (rdf4j#5960/#4806), so this is a backstop, not a first-line limit. - RDF4J_QUERY_TIMEOUT_SECONDS=${RDF4J_QUERY_TIMEOUT_SECONDS:-60} + # Client-side socket timeout (ms) for this app's own reads from rdf4j + # (Utils.getHttpRequestConfig). 10s is aggressive for a store that is slow + # rather than broken: the read raises QueryInterruptedException, the loader's + # generic retry loops treat that as transient, and the retries add load, which + # makes the next read slower still. That storm saturated the OVH node on + # 2026-08-31 and again after the migration. Raise it where rdf4j shares CPU + # with other services; it is a timeout, so a higher value costs nothing when + # reads are fast. + - NANOPUB_QUERY_FETCHING_SOCKET_TIMEOUT=${NANOPUB_QUERY_FETCHING_SOCKET_TIMEOUT:-10000} # One-shot repair/resync switch: re-streams the whole registry at startup; # per-repo isLoaded checks skip everything already present, so WITHOUT a # store wipe this fills holes (missing nanopubs) idempotently. Set it for diff --git a/src/main/java/com/knowledgepixels/query/TripleStore.java b/src/main/java/com/knowledgepixels/query/TripleStore.java index 5fff9ab..c2090fe 100644 --- a/src/main/java/com/knowledgepixels/query/TripleStore.java +++ b/src/main/java/com/knowledgepixels/query/TripleStore.java @@ -423,6 +423,16 @@ private void createRepo(String repoName) { indexTypes = "spoc,posc,ospc"; } + // valueEvictionInterval 0 disables LMDB's value-cache garbage collection. + // Without it, rdf4j frees unused value IDs and REUSES them; a stale reference + // then dereferences to a different value, which is the value-ID-remap + // corruption we hit eight times between 2026-08-20 and 2026-08-31 (upstream + // eclipse-rdf4j/rdf4j#5970). Crucially the eviction pass "is also run after + // opening the database" (ValueStore.java), which explains why every one of + // those events followed a restart of the affected store. Disabling it is the + // workaround recommended upstream on 2026-08-31; it costs disk (dead values + // are never reclaimed) but not memory — gcIds() returns early, so nothing + // accumulates on the heap. Revisit once the upstream race is fixed. String createRegularRepoQueryString = "@prefix rdfs: .\n" + "@prefix rep: .\n" + @@ -441,6 +451,7 @@ private void createRepo(String repoName) { " sail:sailType \"rdf4j:LmdbStore\" ;\n" + " sail:iterationCacheSyncThreshold \"10000\";\n" + " lmdb:tripleIndexes \"" + indexTypes + "\" ;\n" + + " lmdb:valueEvictionInterval 0 ;\n" + " sb:defaultQueryEvaluationMode \"STANDARD\"\n" + " ]\n" + " ].\n"; @@ -468,6 +479,7 @@ private void createRepo(String repoName) { " sail:sailType \"rdf4j:LmdbStore\" ;\n" + " sail:iterationCacheSyncThreshold \"10000\";\n" + " lmdb:tripleIndexes \"" + indexTypes + "\" ;\n" + + " lmdb:valueEvictionInterval 0 ;\n" + " sb:defaultQueryEvaluationMode \"STANDARD\"\n" + " ]\n" + " ]\n" +