[#1063] Give back what the open reserved rather than what the configuration says by then, and ask for a restart when the cache size changes - #1066
Conversation
5d5b3e1 to
725ff9a
Compare
|
Rebased onto master now that #999 is merged ( Re-run green on the rebased head: |
725ff9a to
64f3cff
Compare
|
Rebased onto master once more, now that #994 is merged ( |
64f3cff to
bd9d9d1
Compare
|
Rebased onto master ( The restack carries no change of its own, so the suites are not re-run for it; |
maximthomas
left a comment
There was a problem hiding this comment.
praise: The quota now follows the cache that actually runs, instead of the configuration as it stands at close.
close()gives backreservedCacheSizein bothPDBStorageandJEStorage, andbuildConfigurationfinally honours the result ofacquireMemory(PDBStorage.java:1101,JEStorage.java:778).computeSize()readsserverContext.getMemoryQuota()rather than thememQuotafield, which is null until the first open.- The restart is declared in two places:
component-restartin both XMLs, andadminActionRequiredplus NOTE 630 in theConfigChangeResult.
issue (blocking): After the quota refuses the open's reservation, isConfigurationChangeAcceptable refuses every change to the backend entry, including a change that leaves the cache size alone.
opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java:1556, opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java:1286
A refused reservation leaves reservedCacheSize == 0 while the storage stays open. From then on, newSize <= reservedCacheSize is false for every change. The check falls through to isMemoryAvailable(newSize), which asks for the same amount the quota just refused, and it adds no reason. ConfigurationHandler.replaceEntry asks every change listener on the entry, and nothing filters by property (ConfigurationHandler.java:620-627, ConfigChangeListenerAdaptor.java:339-345). So the following all fail with UNWILLING_TO_PERFORM and an empty reason:
- a
db-txn-no-syncchange; dsconfig set-backend-prop --set enabled:false;- the internal
ds-cfg-enabled: falsemodify thatTaskUtils.disableBackendmakes for an online import-ldif, rebuild-index or restore (TaskUtils.java:203-210).
BASE accepted all of these through newSize <= computeSize(config). Startup opens are not checked against the quota, so this state needs no config change. It comes up with a third PDB/JE backend at the default db-cache-percent 50. It also comes up with one JE backend whose db-cache-size is more than half the reservable pool, because validateDbCacheSize has already taken that size and kept it (#1067). A server restart gets back to the same state.
final long newSize = computeSize(newCfg);
final MemoryQuota quota = serverContext.getMemoryQuota();
// What does not grow past the size already configured asks the quota for nothing (BASE's rule);
// a growth is measured against what this storage holds, which is what the next open adds to.
return (newSize <= Math.max(reservedCacheSize, computeSize(config))
|| quota.isMemoryAvailable(newSize - reservedCacheSize))
&& checkConfigurationDirectories(newCfg, unacceptableReasons);aCacheSizeChangeIsAdmittedAgainstWhatTheStorageHolds keeps its outcome under this fix: with 64 MB held and 128 MB pending, 256 MB is refused and 192 MB admitted at 129 MB free.
Pin: repeat the open of aReservationTheQuotaRefusedIsNotGivenBackOnClose (32 MB free, a 64 MB cache), then assert that a db-txn-no-sync-only change is acceptable. Do this in both classes. The assertion is red at this head.
final PDBBackendCfg unchangedCache = createBackendCfg(SMALL_CACHE);
when(unchangedCache.isDBTxnNoSync()).thenReturn(true);
assertThat(storage.isConfigurationChangeAcceptable(unchangedCache, new ArrayList<LocalizableMessage>())).isTrue();suggestion (non-blocking): Admission is only asserted where the reservation equals the configured size. A refused open, an unchanged size and a shrink are never checked.
opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java:638, :663, opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/JEStorageTest.java:311, :334
The only calls to isConfigurationChangeAcceptable come after a granted 64 MB open, where reservedCacheSize == configuredCacheSize. Two mutants survive all six cases in both classes (found by reading; not run):
reservedCacheSizereplaced byconfiguredCacheSizein the admission line;- the
newSize <= reservedCacheSize ||short circuit dropped. In production that sends every shrink toSemaphore.tryAcquire(negative), which throwsIllegalArgumentException.
The blocking issue above ships green for the same reason.
Pin: after the refused open of aReservationTheQuotaRefusedIsNotGivenBackOnClose, assert that createBackendCfg(SMALL_CACHE + SMALL_CACHE / 4) is not acceptable. It is 16 MB above what is configured but 80 MB above what is held, which kills the reserved → configured swap. Then, after a granted open with the quota drained to 0 free, assert that createBackendCfg(SMALL_CACHE / 2) is acceptable. That kills the dropped short circuit.
suggestion (non-blocking): The percent arm of the restart note is not pinned. Every case sizes the cache with db-cache-size.
opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java:616
Each applyConfigurationChange call in the class (:570, :586, :604, :624, :644) uses createBackendCfg(n * SMALL_CACHE). None goes through db-cache-percent, which is the shipped default (size 0, percent 50). The mutant newCacheSize = cfg.getDBCacheSize() at PDBStorage.java:1649 survives. On a percent-sized backend, that mutant makes every unrelated change set adminActionRequired and emit a note saying "X bytes … 0 bytes".
Pin: open with createBackendCfg(0) and getDBCachePercent() stubbed to 10. Assert that a db-txn-no-sync-only change at percent 10 leaves adminActionRequired() false, and that a change to percent 20 sets it and names memPercentToBytes(10) and memPercentToBytes(20).
suggestion (non-blocking): No test shows that the "opened with" baseline stays fixed across two changes.
opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java:598
Every restart-note case applies a single change to a storage that was just opened. So the mutant configuredCacheSize = newCacheSize; after the addMessage survives. In production, that mutant turns open 64 → change 128 → change back to 64 into a restart note for a pool that still runs at 64.
final ConfigChangeResult back = storage.applyConfigurationChange(createBackendCfg(SMALL_CACHE));
assertThat(back.adminActionRequired()).isFalse();
assertThat(back.getMessages()).isEmpty();Pin: append this to aCacheSizeChangedWhileOpenAsksForARestart in both classes.
suggestion (non-blocking): The open guard of the restart note (db != null / env != null) is not pinned.
opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/JEStorageTest.java:271, opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java:1650, opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java:1380
Every case that calls applyConfigurationChange opens the storage first. The mutant that drops the guard survives both classes. On a storage that has been constructed but never opened, the listener is already registered, and the mutant sets adminActionRequired and names "0 bytes opened with". The effect is small: the apply then fails in registerMonitoredDirectory anyway, at BASE as at this head.
Pin: construct a storage without opening it, apply a config with a different cache size, and assert that adminActionRequired() is false and that NOTE 630 is not among the messages.
issue (non-blocking): NOTE 630 calls the opened-with size "reserved", including when the quota refused the reservation.
opendj-server-legacy/src/messages/org/opends/messages/backend.properties:1173, opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java:1657, opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java:1387
Both callers pass configuredCacheSize into "the memory reserved for it … stay at the %d bytes". After a refused open the storage holds 0 bytes of the quota. A later cache change that the quota now admits therefore reports memory as reserved when none is. On the granted road the text is accurate.
NOTE_CONFIG_DB_CACHE_REQUIRES_RESTART_630=The change to the database cache of backend %s will not take effect \
until the backend is restarted: the cache the backend runs with stays at the %d bytes it was opened with until \
then, and the %d bytes now configured are reserved by the next open…han what the configuration says by then, and ask for a restart when the cache size changes PDBStorage and JEStorage reserved their cache size from the memory quota by reading config in buildConfiguration and released it by reading config again in close(). applyConfigurationChange swapped config in between without touching the quota or the cache, and neither db-cache-size nor db-cache-percent was marked as needing a restart, so a cache grown from 64 MB to 128 MB while the backend ran released 128 against 64 taken at the next disable - the one an online import makes included - and the quota believed 64 MB free that the server did not have, for the life of the JVM; a shrink left the difference reserved by nobody. The running cache was the old size throughout. Both storages now keep two numbers of their own: the cache size of the configuration they opened with, and of it what the quota granted - a tryAcquire it refused, which an open at startup is not checked against, reserved nothing and used to be released all the same. close() gives back the granted size. isConfigurationChangeAcceptable admits the difference to what is held rather than to config, which a change admitted but not yet applied has already moved to the new size. applyConfigurationChange on an open storage whose cache size the change moves sets adminActionRequired and says so (NOTE_CONFIG_DB_CACHE_REQUIRES_RESTART): PersistIt cannot resize a buffer pool once the database is open, and JEStorage has never resized its environment. The two properties are marked component-restart in both configuration XMLs, as db-directory is. PDBStorageTest and JEStorageTest, six cases each: the grow and the shrink give back what was taken, the change asks for a restart and names both sizes, a change which leaves the cache alone asks for nothing, admission is against what is held, and a reservation the quota refused is not given back.
…che past the configured size whatever the storage holds After an open the quota refused, a storage holds nothing of the quota, and the admission of the last commit measured every change against that nothing: newSize <= reservedCacheSize failed for any cache, and isMemoryAvailable(newSize) asked for the very amount the quota had just refused. Every change listener of the backend entry is asked about every change, whatever property it moves, so a change of db-txn-no-sync, a disable, and the disable TaskUtils.disableBackend makes for an online import-ldif, rebuild-index or restore were all refused with UNWILLING_TO_PERFORM and no reason. The state needs no change of configuration to reach: the server does not check the backends it opens at startup against the quota. A size which does not grow past the one configured now asks the quota for nothing again, as it did before this PR; a growth is still measured against what the storage holds, so the case of a change admitted but not applied keeps its outcome. NOTE 630 no longer calls the size the backend was opened with reserved, which after a refused open it is not. PDBStorageTest and JEStorageTest, five more cases each and one extended, each killing a mutant which survived both classes: a change which leaves the cache alone is admitted after a refused reservation, a growth after it is measured against nothing held, a shrink is admitted with the quota exhausted, a cache sized by percent asks for a restart only when the percent moves, a storage which is not open asks for none, and a change back to the size the storage opened with asks for nothing.
bd9d9d1 to
cba9d51
Compare
|
Thanks, all six taken. Round head: Blocking: a refused reservation refuses every change of the backend entry. Confirmed by reading. return (newSize <= Math.max(reservedCacheSize, computeSize(config))
|| quota.isMemoryAvailable(newSize - reservedCacheSize))
&& checkConfigurationDirectories(newCfg, unacceptableReasons);The comment above it now explains why a change that does not grow the size asks the quota for nothing. Admission pins. Percent arm. Fixed baseline across two changes. Your snippet is appended to Open guard. NOTE 630. Reworded to your text. The number and the arguments are unchanged: NOTE_CONFIG_DB_CACHE_REQUIRES_RESTART_630=The change to the database cache of backend %s will not take effect \
until the backend is restarted: the cache the backend runs with stays at the %d bytes it was opened with until \
then, and the %d bytes now configured are reserved by the next openRuns.
Each mutant is red on exactly the case(s) listed and green elsewhere. |
Summary
PDBStorageandJEStoragereserve their cache size from the server'sMemoryQuotawhen they open and release itwhen they close - both times by reading the configuration they hold at that moment. A change of
db-cache-sizeordb-cache-percenton a running backend swaps that configuration (applyConfigurationChangeends inconfig = cfg)without touching the quota or the cache, and neither property is marked as needing a restart. So the storage reserves
the old size and releases the new one at the next close - the disable an online
import-ldifmakes included - andthe quota drifts by the difference for the life of the JVM: a cache grown from 64 MB to 128 MB leaves the quota
believing 64 MB free that the server does not have; a shrink leaves 64 MB reserved by nobody. The running cache is
the old size throughout, and
dsconfigreports the change applied.Fixes #1063.
What changes
Both storages keep two numbers of their own instead of reading
configtwice:configuredCacheSize- the cache size of the configuration the storage opened with (for PDB, what the buffer poolwas built to);
reservedCacheSize- of it, what the quota granted.acquireMemoryis atryAcquire: refused, it reservesnothing, and the return value used to be ignored in both
buildConfigurations, so a close released a size thatwas never taken. That is reachable without any change - the server does not call
isConfigurationAcceptableforthe backends it opens at startup.
close()gives backreservedCacheSize.isConfigurationChangeAcceptableadmits a growth for the difference towhat is held rather than to
config: once a change has been admitted but not applied,computeSize(config)isalready the new size while the reservation is the old one, and a second change was admitted for a difference nobody
would reserve. A size which does not grow past the one configured asks the quota for nothing, as before
(
newSize <= max(reservedCacheSize, computeSize(config))): every change listener of the backend entry is asked aboutevery change, so after an open the quota refused - nothing held - a change of any other property, a disable, and the
disable
TaskUtils.disableBackendmakes for an online import, rebuild or restore would otherwise be refused.applyConfigurationChangeon an open storage whose cache size the change moves setsadminActionRequiredand addsNOTE_CONFIG_DB_CACHE_REQUIRES_RESTART(630), naming the size the backend runs with and the one now configured.db-cache-sizeanddb-cache-percentare markedcomponent-restartinPDBBackendConfiguration.xmlandJEBackendConfiguration.xml, asdb-directoryis in the same files, sodsconfigsays so too.Why a restart rather than a resize
PersistIt sizes its buffer pool when the database opens and has no way to resize it (
Persistit.setConfigurationrefuses a second configuration). JE could -
je.maxMemoryandje.maxMemoryPercentare mutable throughEnvironment.setMutableConfig- butJEStoragehas never resized its environment, and it is not alone: none of theJE properties the pluggable backend left without
requires-admin-actionis applied live since OPENDJ-1719 droppedthe
setMutableConfigroad of the old backend. Restoring that road for all of them is #1068; here the twostorages take the same shape, and the quota follows the cache that actually runs.
On master
The fix builds on the
close()of #999 - the quota given back once, and since its last round after the database hasclosed, in a
finally- and onJEStorageTest, which #999 introduces. #999 is merged; the branch sits on master,and the give-back of what the open reserved is the one inside that
finally. Rebased onto master0e039c6473for round 1 of the review: the one conflict was
JEStorageTest, where the replay cases of #1065 added theirimports and constants at the same places - a union, the test bodies merged on their own. #1070 (#1067) now asks
the quota in
validateDbCacheSizeinstead of taking from it, leaving the reservation to the storage, as here.Two commits: the fix, and round 1 on top.
Tests
PDBStorageTestandJEStorageTest, eleven cases each, over a mockedServerContextwith a freshMemoryQuota:aCacheGrownWhileOpenIsGivenBackAsItWasTaken,aCacheShrunkWhileOpenIsGivenBackAsItWasTaken- the quota isback where it started after open, change, close;
aCacheSizeChangedWhileOpenAsksForARestart-adminActionRequired, the message id and both sizes; a changeback to the size opened with asks for nothing;
aChangeWhichLeavesTheCacheSizeAloneAsksForNothing- a change of another property asks for nothing, as before;aCacheSizedByPercentAsksForARestartOnlyWhenThePercentChanges-db-cache-size0 at percent 10: anotherproperty asks for nothing, percent 20 names
memPercentToBytes(10)andmemPercentToBytes(20);aStorageWhichIsNotOpenAsksForNoRestart- a storage constructed but never opened;aCacheSizeChangeIsAdmittedAgainstWhatTheStorageHolds- with 64 MB held and a change to 128 MB pending, 256 MB isrefused and 192 MB admitted at 129 MB free;
aChangeWhichLeavesTheCacheSizeAloneIsAdmittedAfterARefusedReservation- nothing held, 32 MB free, adb-txn-no-syncchange is admitted;aGrowthAfterARefusedReservationIsMeasuredAgainstNothingHeld- same state, 80 MB is refused;aShrinkIsAdmittedWithTheQuotaExhausted- 64 MB held, 0 free, 32 MB is admitted;aReservationTheQuotaRefusedIsNotGivenBackOnClose- 32 MB free, a 64 MB cache opens, and the close leaves 32 MB.Five of the six were red on the head of #999 before the fix (the sixth pins existing behaviour): +64 MB, −64 MB,
admission of 256 MB, no admin action, 96 MB after the close of a refused reservation.
Mutants, each run against both classes:
close()releasingconfiguredCacheSizeinstead of the reserved size(the refused-reservation case red, nothing else), admission against
computeSize(config)(the admission case),setAdminActionRequireddropped (the restart case), and the old release byconfig(grow, shrink and the refusedreservation). Round 1, on master
0e039c6473, one JVM per run: the admission of the first commit(
newSize <= reservedCacheSize),reservedCacheSize→configuredCacheSizein the admission, the short circuitdropped,
newCacheSize = cfg.getDBCacheSize(), the opened-with baseline moved after the note, and the open guarddropped - each red on exactly its own case(s) in both classes. On the round head:
PDBStorageTest25/25,JEStorageTest22/22 (with the replay cases of #1065),FailedBackendOpenTest8/8,BackendConfigManagerTestCase11/11.
Regression set (one JVM per class, the fixed storages first on the classpath):
FailedBackendOpenTest,PDBTestCase,EncryptedPDBTestCase,JETestCase,EncryptedJETestCase,ReplayedConfigChangeTest,OnDiskMergeImporterTest,PersistentCompressedSchemaTest,DN2IDTest,StateTest,ID2EntryTest,ID2ChildrenCountTest,BulkCursorTest,DefaultIndexTest,ImportLDIFTestCase,RebuildIndexTestCase,VerifyIndexTestCase,BackendConfigManagerTestCase- 240 tests, 0 failures (FailedBackendOpenTestandPDBTestCasere-run after a collision on the admin port 65534 with another JVM on the machine).Not in this PR
ConfigurableEnvironment.validateDbCacheSizetookdb-cache-sizefrom the server's quota as aprobe and never gave it back; fixed on master by [#1067] Ask the memory quota whether an explicit db-cache-size fits instead of taking it #1070.
db-checkpointer-wakeup-interval) that are neither applied livenor marked as needing a restart.