private void pruneDeletedTombstones() { long timeMSec = engineConfig.getThreadPool().estimatedTimeInMillis(); // TODO: not good that we reach into LiveVersionMap here; can we move this inside VersionMap // instead? problem is the dirtyLock... // we only need to prune the deletes map; the current/old version maps are cleared on refresh: for (Map.Entry<BytesRef, VersionValue> entry : versionMap.getAllTombstones()) { BytesRef uid = entry.getKey(); synchronized ( dirtyLock( uid)) { // can we do it without this lock on each value? maybe batch to a set and get // the lock once per set? // Must re-get it here, vs using entry.getValue(), in case the uid was indexed/deleted since // we pulled the iterator: VersionValue versionValue = versionMap.getTombstoneUnderLock(uid); if (versionValue != null) { if (timeMSec - versionValue.time() > getGcDeletesInMillis()) { versionMap.removeTombstoneUnderLock(uid); } } } } lastDeleteVersionPruneTimeMSec = timeMSec; }
private boolean innerIndex(Index index) throws IOException { synchronized (dirtyLock(index.uid())) { lastWriteNanos = index.startTime(); final long currentVersion; final boolean deleted; VersionValue versionValue = versionMap.getUnderLock(index.uid().bytes()); if (versionValue == null) { currentVersion = loadCurrentVersionFromIndex(index.uid()); deleted = currentVersion == Versions.NOT_FOUND; } else { deleted = versionValue.delete(); if (engineConfig.isEnableGcDeletes() && versionValue.delete() && (engineConfig.getThreadPool().estimatedTimeInMillis() - versionValue.time()) > getGcDeletesInMillis()) { currentVersion = Versions.NOT_FOUND; // deleted, and GC } else { currentVersion = versionValue.version(); } } long expectedVersion = index.version(); if (isVersionConflictForWrites(index, currentVersion, deleted, expectedVersion)) { if (index.origin() != Operation.Origin.RECOVERY) { throw new VersionConflictEngineException( shardId, index.type(), index.id(), index .versionType() .explainConflictForWrites(currentVersion, expectedVersion, deleted)); } return false; } long updatedVersion = index.versionType().updateVersion(currentVersion, expectedVersion); final boolean created; index.updateVersion(updatedVersion); if (currentVersion == Versions.NOT_FOUND) { // document does not exists, we can optimize for create created = true; index(index, indexWriter); } else { created = update(index, versionValue, indexWriter); } Translog.Location translogLocation = translog.add(new Translog.Index(index)); versionMap.putUnderLock( index.uid().bytes(), new VersionValue(updatedVersion, translogLocation)); index.setTranslogLocation(translogLocation); return created; } }
@Override public GetResult get(Get get, Function<String, Searcher> searcherFactory) throws EngineException { try (ReleasableLock lock = readLock.acquire()) { ensureOpen(); if (get.realtime()) { VersionValue versionValue = versionMap.getUnderLock(get.uid().bytes()); if (versionValue != null) { if (versionValue.delete()) { return GetResult.NOT_EXISTS; } if (get.versionType().isVersionConflictForReads(versionValue.version(), get.version())) { Uid uid = Uid.createUid(get.uid().text()); throw new VersionConflictEngineException( shardId, uid.type(), uid.id(), get.versionType().explainConflictForReads(versionValue.version(), get.version())); } Translog.Operation op = translog.read(versionValue.translogLocation()); if (op != null) { return new GetResult(true, versionValue.version(), op.getSource()); } } } // no version, get the version from the index, we know that we refresh on flush return getFromSearcher(get, searcherFactory); } }
private static boolean update(Index index, VersionValue versionValue, IndexWriter indexWriter) throws IOException { boolean created; if (versionValue != null) { created = versionValue.delete(); // we have a delete which is not GC'ed... } else { created = false; } if (index.docs().size() > 1) { indexWriter.updateDocuments(index.uid(), index.docs()); } else { indexWriter.updateDocument(index.uid(), index.docs().get(0)); } return created; }
private void innerDelete(Delete delete) throws IOException { synchronized (dirtyLock(delete.uid())) { lastWriteNanos = delete.startTime(); final long currentVersion; final boolean deleted; VersionValue versionValue = versionMap.getUnderLock(delete.uid().bytes()); if (versionValue == null) { currentVersion = loadCurrentVersionFromIndex(delete.uid()); deleted = currentVersion == Versions.NOT_FOUND; } else { deleted = versionValue.delete(); if (engineConfig.isEnableGcDeletes() && versionValue.delete() && (engineConfig.getThreadPool().estimatedTimeInMillis() - versionValue.time()) > getGcDeletesInMillis()) { currentVersion = Versions.NOT_FOUND; // deleted, and GC } else { currentVersion = versionValue.version(); } } long updatedVersion; long expectedVersion = delete.version(); if (delete .versionType() .isVersionConflictForWrites(currentVersion, expectedVersion, deleted)) { if (delete.origin() == Operation.Origin.RECOVERY) { return; } else { throw new VersionConflictEngineException( shardId, delete.type(), delete.id(), delete .versionType() .explainConflictForWrites(currentVersion, expectedVersion, deleted)); } } updatedVersion = delete.versionType().updateVersion(currentVersion, expectedVersion); final boolean found; if (currentVersion == Versions.NOT_FOUND) { // doc does not exist and no prior deletes found = false; } else if (versionValue != null && versionValue.delete()) { // a "delete on delete", in this case, we still increment the version, log it, and return // that version found = false; } else { // we deleted a currently existing document indexWriter.deleteDocuments(delete.uid()); found = true; } delete.updateVersion(updatedVersion, found); Translog.Location translogLocation = translog.add(new Translog.Delete(delete)); versionMap.putUnderLock( delete.uid().bytes(), new DeleteVersionValue( updatedVersion, engineConfig.getThreadPool().estimatedTimeInMillis(), translogLocation)); delete.setTranslogLocation(translogLocation); } }