Skip to content

[#930] Build the names a JDBC backend gives its own trees once, from the id it was created with - #1007

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/930-jdbc-catalog-name
Open

[#930] Build the names a JDBC backend gives its own trees once, from the id it was created with#1007
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/930-jdbc-catalog-name

Conversation

@vharseko

@vharseko vharseko commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes #930.

What it changes

The three names a JDBC backend builds out of its backend id — the escaped id, the TreeName of its catalog (#888) and the base DN of its own compressed schema pair (#881/#873) — were built again on every call: per tree of every read-write open (enrolInCatalog(), about 25 for a stock suffix), twice per isEnrolledTree(), per clear (catalogTables(), removeStorageFiles()), and per stamped table of a clear's leftover scan (isOwnTree()). They are now built once, in one object, at the first call that needs one.

Held together in one holder rather than in three fields so the three can never come from two different ids.

Why once is correct, and not only cheaper. applyConfigurationChange() replaces config whole, while the tables of a storage — the catalog table among them — stand under the id they were created with. A name read again from a configuration that has moved would leave the storage naming a catalog nothing has ever written, and its own tables attributed to nobody by the clear's report. The configuration framework holds backend-id read-only ("The backend ID may not be altered after the backend is created in the server", BackendConfiguration.xml), so nothing renames a live backend through it — which is what makes reading the id once correct in the first place. It is the same reason poolConnectionString and the standing read bound are read once and not re-read (#878, #885).

Lazily and not in the constructor. A storage is constructed by callers that go on to name no tree of it at all — JDBCStatementBoundTestCase asks the bounds of statements of a storage whose configuration carries no backend id whatsoever, and a construction reading the id would fail there where today nothing reads it.

The performance half is the smaller half. String.replace(CharSequence,CharSequence) returns this when the target is absent, so an ordinary id allocated nothing in the escape to begin with; what is saved per call is the TreeName and its string. Every one of those call sites is already paying a round trip to the database — an enrolment writes a row, and each table of the leftover scan costs a readStoredComment() query. So this is hygiene, as the issue says in its own Severity section, not a hot path.

Also, in the same method

isOwnTree() called DN.toNormalizedUrlSafeString() on every base DN for every table of the leftover scan, and that method memoizes nothing — it walks the DN and normalizes every RDN into a fresh StringBuilder. It was by a good margin the larger half of that loop's string work. The normalized set is now built once per scan and handed in. base-dn, unlike backend-id, is not read-only, so it is not pinned to a field — only hoisted out of the per-table loop. Behaviour is unchanged.

Tests

New CatalogNameTestCase (no database needed) pins that the catalog keeps the backend id the storage was built with. Written first; before the fix it failed with

expected [/opendj_catalog/pinnedBackend] but found [/opendj_catalog/renamedBackend]

Ran locally:

  • CatalogNameTestCase, CatalogConnectionTestCase, StampConnectionTestCase, JDBCStatementBoundTestCase — 63 tests, 0 failures;
  • PgSqlTestCase (-Pprecommit verify -Dapi.version=1.44) — 79 tests, 0 failures, 0 skipped, so the container did start. It covers the clear and the leftover scan, including the assertion that a table of a base DN this backend serves is reported as its own.

…wn trees once, from the id it was created with

The escaped backend id, the catalog TreeName and the base DN of this
backend's own compressed schema pair were built again on every call -
per tree of every read-write open, per clear, and per stamped table of a
clear's leftover scan. They are held in one object now, built at the
first call that needs one: applyConfigurationChange() replaces config
whole, while the tables of a storage stand under the id they were
created with, and backend-id is read-only in the configuration framework
precisely because it cannot move under a live backend.

Built lazily and not in the constructor: a storage is constructed by
callers that go on to name no tree of it at all.

The base DNs isOwnTree() compares against are normalized once per
leftover scan rather than once per table - DN.toNormalizedUrlSafeString()
memoizes nothing, and it was the larger half of that loop's string work.
@vharseko
vharseko requested a review from maximthomas September 9, 2026 17:14
@vharseko vharseko added jdbc performance Performance / concurrency / lock-contention work tests Test suites: fixing, enabling, un-disabling labels Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jdbc performance Performance / concurrency / lock-contention work tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JDBC backend: getCatalogTree() re-escapes the backend id and allocates a TreeName on every call, including per table in a clear's leftover scan

1 participant