1
0
Fork 0

Bug fix/avoid cache double invalidation (#6801)

* avoid query cache double invalidation

* do not invalidate cache twice

* can't use CAS on directory_reader for the time being, protect with mutex instead

* do not lock properties during index consolidation

* reduce scope of _callbacksLock in FlushFeature

* prevent concurrent reader reopen
This commit is contained in:
Andrey Abramov 2018-10-11 14:23:34 +03:00 committed by GitHub
parent bccd10d622
commit 287b44d66f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 32 deletions

View File

@ -670,9 +670,9 @@ IResearchView::IResearchView(
FlushTransaction(toString(*this)), FlushTransaction(toString(*this)),
_asyncFeature(nullptr), _asyncFeature(nullptr),
_asyncSelf(irs::memory::make_unique<AsyncSelf>(this)), _asyncSelf(irs::memory::make_unique<AsyncSelf>(this)),
_asyncTerminate(false),
_meta(std::make_shared<AsyncMeta>()), _meta(std::make_shared<AsyncMeta>()),
_storePersisted(getPersistedPath(dbPathFeature, vocbase, id())), _storePersisted(getPersistedPath(dbPathFeature, vocbase, id())),
_asyncTerminate(false),
_inRecovery(false) { _inRecovery(false) {
// set up in-recovery insertion hooks // set up in-recovery insertion hooks
auto* feature = arangodb::application_features::ApplicationServer::lookupFeature< auto* feature = arangodb::application_features::ApplicationServer::lookupFeature<
@ -683,7 +683,7 @@ IResearchView::IResearchView(
auto view = _asyncSelf; // create copy for lambda auto view = _asyncSelf; // create copy for lambda
feature->registerPostRecoveryCallback([view]()->arangodb::Result { feature->registerPostRecoveryCallback([view]()->arangodb::Result {
auto viewMutex = view->mutex(); auto& viewMutex = view->mutex();
SCOPED_LOCK(viewMutex); // ensure view does not get deallocated before call back finishes SCOPED_LOCK(viewMutex); // ensure view does not get deallocated before call back finishes
auto* viewPtr = view->get(); auto* viewPtr = view->get();
@ -757,9 +757,6 @@ IResearchView::IResearchView(
_asyncFeature->async( _asyncFeature->async(
self(), self(),
[this, state](size_t& timeoutMsec, bool) mutable ->bool { [this, state](size_t& timeoutMsec, bool) mutable ->bool {
auto* store = &_storePersisted;
char const* name = "persistent store";
if (_asyncTerminate.load()) { if (_asyncTerminate.load()) {
return false; // termination requested return false; // termination requested
} }
@ -787,7 +784,7 @@ IResearchView::IResearchView(
if (usedMsec < state._consolidationIntervalMsec) { if (usedMsec < state._consolidationIntervalMsec) {
timeoutMsec = state._consolidationIntervalMsec - usedMsec; // still need to sleep timeoutMsec = state._consolidationIntervalMsec - usedMsec; // still need to sleep
return true; // reschedule (with possibly updated '_commitIntervalMsec') return true; // reschedule (with possibly updated '_consolidationIntervalMsec')
} }
state._last = std::chrono::system_clock::now(); // remember last task start time state._last = std::chrono::system_clock::now(); // remember last task start time
@ -796,19 +793,18 @@ IResearchView::IResearchView(
auto const runCleanupAfterConsolidation = auto const runCleanupAfterConsolidation =
state._cleanupIntervalCount > state._cleanupIntervalStep; state._cleanupIntervalCount > state._cleanupIntervalStep;
ReadMutex mutex(_mutex); // 'store' can be asynchronously modified auto& viewMutex = self()->mutex();
SCOPED_LOCK(mutex); SCOPED_LOCK(viewMutex); // ensure view does not get deallocated before call back finishes
if (store->_directory if (_storePersisted
&& store->_writer
&& consolidateCleanupStore( && consolidateCleanupStore(
*(store->_directory), *_storePersisted._directory,
*(store->_writer), *_storePersisted._writer,
store->_segmentCount, _storePersisted._segmentCount,
state._consolidationPolicy, state._consolidationPolicy,
runCleanupAfterConsolidation, runCleanupAfterConsolidation,
*this, *this,
name) "persistent store")
&& state._cleanupIntervalStep && state._cleanupIntervalStep
&& state._cleanupIntervalCount++ > state._cleanupIntervalStep) { && state._cleanupIntervalCount++ > state._cleanupIntervalStep) {
state._cleanupIntervalCount = 0; state._cleanupIntervalCount = 0;
@ -845,7 +841,7 @@ IResearchView::IResearchView(
} }
ViewStateHelper::commitWrite( ViewStateHelper::commitWrite(
*state, viewRef, arangodb::transaction::Status::COMMITTED != state->status() *state, viewRef, arangodb::transaction::Status::COMMITTED != status
); );
}; };
} }
@ -1273,18 +1269,32 @@ arangodb::Result IResearchView::commit() {
<< "starting persisted-sync sync for arangosearch view '" << name() << "'"; << "starting persisted-sync sync for arangosearch view '" << name() << "'";
_storePersisted._writer->commit(); // finishing flush transaction _storePersisted._writer->commit(); // finishing flush transaction
const auto reader = _storePersisted._reader.reopen(); // update reader
if (reader && reader != _storePersisted._reader) { {
// invalidate query cache if there were some data changes SCOPED_LOCK(_readerLock);
arangodb::aql::QueryCache::instance()->invalidate(
&vocbase(), name()
);
_storePersisted._reader = reader; auto reader = _storePersisted._reader.reopen(); // update reader
_storePersisted._segmentCount = _storePersisted._reader.size(); // add commited segments
if (!reader) {
// nothing more to do
LOG_TOPIC(WARN, arangodb::iresearch::TOPIC)
<< "failed to update snapshot after commit, reuse the existing snapshot for arangosearch view '" << name() << "'";
return {};
}
if (_storePersisted._reader != reader) {
// update reader
_storePersisted._reader = reader;
// invalidate query cache if there were some data changes
arangodb::aql::QueryCache::instance()->invalidate(
&vocbase(), name()
);
}
} }
_storePersisted._segmentCount = _storePersisted._reader.size(); // set commited segments
LOG_TOPIC(TRACE, arangodb::iresearch::TOPIC) LOG_TOPIC(TRACE, arangodb::iresearch::TOPIC)
<< "finished persisted-sync sync for arangosearch view '" << name() << "'"; << "finished persisted-sync sync for arangosearch view '" << name() << "'";

View File

@ -316,8 +316,10 @@ class IResearchView
struct DataStore { struct DataStore {
irs::directory::ptr _directory; irs::directory::ptr _directory;
irs::directory_reader _reader; irs::directory_reader _reader;
irs::index_reader::ptr _readerImpl; // need this for 'std::atomic_exchange_strong'
std::atomic<size_t> _segmentCount{}; // FIXME remove total number of segments in the writer std::atomic<size_t> _segmentCount{}; // FIXME remove total number of segments in the writer
irs::index_writer::ptr _writer; irs::index_writer::ptr _writer;
DataStore() = default; DataStore() = default;
DataStore(DataStore&& other) noexcept; DataStore(DataStore&& other) noexcept;
DataStore& operator=(DataStore&& other) noexcept; DataStore& operator=(DataStore&& other) noexcept;
@ -364,14 +366,15 @@ class IResearchView
IResearchFeature* _asyncFeature; // the feature where async jobs were registered (nullptr == no jobs registered) IResearchFeature* _asyncFeature; // the feature where async jobs were registered (nullptr == no jobs registered)
AsyncSelf::ptr _asyncSelf; // 'this' for the lifetime of the view (for use with asynchronous calls) AsyncSelf::ptr _asyncSelf; // 'this' for the lifetime of the view (for use with asynchronous calls)
std::atomic<bool> _asyncTerminate; // trigger termination of long-running async jobs
std::shared_ptr<AsyncMeta> _meta; // the shared view configuration (never null!!!) std::shared_ptr<AsyncMeta> _meta; // the shared view configuration (never null!!!)
IResearchViewMetaState _metaState; // the per-instance configuration state IResearchViewMetaState _metaState; // the per-instance configuration state
mutable irs::async_utils::read_write_mutex _mutex; // for use with member maps/sets and '_metaState' mutable irs::async_utils::read_write_mutex _mutex; // for use with member maps/sets and '_metaState'
PersistedStore _storePersisted; PersistedStore _storePersisted;
std::mutex _readerLock; // prevents query cache double invalidation
FlushCallback _flushCallback; // responsible for flush callback unregistration FlushCallback _flushCallback; // responsible for flush callback unregistration
std::function<void(arangodb::transaction::Methods& trx, arangodb::transaction::Status status)> _trxReadCallback; // for snapshot(...) std::function<void(arangodb::transaction::Methods& trx, arangodb::transaction::Status status)> _trxReadCallback; // for snapshot(...)
std::function<void(arangodb::transaction::Methods& trx, arangodb::transaction::Status status)> _trxWriteCallback; // for insert(...)/remove(...) std::function<void(arangodb::transaction::Methods& trx, arangodb::transaction::Status status)> _trxWriteCallback; // for insert(...)/remove(...)
std::atomic<bool> _asyncTerminate; // trigger termination of long-running async jobs
std::atomic<bool> _inRecovery; std::atomic<bool> _inRecovery;
}; };

View File

@ -154,15 +154,17 @@ bool FlushFeature::unregisterCallback(void* ptr) {
void FlushFeature::executeCallbacks() { void FlushFeature::executeCallbacks() {
std::vector<FlushTransactionPtr> transactions; std::vector<FlushTransactionPtr> transactions;
READ_LOCKER(locker, _callbacksLock); {
transactions.reserve(_callbacks.size()); READ_LOCKER(locker, _callbacksLock);
transactions.reserve(_callbacks.size());
// execute all callbacks. this will create as many transactions as // execute all callbacks. this will create as many transactions as
// there are callbacks // there are callbacks
for (auto const& cb : _callbacks) { for (auto const& cb : _callbacks) {
// copy elision, std::move(..) not required // copy elision, std::move(..) not required
LOG_TOPIC(TRACE, arangodb::Logger::FLUSH) << "executing flush callback"; LOG_TOPIC(TRACE, arangodb::Logger::FLUSH) << "executing flush callback";
transactions.emplace_back(cb.second()); transactions.emplace_back(cb.second());
}
} }
// TODO: make sure all data is synced // TODO: make sure all data is synced