OPENDJ-2016 Implement new on disk merge import strategy based on storage engine
OnDiskMergeStorageImporter now writes all keys/values to the Importer.
Added various toKey() and toValue() methods to the index classes.
OnDiskMergeStorageImporter.java
Write all keys/values to the Importer.
Rewrote the code inherited from OnDiskMergeBufferImporter + simplified the code
DN2ID.java:
Renamed dnToKey() to toKey().
DN2URI.java:
Added toKey() and toValue().
Used try-with-resources + diamond operator.
VLVIndex.java:
Extracted toKey() from encodeVLVKey().
Used try-with-resources.
VerifyJob.java:
Consequence of the change to VLVIndex.encodeKey().
Code cleanup.
IndexBuffer.java:
Code cleanup
| | |
| | | * for each entry. The key is the normalized entry DN and the value |
| | | * is the entry ID. |
| | | */ |
| | | @SuppressWarnings("javadoc") |
| | | class DN2ID extends AbstractTree |
| | | { |
| | | private static final Function<ByteString, Void, DirectoryException> TO_VOID_KEY = |
| | |
| | | |
| | | private final DN baseDN; |
| | | |
| | | |
| | | /** |
| | | * Create a DN2ID instance for in a given entryContainer. |
| | | * |
| | |
| | | */ |
| | | void put(final WriteableTransaction txn, DN dn, final EntryID entryID) throws StorageRuntimeException |
| | | { |
| | | txn.put(getName(), dnToKey(dn), entryID.toByteString()); |
| | | txn.put(getName(), toKey(dn), entryID.toByteString()); |
| | | } |
| | | |
| | | boolean insert(final WriteableTransaction txn, DN dn, final EntryID entryID) throws StorageRuntimeException |
| | | { |
| | | return txn.update(getName(), dnToKey(dn), new UpdateFunction() |
| | | return txn.update(getName(), toKey(dn), new UpdateFunction() |
| | | { |
| | | @Override |
| | | public ByteSequence computeNewValue(ByteSequence oldEntryID) |
| | |
| | | }); |
| | | } |
| | | |
| | | private ByteString dnToKey(DN dn) { |
| | | ByteString toKey(DN dn) |
| | | { |
| | | return dnToDNKey(dn, baseDN.size()); |
| | | } |
| | | |
| | |
| | | */ |
| | | boolean remove(WriteableTransaction txn, DN dn) throws StorageRuntimeException |
| | | { |
| | | return txn.delete(getName(), dnToKey(dn)); |
| | | return txn.delete(getName(), toKey(dn)); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | EntryID get(ReadableTransaction txn, DN dn) throws StorageRuntimeException |
| | | { |
| | | final ByteString value = txn.read(getName(), dnToKey(dn)); |
| | | final ByteString value = txn.read(getName(), toKey(dn)); |
| | | return value != null ? new EntryID(value) : null; |
| | | } |
| | | |
| | |
| | | |
| | | private Cursor<ByteString, ByteString> openCursor0(ReadableTransaction txn, DN dn) { |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | cursor.positionToKey(dnToKey(dn)); |
| | | cursor.positionToKey(toKey(dn)); |
| | | return cursor; |
| | | } |
| | | |
| | |
| | | return new SubtreeCursor(openCursor0(txn, dn)); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Check if two DN have a parent-child relationship. |
| | | * |
| | |
| | | public boolean next() |
| | | { |
| | | if (cursorOnParent) { |
| | | /** Go to the first children */ |
| | | // Go to the first children |
| | | delegate.next(); |
| | | cursorOnParent = false; |
| | | } else { |
| | | /** Go to the next sibling */ |
| | | // Go to the next sibling |
| | | delegate.positionToKeyOrNext(nextSibling()); |
| | | } |
| | | return isDefined() && delegate.getKey().startsWith(parentDN); |
| | |
| | | * as in dn2id so that all referrals in a subtree can be retrieved by cursoring |
| | | * through a range of the records. |
| | | */ |
| | | @SuppressWarnings("javadoc") |
| | | class DN2URI extends AbstractTree |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | |
| | | * The standard attribute type that is used to specify the set of referral |
| | | * URLs in a referral entry. |
| | | */ |
| | | private final AttributeType referralType = |
| | | DirectoryServer.getAttributeType(ATTR_REFERRAL_URL); |
| | | private final AttributeType referralType = DirectoryServer.getAttributeType(ATTR_REFERRAL_URL); |
| | | |
| | | /** |
| | | * A flag that indicates whether there are any referrals contained in this |
| | | * tree. It should only be set to {@code false} when it is known that |
| | | * there are no referrals. |
| | | */ |
| | | private volatile ConditionResult containsReferrals = |
| | | ConditionResult.UNDEFINED; |
| | | |
| | | private volatile ConditionResult containsReferrals = ConditionResult.UNDEFINED; |
| | | |
| | | /** |
| | | * Create a new object representing a referral tree in a given |
| | |
| | | prefixRDNComponents = entryContainer.getBaseDN().size(); |
| | | } |
| | | |
| | | /** Encodes the value. */ |
| | | private ByteSequence encode(DN dn, Collection<String> col) |
| | | { |
| | | if (col != null && !col.isEmpty()) |
| | | { |
| | | ByteStringBuilder b = new ByteStringBuilder(); |
| | | // encode the dn inside the value |
| | | // because the dn is encoded in a non reversible way in the key |
| | | byte[] dnBytes = StaticUtils.getBytes(dn.toString()); |
| | | b.append(dnBytes.length); |
| | | b.append(dnBytes); |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** Decodes the value as a pair where the first element is the DN key and the second is the actual value. */ |
| | | private Pair<DN, List<String>> decode(ByteSequence bs) throws StorageRuntimeException |
| | | { |
| | | if (!bs.isEmpty()) |
| | |
| | | throw new StorageRuntimeException("Unable to decode DN from binary value", e); |
| | | } |
| | | final int nbElems = r.getInt(); |
| | | List<String> results = new ArrayList<String>(nbElems); |
| | | List<String> results = new ArrayList<>(nbElems); |
| | | for (int i = 0; i < nbElems; i++) |
| | | { |
| | | final int stringLength = r.getInt(); |
| | |
| | | */ |
| | | private ConditionResult containsReferrals(ReadableTransaction txn) |
| | | { |
| | | Cursor<?, ?> cursor = txn.openCursor(getName()); |
| | | try |
| | | try (Cursor<?, ?> cursor = txn.openCursor(getName())) |
| | | { |
| | | return ConditionResult.valueOf(cursor.next()); |
| | | } |
| | |
| | | |
| | | return ConditionResult.UNDEFINED; |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | private List<String> toStrings(Attribute a) |
| | | { |
| | | List<String> results = new ArrayList<String>(a.size()); |
| | | List<String> results = new ArrayList<>(a.size()); |
| | | for (ByteString v : a) |
| | | { |
| | | results.add(v.toString()); |
| | |
| | | Set<String> referralURLs = entry.getReferralURLs(); |
| | | if (referralURLs != null) |
| | | { |
| | | throwReferralException(entry.getName(), entry.getName(), referralURLs, |
| | | searchScope); |
| | | throwReferralException(entry.getName(), entry.getName(), referralURLs, searchScope); |
| | | } |
| | | } |
| | | |
| | |
| | | * in the referral entry. |
| | | */ |
| | | private void throwReferralException(DN targetDN, DN referralDN, Collection<String> labeledURIs, |
| | | SearchScope searchScope) |
| | | throws DirectoryException |
| | | SearchScope searchScope) throws DirectoryException |
| | | { |
| | | ArrayList<String> URIList = new ArrayList<String>(labeledURIs.size()); |
| | | ArrayList<String> URIList = new ArrayList<>(labeledURIs.size()); |
| | | for (String labeledURI : labeledURIs) |
| | | { |
| | | // Remove the label part of the labeled URI if there is a label. |
| | |
| | | DN urlBaseDN = targetDN; |
| | | if (!referralDN.equals(ldapurl.getBaseDN())) |
| | | { |
| | | urlBaseDN = |
| | | EntryContainer.modDN(targetDN, |
| | | referralDN.size(), |
| | | ldapurl.getBaseDN()); |
| | | urlBaseDN = EntryContainer.modDN(targetDN, referralDN.size(), ldapurl.getBaseDN()); |
| | | } |
| | | ldapurl.setBaseDN(urlBaseDN); |
| | | if (searchScope == null) |
| | |
| | | return; |
| | | } |
| | | |
| | | try |
| | | try (Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) |
| | | { |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | try |
| | | // Go up through the DIT hierarchy until we find a referral. |
| | | for (DN dn = getParentWithinBase(targetDN); dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | // Go up through the DIT hierarchy until we find a referral. |
| | | for (DN dn = getParentWithinBase(targetDN); dn != null; dn = getParentWithinBase(dn)) |
| | | // Look for a record whose key matches the current DN. |
| | | if (cursor.positionToKey(toKey(dn))) |
| | | { |
| | | // Look for a record whose key matches the current DN. |
| | | if (cursor.positionToKey(toKey(dn))) |
| | | { |
| | | // Construct a set of all the labeled URIs in the referral. |
| | | final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue()); |
| | | Collection<String> labeledURIs = dnAndUris.getSecond(); |
| | | throwReferralException(targetDN, dn, labeledURIs, searchScope); |
| | | } |
| | | // Construct a set of all the labeled URIs in the referral. |
| | | final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue()); |
| | | Collection<String> labeledURIs = dnAndUris.getSecond(); |
| | | throwReferralException(targetDN, dn, labeledURIs, searchScope); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | } |
| | | } |
| | | catch (StorageRuntimeException e) |
| | | { |
| | |
| | | ByteStringBuilder suffix = beforeKey(baseDN); |
| | | ByteStringBuilder end = afterKey(baseDN); |
| | | |
| | | try |
| | | try (Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) |
| | | { |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | try |
| | | // Initialize the cursor very close to the starting value then |
| | | // step forward until we pass the ending value. |
| | | boolean success = cursor.positionToKey(suffix); |
| | | while (success && cursor.getKey().compareTo(end) < 0) |
| | | { |
| | | // Initialize the cursor very close to the starting value then |
| | | // step forward until we pass the ending value. |
| | | boolean success = cursor.positionToKey(suffix); |
| | | while (success && cursor.getKey().compareTo(end) < 0) |
| | | // We have found a subordinate referral. |
| | | // Make sure the referral is within scope. |
| | | if (searchOp.getScope() == SearchScope.SINGLE_LEVEL |
| | | && DnKeyFormat.findDNKeyParent(cursor.getKey()) != baseDN.length()) |
| | | { |
| | | // We have found a subordinate referral. |
| | | // Make sure the referral is within scope. |
| | | if (searchOp.getScope() == SearchScope.SINGLE_LEVEL |
| | | && DnKeyFormat.findDNKeyParent(cursor.getKey()) != baseDN.length()) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | // Construct a list of all the URIs in the referral. |
| | | final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue()); |
| | | final DN dn = dnAndUris.getFirst(); |
| | | final Collection<String> labeledURIs = dnAndUris.getSecond(); |
| | | SearchResultReference reference = toSearchResultReference(dn, labeledURIs, searchOp.getScope()); |
| | | if (!searchOp.returnReference(dn, reference)) |
| | | { |
| | | return false; |
| | | } |
| | | success = cursor.next(); |
| | | continue; |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | |
| | | // Construct a list of all the URIs in the referral. |
| | | final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue()); |
| | | final DN dn = dnAndUris.getFirst(); |
| | | final Collection<String> labeledURIs = dnAndUris.getSecond(); |
| | | SearchResultReference reference = toSearchResultReference(dn, labeledURIs, searchOp.getScope()); |
| | | if (!searchOp.returnReference(dn, reference)) |
| | | { |
| | | return false; |
| | | } |
| | | success = cursor.next(); |
| | | } |
| | | } |
| | | catch (StorageRuntimeException e) |
| | |
| | | |
| | | private SearchResultReference toSearchResultReference(DN dn, Collection<String> labeledURIs, SearchScope scope) |
| | | { |
| | | ArrayList<String> URIList = new ArrayList<String>(labeledURIs.size()); |
| | | ArrayList<String> URIList = new ArrayList<>(labeledURIs.size()); |
| | | for (String labeledURI : labeledURIs) |
| | | { |
| | | // Remove the label part of the labeled URI if there is a label. |
| | |
| | | return new SearchResultReference(URIList); |
| | | } |
| | | |
| | | private ByteString toKey(DN dn) |
| | | ByteString toKey(DN dn) |
| | | { |
| | | return DnKeyFormat.dnToDNKey(dn, prefixRDNComponents); |
| | | } |
| | | |
| | | ByteSequence toValue(DN dn, Entry entry) |
| | | { |
| | | // FIXME JNR This is not very efficient: |
| | | // getReferralsURL() converts from bytestring into string |
| | | // and the code down below then does the reverse |
| | | return encode(dn, entry.getReferralURLs()); |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.*; |
| | | |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | | import java.util.Map.Entry; |
| | | import java.util.TreeMap; |
| | | import java.util.TreeSet; |
| | | |
| | |
| | | /** |
| | | * A buffered index is used to buffer multiple reads or writes to the |
| | | * same index key into a single read or write. |
| | | * <p> |
| | | * It can only be used to buffer multiple reads and writes under |
| | | * the same transaction. The transaction may be null if it is known |
| | | * that there are no other concurrent updates to the index. |
| | | */ |
| | | @SuppressWarnings("javadoc") |
| | | class IndexBuffer |
| | | { |
| | | private final EntryContainer entryContainer; |
| | |
| | | * The buffered records stored as a map from the record key to the |
| | | * buffered value for that key for each index. |
| | | */ |
| | | private final LinkedHashMap<Index, TreeMap<ByteString, BufferedIndexValues>> bufferedIndexes = |
| | | new LinkedHashMap<Index, TreeMap<ByteString, BufferedIndexValues>>(); |
| | | private final LinkedHashMap<Index, TreeMap<ByteString, BufferedIndexValues>> bufferedIndexes = new LinkedHashMap<>(); |
| | | |
| | | /** The buffered records stored as a set of buffered VLV values for each index. */ |
| | | private final LinkedHashMap<VLVIndex, BufferedVLVIndexValues> bufferedVLVIndexes = |
| | | new LinkedHashMap<VLVIndex, BufferedVLVIndexValues>(); |
| | | private final LinkedHashMap<VLVIndex, BufferedVLVIndexValues> bufferedVLVIndexes = new LinkedHashMap<>(); |
| | | |
| | | /** |
| | | * A simple class representing a pair of added and deleted indexed IDs. Initially both addedIDs |
| | |
| | | { |
| | | if (addedSortKeys == null) |
| | | { |
| | | addedSortKeys = new TreeSet<ByteString>(); |
| | | addedSortKeys = new TreeSet<>(); |
| | | } |
| | | addedSortKeys.add(sortKey); |
| | | } |
| | |
| | | { |
| | | if (deletedSortKeys == null) |
| | | { |
| | | deletedSortKeys = new TreeSet<ByteString>(); |
| | | deletedSortKeys = new TreeSet<>(); |
| | | } |
| | | deletedSortKeys.add(sortKey); |
| | | } |
| | |
| | | |
| | | private BufferedIndexValues createOrGetBufferedIndexValues(Index index, ByteString keyBytes) |
| | | { |
| | | BufferedIndexValues values = null; |
| | | Map<ByteString, BufferedIndexValues> bufferedOperations = createOrGetBufferedOperations(index); |
| | | |
| | | TreeMap<ByteString, BufferedIndexValues> bufferedOperations = bufferedIndexes.get(index); |
| | | if (bufferedOperations == null) |
| | | { |
| | | bufferedOperations = new TreeMap<ByteString, BufferedIndexValues>(); |
| | | bufferedIndexes.put(index, bufferedOperations); |
| | | } |
| | | else |
| | | { |
| | | values = bufferedOperations.get(keyBytes); |
| | | } |
| | | |
| | | BufferedIndexValues values = bufferedOperations.get(keyBytes); |
| | | if (values == null) |
| | | { |
| | | values = new BufferedIndexValues(); |
| | |
| | | return values; |
| | | } |
| | | |
| | | private Map<ByteString, BufferedIndexValues> createOrGetBufferedOperations(Index index) |
| | | { |
| | | TreeMap<ByteString, BufferedIndexValues> bufferedOperations = bufferedIndexes.get(index); |
| | | if (bufferedOperations == null) |
| | | { |
| | | bufferedOperations = new TreeMap<>(); |
| | | bufferedIndexes.put(index, bufferedOperations); |
| | | } |
| | | return bufferedOperations; |
| | | } |
| | | |
| | | /** |
| | | * Flush the buffered index changes to storage. |
| | | * |
| | |
| | | void flush(WriteableTransaction txn) throws StorageRuntimeException, DirectoryException |
| | | { |
| | | /* |
| | | * FIXME: this seems like a surprising way to update the indexes. Why not |
| | | * store the buffered changes in a TreeMap in order to have a predictable |
| | | * iteration order? |
| | | * FIXME: this seems like a surprising way to update the indexes. Why not store the buffered |
| | | * changes in a TreeMap in order to have a predictable iteration order? |
| | | */ |
| | | for (AttributeIndex attributeIndex : entryContainer.getAttributeIndexes()) |
| | | { |
| | |
| | | createOrGetBufferedIndexValues(index, key).deleteEntryID(entryID); |
| | | } |
| | | |
| | | private void flushIndex(Index index, WriteableTransaction txn, |
| | | Map<ByteString, BufferedIndexValues> bufferedValues) |
| | | private void flushIndex(Index index, WriteableTransaction txn, Map<ByteString, BufferedIndexValues> bufferedValues) |
| | | { |
| | | if (bufferedValues != null) |
| | | { |
| | | final Iterator<Map.Entry<ByteString, BufferedIndexValues>> it = bufferedValues.entrySet().iterator(); |
| | | while (it.hasNext()) |
| | | for (Entry<ByteString, BufferedIndexValues> entry : bufferedValues.entrySet()) |
| | | { |
| | | final Map.Entry<ByteString, BufferedIndexValues> entry = it.next(); |
| | | final ByteString key = entry.getKey(); |
| | | final BufferedIndexValues values = entry.getValue(); |
| | | index.update(txn, key, values.deletedEntryIDs, values.addedEntryIDs); |
| | | it.remove(); |
| | | } |
| | | bufferedValues.clear(); |
| | | } |
| | | } |
| | | } |
| | |
| | | import org.opends.server.backends.pluggable.AttributeIndex.MatchingRuleIndex; |
| | | import org.opends.server.backends.pluggable.ImportLDIFReader.EntryInformation; |
| | | import org.opends.server.backends.pluggable.OnDiskMergeBufferImporter.DNCache; |
| | | import org.opends.server.backends.pluggable.OnDiskMergeBufferImporter.IndexKey; |
| | | import org.opends.server.backends.pluggable.spi.Cursor; |
| | | import org.opends.server.backends.pluggable.spi.Importer; |
| | | import org.opends.server.backends.pluggable.spi.ReadOperation; |
| | |
| | | processDN2ID(suffix, entry.getName(), entryID); |
| | | } |
| | | processDN2URI(suffix, entry); |
| | | processIndexes(suffix, entry, entryID, false); |
| | | processIndexes(suffix, entry, entryID); |
| | | processVLVIndexes(suffix, entry, entryID); |
| | | // FIXME JNR run a dedicated thread to do the puts ordered by entryID |
| | | // suffix.getID2Entry().put(importer, entryID, entry); |
| | |
| | | return true; |
| | | } |
| | | |
| | | void processIndexes(Suffix suffix, Entry entry, EntryID entryID, boolean allIndexes) |
| | | void processDN2ID(Suffix suffix, DN dn, EntryID entryID) |
| | | { |
| | | DN2ID dn2id = suffix.getDN2ID(); |
| | | importer.put(dn2id.getName(), dn2id.toKey(dn), entryID.toByteString()); |
| | | } |
| | | |
| | | private void processDN2URI(Suffix suffix, Entry entry) |
| | | { |
| | | DN2URI dn2uri = suffix.getDN2URI(); |
| | | DN entryDN = entry.getName(); |
| | | ByteSequence value = dn2uri.toValue(entryDN, entry); |
| | | if (value != null) |
| | | { |
| | | importer.put(dn2uri.getName(), dn2uri.toKey(entryDN), value); |
| | | } |
| | | } |
| | | |
| | | void processIndexes(Suffix suffix, Entry entry, EntryID entryID) |
| | | throws StorageRuntimeException, InterruptedException |
| | | { |
| | | final ByteString value = entryID.toByteString(); |
| | | for (Map.Entry<AttributeType, AttributeIndex> mapEntry : suffix.getAttrIndexMap().entrySet()) |
| | | { |
| | | AttributeType attrType = mapEntry.getKey(); |
| | | AttributeIndex attrIndex = mapEntry.getValue(); |
| | | if (allIndexes || entry.hasAttribute(attrType)) |
| | | final AttributeType attrType = mapEntry.getKey(); |
| | | final AttributeIndex attrIndex = mapEntry.getValue(); |
| | | if (entry.hasAttribute(attrType)) |
| | | { |
| | | for (Map.Entry<String, MatchingRuleIndex> mapEntry2 : attrIndex.getNameToIndexes().entrySet()) |
| | | for (MatchingRuleIndex index : attrIndex.getNameToIndexes().values()) |
| | | { |
| | | String indexID = mapEntry2.getKey(); |
| | | MatchingRuleIndex index = mapEntry2.getValue(); |
| | | |
| | | IndexKey indexKey = new IndexKey(attrType, indexID, index.getIndexEntryLimit()); |
| | | processAttribute(index, entry, entryID, indexKey); |
| | | for (ByteString key : index.indexEntry(entry)) |
| | | { |
| | | importer.put(index.getName(), key, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | void processVLVIndexes(Suffix suffix, Entry entry, EntryID entryID) |
| | | throws DirectoryException |
| | | { |
| | | final EntryContainer entryContainer = suffix.getEntryContainer(); |
| | | final IndexBuffer buffer = new IndexBuffer(entryContainer); |
| | | for (VLVIndex vlvIdx : entryContainer.getVLVIndexes()) |
| | | for (VLVIndex vlvIndex : suffix.getEntryContainer().getVLVIndexes()) |
| | | { |
| | | vlvIdx.addEntry(buffer, entryID, entry); |
| | | ByteString key = vlvIndex.toKey(entry, entryID); |
| | | importer.put(vlvIndex.getName(), key, ByteString.empty()); |
| | | } |
| | | // buffer.flush(txn); // TODO JNR do something about it |
| | | } |
| | | |
| | | void processAttribute(MatchingRuleIndex index, Entry entry, EntryID entryID, IndexKey indexKey) |
| | | throws StorageRuntimeException, InterruptedException |
| | | { |
| | | for (ByteString key : index.indexEntry(entry)) |
| | | { |
| | | processKey(index, key, entryID, indexKey); |
| | | } |
| | | } |
| | | |
| | | final int processKey(Tree tree, ByteString key, EntryID entryID, IndexKey indexKey) throws InterruptedException |
| | | { |
| | | // TODO JNR implement |
| | | return -1; |
| | | } |
| | | |
| | | void processDN2ID(Suffix suffix, DN dn, EntryID entryID) |
| | | { |
| | | // TODO JNR implement |
| | | } |
| | | |
| | | private void processDN2URI(Suffix suffix, Entry entry) |
| | | { |
| | | // TODO JNR implement |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | |
| | | /** The VLV vlvIndex configuration. */ |
| | | private BackendVLVIndexCfg config; |
| | | |
| | |
| | | { |
| | | if (shouldInclude(entry)) |
| | | { |
| | | buffer.put(this, encodeVLVKey(entry, entryID.longValue())); |
| | | buffer.put(this, toKey(entry, entryID)); |
| | | } |
| | | } |
| | | |
| | | ByteString toKey(final Entry entry, final EntryID entryID) |
| | | { |
| | | return encodeVLVKey(entry, entryID.longValue()); |
| | | } |
| | | |
| | | private boolean shouldInclude(final Entry entry) throws DirectoryException |
| | | { |
| | | return entry.getName().matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry); |
| | |
| | | { |
| | | if (shouldInclude(entry)) |
| | | { |
| | | buffer.remove(this, encodeVLVKey(entry, entryID.longValue())); |
| | | buffer.remove(this, toKey(entry, entryID)); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | private EntryIDSet evaluateNonVLVRequest(final ReadableTransaction txn, final StringBuilder debugBuilder) |
| | | { |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | try |
| | | try (Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) |
| | | { |
| | | final long[] selectedIDs = readRange(cursor, count.get(), debugBuilder); |
| | | return newDefinedSet(selectedIDs); |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | final ByteString assertion = vlvRequest.getGreaterThanOrEqualAssertion(); |
| | | final ByteSequence encodedTargetAssertion = |
| | | encodeTargetAssertion(sortOrder, assertion, searchOperation, currentCount); |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | try |
| | | try (Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) |
| | | { |
| | | final LinkedList<Long> selectedIDs = new LinkedList<Long>(); |
| | | final LinkedList<Long> selectedIDs = new LinkedList<>(); |
| | | int targetPosition = 0; |
| | | |
| | | // Don't waste cycles looking for an assertion that does not match anything. |
| | |
| | | LDAPResultCode.SUCCESS)); |
| | | return newDefinedSet(toPrimitiveLongArray(selectedIDs)); |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | } |
| | | } |
| | | |
| | | private long[] toPrimitiveLongArray(final List<Long> entryIDs) |
| | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Normalize the assertion using the primary key's ordering matching rule. |
| | | */ |
| | | /** Normalize the assertion using the primary key's ordering matching rule. */ |
| | | static ByteSequence encodeTargetAssertion(final SortOrder sortOrder, final ByteString assertion, |
| | | final SearchOperation searchOperation, final int resultSetSize) throws DirectoryException |
| | | { |
| | |
| | | } |
| | | |
| | | final int count = 1 + beforeCount + afterCount; |
| | | final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); |
| | | try |
| | | try (Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) |
| | | { |
| | | final long[] selectedIDs; |
| | | if (cursor.positionToIndex(startPos)) |
| | |
| | | searchOperation.addResponseControl(new VLVResponseControl(targetOffset, currentCount, LDAPResultCode.SUCCESS)); |
| | | return newDefinedSet(selectedIDs); |
| | | } |
| | | finally |
| | | { |
| | | cursor.close(); |
| | | } |
| | | } |
| | | |
| | | private long[] readRange(final Cursor<ByteString, ByteString> cursor, final int count, |
| | |
| | | { |
| | | if (shouldInclude(entry)) |
| | | { |
| | | final ByteString key = encodeVLVKey(entry, entryID.longValue()); |
| | | final ByteString key = toKey(entry, entryID); |
| | | return txn.read(getName(), key) != null; |
| | | } |
| | | return false; |
| | |
| | | return builder.toByteString(); |
| | | } |
| | | |
| | | ByteString encodeVLVKey(final Entry entry, final long entryID) |
| | | private ByteString encodeVLVKey(final Entry entry, final long entryID) |
| | | { |
| | | return encodeVLVKey(sortOrder, entry, entryID); |
| | | } |
| | |
| | | import static org.opends.messages.BackendMessages.*; |
| | | import static org.opends.server.backends.pluggable.DnKeyFormat.*; |
| | | import static org.opends.server.backends.pluggable.VLVIndex.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | import java.util.AbstractSet; |
| | | import java.util.ArrayList; |
| | |
| | | /** The maximum number of references per record. */ |
| | | private long maxEntryPerValue; |
| | | |
| | | /** |
| | | * This map is used to gather some statistics about values that have |
| | | * exceeded the entry limit. |
| | | */ |
| | | private IdentityHashMap<Index, HashMap<ByteString, Long>> entryLimitMap = |
| | | new IdentityHashMap<Index, HashMap<ByteString, Long>>(); |
| | | /** This map is used to gather some statistics about values that have exceeded the entry limit. */ |
| | | private IdentityHashMap<Index, HashMap<ByteString, Long>> entryLimitMap = new IdentityHashMap<>(); |
| | | |
| | | /** Indicates whether dn2id is to be verified. */ |
| | | private boolean verifyDN2ID; |
| | |
| | | private ID2Count id2childrenCount; |
| | | |
| | | /** A list of the attribute indexes to be verified. */ |
| | | private final ArrayList<AttributeIndex> attrIndexList = new ArrayList<AttributeIndex>(); |
| | | private final ArrayList<AttributeIndex> attrIndexList = new ArrayList<>(); |
| | | /** A list of the VLV indexes to be verified. */ |
| | | private final ArrayList<VLVIndex> vlvIndexList = new ArrayList<VLVIndex>(); |
| | | private final ArrayList<VLVIndex> vlvIndexList = new ArrayList<>(); |
| | | |
| | | /** |
| | | * Construct a VerifyJob. |
| | | * |
| | | * @param rootContainer The root container. |
| | | * @param verifyConfig The verify configuration. |
| | | */ |
| | | VerifyJob(RootContainer rootContainer, VerifyConfig verifyConfig) |
| | |
| | | * @throws StorageRuntimeException If an error occurs in the storage. |
| | | * @throws DirectoryException If an error occurs while verifying the backend. |
| | | */ |
| | | long verifyBackend() throws StorageRuntimeException, |
| | | DirectoryException |
| | | long verifyBackend() throws StorageRuntimeException, DirectoryException |
| | | { |
| | | try |
| | | { |
| | |
| | | |
| | | private long verifyBackend0(ReadableTransaction txn) throws StorageRuntimeException, DirectoryException |
| | | { |
| | | EntryContainer entryContainer = |
| | | rootContainer.getEntryContainer(verifyConfig.getBaseDN()); |
| | | EntryContainer entryContainer = rootContainer.getEntryContainer(verifyConfig.getBaseDN()); |
| | | |
| | | entryContainer.sharedLock.lock(); |
| | | try |
| | |
| | | throw new StorageRuntimeException(ERR_VLV_INDEX_NOT_CONFIGURED.get(lowerName).toString()); |
| | | } |
| | | |
| | | VLVIndex vlvIndex = |
| | | entryContainer.getVLVIndex(lowerName.substring(4)); |
| | | VLVIndex vlvIndex = entryContainer.getVLVIndex(lowerName.substring(4)); |
| | | if(vlvIndex == null) |
| | | { |
| | | throw new StorageRuntimeException(ERR_VLV_INDEX_NOT_CONFIGURED.get(lowerName.substring(4)).toString()); |
| | |
| | | } |
| | | } |
| | | |
| | | entryLimitMap = new IdentityHashMap<Index, HashMap<ByteString, Long>>(attrIndexList.size()); |
| | | entryLimitMap = new IdentityHashMap<>(attrIndexList.size()); |
| | | |
| | | // We will be updating these files independently of the indexes |
| | | // so we need direct access to them rather than going through |
| | |
| | | { |
| | | iterateID2ChildrenCount(txn); |
| | | } |
| | | else if (attrIndexList.size() > 0) |
| | | else if (!attrIndexList.isEmpty()) |
| | | { |
| | | AttributeIndex attrIndex = attrIndexList.get(0); |
| | | for (MatchingRuleIndex index : attrIndex.getNameToIndexes().values()) |
| | |
| | | iterateAttrIndex(txn, index); |
| | | } |
| | | } |
| | | else if (vlvIndexList.size() > 0) |
| | | else if (!vlvIndexList.isEmpty()) |
| | | { |
| | | iterateVLVIndex(txn, vlvIndexList.get(0), true); |
| | | } |
| | |
| | | EntryID currentEntryID = new EntryID(-1); |
| | | while(cursor.next()) { |
| | | if (cursor.getKey().equals(currentEntryID)) { |
| | | /** Sharded cursor may return the same EntryID multiple times */ |
| | | // Sharded cursor may return the same EntryID multiple times |
| | | continue; |
| | | } |
| | | currentEntryID = cursor.getKey(); |
| | |
| | | HashMap<ByteString,Long> hashMap = entryLimitMap.get(index); |
| | | if (hashMap == null) |
| | | { |
| | | hashMap = new HashMap<ByteString, Long>(); |
| | | hashMap = new HashMap<>(); |
| | | entryLimitMap.put(index, hashMap); |
| | | } |
| | | Long counter = hashMap.get(key); |
| | |
| | | continue; |
| | | } |
| | | |
| | | ByteString expectedKey = vlvIndex.encodeVLVKey(entry, id.longValue()); |
| | | ByteString expectedKey = vlvIndex.toKey(entry, id); |
| | | if (expectedKey.compareTo(key) != 0) |
| | | { |
| | | errorCount++; |
| | |
| | | id, keyDump(vlvIndex.toString(), expectedKey)); |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | if (prevID != null && id.equals(prevID) && logger.isTraceEnabled()) |
| | | { |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace("Duplicate reference to ID %d%n%s", id, keyDump(index.toString(), key)); |
| | | } |
| | | logger.trace("Duplicate reference to ID %d%n%s", id, keyDump(index.toString(), key)); |
| | | } |
| | | prevID = id; |
| | | |
| | |
| | | private static String keyDump(String indexName, ByteSequence key) |
| | | { |
| | | StringBuilder buffer = new StringBuilder(128); |
| | | buffer.append("Index: "); |
| | | buffer.append(indexName); |
| | | buffer.append(ServerConstants.EOL); |
| | | buffer.append("Key:"); |
| | | buffer.append(ServerConstants.EOL); |
| | | buffer.append("Index: ").append(indexName).append(ServerConstants.EOL); |
| | | buffer.append("Key:").append(ServerConstants.EOL); |
| | | StaticUtils.byteArrayToHexPlusAscii(buffer, key.toByteArray(), 6); |
| | | return buffer.toString(); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check that an attribute index is complete for a given attribute. |
| | | */ |
| | | /** Check that an attribute index is complete for a given attribute. */ |
| | | private void verifyAttribute(ReadableTransaction txn, EntryID entryID, Entry entry, AttributeIndex attrIndex) |
| | | { |
| | | for (MatchingRuleIndex index : attrIndex.getNameToIndexes().values()) |
| | |
| | | return dn.getParentDNInSuffix(); |
| | | } |
| | | |
| | | /** |
| | | * This class maintain the number of children for a given dn |
| | | */ |
| | | /** This class maintain the number of children for a given dn. */ |
| | | private static final class ChildrenCount { |
| | | private final ByteString baseDN; |
| | | private final EntryID entryID; |
| | |
| | | { |
| | | /** The total number of records to process. */ |
| | | private long totalCount; |
| | | |
| | | /** |
| | | * The number of records that had been processed at the time of the |
| | | * previous progress report. |
| | | */ |
| | | /** The number of records that had been processed at the time of the previous progress report. */ |
| | | private long previousCount; |
| | | |
| | | /** The time in milliseconds of the previous progress report. */ |
| | | private long previousTime; |
| | | |
| | | /** |
| | | * The number of bytes in a megabyte. |
| | | * Note that 1024*1024 bytes may eventually become known as a mebibyte(MiB). |
| | | */ |
| | | private static final int bytesPerMegabyte = 1024*1024; |
| | | |
| | | /** |
| | | * Create a new verify progress task. |
| | | * @param indexIterator boolean, indicates if the task is iterating |
| | | * through indexes or the entries. |
| | |
| | | try |
| | | { |
| | | Runtime runtime = Runtime.getRuntime(); |
| | | long freeMemory = runtime.freeMemory() / bytesPerMegabyte; |
| | | long freeMemory = runtime.freeMemory() / MB; |
| | | |
| | | // FIXME JNR compute the cache miss rate |
| | | float cacheMissRate = 0; |
| | |
| | | logger.traceException(e); |
| | | } |
| | | |
| | | |
| | | previousCount = latestCount; |
| | | previousTime = latestTime; |
| | | } |