blob: 157ae4183c4f2fa3548774a123c0f0b4cd3da409 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index;
import java.io.IOException;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.TrackingDirectoryWrapper;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.IOSupplier;
import org.apache.lucene.util.IOUtils;
/**
* This class handles accounting and applying pending deletes for live segment readers
*/
class PendingDeletes {
protected final SegmentCommitInfo info;
// Read-only live docs, null until live docs are initialized or if all docs are alive
private Bits liveDocs;
// Writeable live docs, null if this instance is not ready to accept writes, in which
// case getMutableBits needs to be called
private FixedBitSet writeableLiveDocs;
protected int pendingDeleteCount;
boolean liveDocsInitialized;
PendingDeletes(SegmentReader reader, SegmentCommitInfo info) {
this(info, reader.getLiveDocs(), true);
pendingDeleteCount = reader.numDeletedDocs() - info.getDelCount();
}
PendingDeletes(SegmentCommitInfo info) {
this(info, null, info.hasDeletions() == false);
// if we don't have deletions we can mark it as initialized since we might receive deletes on a segment
// without having a reader opened on it ie. after a merge when we apply the deletes that IW received while merging.
// For segments that were published we enforce a reader in the BufferedUpdatesStream.SegmentState ctor
}
PendingDeletes(SegmentCommitInfo info, Bits liveDocs, boolean liveDocsInitialized) {
this.info = info;
this.liveDocs = liveDocs;
pendingDeleteCount = 0;
this.liveDocsInitialized = liveDocsInitialized;
}
protected FixedBitSet getMutableBits() {
// if we pull mutable bits but we haven't been initialized something is completely off.
// this means we receive deletes without having the bitset that is on-disk ready to be cloned
assert liveDocsInitialized : "can't delete if liveDocs are not initialized";
if (writeableLiveDocs == null) {
// Copy on write: this means we've cloned a
// SegmentReader sharing the current liveDocs
// instance; must now make a private clone so we can
// change it:
if (liveDocs != null) {
writeableLiveDocs = FixedBitSet.copyOf(liveDocs);
} else {
writeableLiveDocs = new FixedBitSet(info.info.maxDoc());
writeableLiveDocs.set(0, info.info.maxDoc());
}
liveDocs = writeableLiveDocs.asReadOnlyBits();
}
return writeableLiveDocs;
}
/**
* Marks a document as deleted in this segment and return true if a document got actually deleted or
* if the document was already deleted.
*/
boolean delete(int docID) throws IOException {
assert info.info.maxDoc() > 0;
FixedBitSet mutableBits = getMutableBits();
assert mutableBits != null;
assert docID >= 0 && docID < mutableBits.length() : "out of bounds: docid=" + docID + " liveDocsLength=" + mutableBits.length() + " seg=" + info.info.name + " maxDoc=" + info.info.maxDoc();
final boolean didDelete = mutableBits.get(docID);
if (didDelete) {
mutableBits.clear(docID);
pendingDeleteCount++;
}
return didDelete;
}
/**
* Returns a snapshot of the current live docs.
*/
Bits getLiveDocs() {
// Prevent modifications to the returned live docs
writeableLiveDocs = null;
return liveDocs;
}
/**
* Returns a snapshot of the hard live docs.
*/
Bits getHardLiveDocs() {
return getLiveDocs();
}
/**
* Returns the number of pending deletes that are not written to disk.
*/
protected int numPendingDeletes() {
return pendingDeleteCount;
}
/**
* Called once a new reader is opened for this segment ie. when deletes or updates are applied.
*/
void onNewReader(CodecReader reader, SegmentCommitInfo info) throws IOException {
if (liveDocsInitialized == false) {
assert writeableLiveDocs == null;
if (reader.hasDeletions()) {
// we only initialize this once either in the ctor or here
// if we use the live docs from a reader it has to be in a situation where we don't
// have any existing live docs
assert pendingDeleteCount == 0 : "pendingDeleteCount: " + pendingDeleteCount;
liveDocs = reader.getLiveDocs();
assert liveDocs == null || assertCheckLiveDocs(liveDocs, info.info.maxDoc(), info.getDelCount());
}
liveDocsInitialized = true;
}
}
private boolean assertCheckLiveDocs(Bits bits, int expectedLength, int expectedDeleteCount) {
assert bits.length() == expectedLength;
int deletedCount = 0;
for (int i = 0; i < bits.length(); i++) {
if (bits.get(i) == false) {
deletedCount++;
}
}
assert deletedCount == expectedDeleteCount : "deleted: " + deletedCount + " != expected: " + expectedDeleteCount;
return true;
}
/**
* Resets the pending docs
*/
void dropChanges() {
pendingDeleteCount = 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PendingDeletes(seg=").append(info);
sb.append(" numPendingDeletes=").append(pendingDeleteCount);
sb.append(" writeable=").append(writeableLiveDocs != null);
return sb.toString();
}
/**
* Writes the live docs to disk and returns <code>true</code> if any new docs were written.
*/
boolean writeLiveDocs(Directory dir) throws IOException {
if (pendingDeleteCount == 0) {
return false;
}
Bits liveDocs = this.liveDocs;
assert liveDocs != null;
// We have new deletes
assert liveDocs.length() == info.info.maxDoc();
// Do this so we can delete any created files on
// exception; this saves all codecs from having to do
// it:
TrackingDirectoryWrapper trackingDir = new TrackingDirectoryWrapper(dir);
// We can write directly to the actual name (vs to a
// .tmp & renaming it) because the file is not live
// until segments file is written:
boolean success = false;
try {
Codec codec = info.info.getCodec();
codec.liveDocsFormat().writeLiveDocs(liveDocs, trackingDir, info, pendingDeleteCount, IOContext.DEFAULT);
success = true;
} finally {
if (!success) {
// Advance only the nextWriteDelGen so that a 2nd
// attempt to write will write to a new file
info.advanceNextWriteDelGen();
// Delete any partially created file(s):
for (String fileName : trackingDir.getCreatedFiles()) {
IOUtils.deleteFilesIgnoringExceptions(dir, fileName);
}
}
}
// If we hit an exc in the line above (eg disk full)
// then info's delGen remains pointing to the previous
// (successfully written) del docs:
info.advanceDelGen();
info.setDelCount(info.getDelCount() + pendingDeleteCount);
dropChanges();
return true;
}
/**
* Returns <code>true</code> iff the segment represented by this {@link PendingDeletes} is fully deleted
*/
boolean isFullyDeleted(IOSupplier<CodecReader> readerIOSupplier) throws IOException {
return getDelCount() == info.info.maxDoc();
}
/**
* Called for every field update for the given field at flush time
* @param info the field info of the field that's updated
* @param iterator the values to apply
*/
void onDocValuesUpdate(FieldInfo info, DocValuesFieldUpdates.Iterator iterator) throws IOException {
}
int numDeletesToMerge(MergePolicy policy, IOSupplier<CodecReader> readerIOSupplier) throws IOException {
return policy.numDeletesToMerge(info, getDelCount(), readerIOSupplier);
}
/**
* Returns true if the given reader needs to be refreshed in order to see the latest deletes
*/
final boolean needsRefresh(CodecReader reader) {
return reader.getLiveDocs() != getLiveDocs() || reader.numDeletedDocs() != getDelCount();
}
/**
* Returns the number of deleted docs in the segment.
*/
final int getDelCount() {
int delCount = info.getDelCount() + info.getSoftDelCount() + numPendingDeletes();
return delCount;
}
/**
* Returns the number of live documents in this segment
*/
final int numDocs() {
return info.info.maxDoc() - getDelCount();
}
// Call only from assert!
boolean verifyDocCounts(CodecReader reader) {
int count = 0;
Bits liveDocs = getLiveDocs();
if (liveDocs != null) {
for(int docID = 0; docID < info.info.maxDoc(); docID++) {
if (liveDocs.get(docID)) {
count++;
}
}
} else {
count = info.info.maxDoc();
}
assert numDocs() == count: "info.maxDoc=" + info.info.maxDoc() + " info.getDelCount()=" + info.getDelCount() +
" info.getSoftDelCount()=" + info.getSoftDelCount() +
" pendingDeletes=" + toString() + " count=" + count + " numDocs: " + numDocs();
assert reader.numDocs() == numDocs() : "reader.numDocs() = " + reader.numDocs() + " numDocs() " + numDocs();
assert reader.numDeletedDocs() <= info.info.maxDoc(): "delCount=" + reader.numDeletedDocs() + " info.maxDoc=" +
info.info.maxDoc() + " rld.pendingDeleteCount=" + numPendingDeletes() +
" info.getDelCount()=" + info.getDelCount();
return true;
}
/**
* Returns {@code true} if we have to initialize this PendingDeletes before {@link #delete(int)};
* otherwise this PendingDeletes is ready to accept deletes. A PendingDeletes can be initialized
* by providing it a reader via {@link #onNewReader(CodecReader, SegmentCommitInfo)}.
*/
boolean mustInitOnDelete() {
return false;
}
}