fix: stop concurrent flushes and the updater from skipping stored files - #18
Merged
Merged
Conversation
ConfigSyncService kept one lastChecked watermark per node, shared by every flush and by the scheduled ConfigFileUpdater. ConfigFileWriter searched for files with @timestamp >= lastChecked and moved the watermark to the current time before its own search had run. When several clients stored files and flushed at the same time, a flush could search from a watermark another flush had just moved and return before that other flush had written its caller's files. A file stamped before a run started but searchable only after the run's search was never selected again, so it stayed missing on that node until restart. Several Fess instances with their own index names, started at once against one cluster, hit this: all but one failed to create their document index with "IOException while reading mappings_path: file not readable" for their own dictionary files. - A flush now reads every stored file and does not touch the watermark, so every file stored before the flush request is on disk on every data node when the flush returns. - The updater still reads incrementally, but only a completed run advances the watermark, and only to five minutes before that run started. This covers files that become searchable late and clock differences between nodes. A failed or terminated run leaves it unchanged. - lastChecked is an AtomicLong instead of an unsynchronized field. Reading a file again is safe: a file is written only when it is missing or older than the stored @timestamp. Add ConfigSyncPluginTest#test_configFiles_concurrentFlush and #test_configFiles_storedBeforeUpdaterRun; both fail on the previous code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When several clients store files with
POST /_configsync/fileand then callPOST /_configsync/flushat the same time, a flush can return before the files its caller stored are written, and a few files are never written at all, not even by the scheduled updater. Every store and flush request still returns 200.Found while verifying Fess 15.9.0. Several Fess instances with their own index names and dictionary prefix, started at the same time against one OpenSearch cluster, each upload their dictionary files, flush, and create their indices. All but one instance then fail to create the document index with
IOException while reading mappings_path: file not readable(orstopwords_path), and OpenSearch logsNoSuchFileExceptionfor files under that instance's own dictionary prefix.Cause
ConfigSyncServicekeeps onelastCheckedwatermark per node (ConfigSyncService.java:145), used by both the flush transport handler (:715) and the scheduledConfigFileUpdater(:632).ConfigFileWriter#execute(:651-659) searches for files with@timestamp >= lastCheckedand setslastCheckedto the current time before its search has started:configsync.scroll_size. Flush B, started a moment later, searches from A's new watermark, finds none of the files B's client stored, and returns while A is still writing them.store()takes a file's@timestamp(:395) before the file is indexed. A flush or updater run that takes its own timestamp after that, but searches before the file is searchable, moves the watermark past a file it never saw, and no later run selects it. The file stays missing on that node until the node restarts.Fix
RefreshPolicy.IMMEDIATE, so a file whose store request returned before the flush was sent is searchable and is on disk on every data node when the flush returns, whatever other flushes, updater runs or node clocks do.lastCheckedis now anAtomicLong; it was a plain field written from transport and scheduler threads.Reading a file again is safe:
updateConfigFilewrites a file only when it is missing or older than the stored@timestamp(:580), so files already on disk are not rewritten.Compatibility
configsync.scroll_sizeof 1. Flushes are explicit requests, so this cost is paid only when a client asks for it.No settings or REST API change.
Verification
ConfigSyncPluginTest#test_configFiles_concurrentFlush(new): three clients, each talking to a different node, store 50 files and flush at the same time, for three rounds; after its own flush each client checks all of its files on all three nodes. Onmainit fails (for example 16 and 18 of 50 files missing on two nodes); with this change it passes.ConfigSyncPluginTest#test_configFiles_storedBeforeUpdaterRun(new): indexes a file whose@timestampis older than updater runs that have already completed, as a store that became searchable late would be. Onmainno node ever writes it; with this change every node writes it on its next run.mvn package: 68 tests, 0 failures, 0 errors.main) with separate index names and dictionary prefixes, started at once against a fresh OpenSearch 3.8.0 cluster, five runs each. Released plugin: all three instances came up in 1 of 5 runs, 9 of 15 instances in total; every failed instance hadfile not readablefor one of its own dictionary files. This branch: all three came up in 5 of 5 runs, 15 of 15 instances, and no such error.