Skip to content

Commit

Permalink
refactor: index mechanism to enhance overall performance
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Jun 5, 2024
1 parent ba96118 commit 0596246
Show file tree
Hide file tree
Showing 33 changed files with 1,746 additions and 1,132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class IndexDescriptor {
private final IndexSpec spec;

/**
* Record whether the index is ready, managed by {@link IndexBuilder}.
* Record whether the index is ready, managed by {@code IndexBuilder}.
*/
private boolean ready;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,27 @@ public interface IndexEntry {
Collection<Map.Entry<String, String>> entries();

/**
* Returns the immutable entries of this entry in order, it is safe to modify the returned
* result, but extra cost is made.
*
* @return immutable entries of this entry.
* <p>Returns the position of the object name in the indexed attribute value mapping for
* sorting.</p>
* For example:
* <pre>
* metadata.name | field1
* ------------- | ------
* foo | 1
* bar | 2
* baz | 2
* </pre>
* "field1" is the indexed attribute, and the position of the object name in the indexed
* attribute
* value mapping for sorting is:
* <pre>
* foo -> 0
* bar -> 1
* baz -> 1
* </pre>
* "bar" and "baz" have the same value, so they have the same position.
*/
Collection<Map.Entry<String, String>> immutableEntries();
Map<String, Integer> getIdPositionMap();

/**
* Returns the object names of this entry in order.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package run.halo.app.extension.index;

import java.util.Collection;
import java.util.NavigableSet;
import java.util.Set;

public interface IndexEntryOperator {

/**
* Search all values that key less than the target key.
*
* @param key target key
* @param orEqual whether to include the value of the target key
* @return object names that key less than the target key
*/
NavigableSet<String> lessThan(String key, boolean orEqual);

/**
* Search all values that key greater than the target key.
*
* @param key target key
* @param orEqual whether to include the value of the target key
* @return object names that key greater than the target key
*/
NavigableSet<String> greaterThan(String key, boolean orEqual);

/**
* Search all values that key in the range of [start, end].
*
* @param start start key
* @param end end key
* @param startInclusive whether to include the value of the start key
* @param endInclusive whether to include the value of the end key
* @return object names that key in the range of [start, end]
*/
NavigableSet<String> range(String start, String end, boolean startInclusive,
boolean endInclusive);

/**
* Find all values that key equals to the target key.
*
* @param key target key
* @return object names that key equals to the target key
*/
NavigableSet<String> find(String key);

NavigableSet<String> findIn(Collection<String> keys);

/**
* Get all values in the index entry.
*
* @return a set of all object names
*/
Set<String> getValues();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package run.halo.app.extension.index;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.util.Assert;

public class IndexEntryOperatorImpl implements IndexEntryOperator {
private final IndexEntry indexEntry;

public IndexEntryOperatorImpl(IndexEntry indexEntry) {
this.indexEntry = indexEntry;
}

private static NavigableSet<String> createNavigableSet() {
return new TreeSet<>(KeyComparator.INSTANCE);
}

@Override
public NavigableSet<String> lessThan(String key, boolean orEqual) {
Assert.notNull(key, "Key must not be null.");
indexEntry.acquireReadLock();
try {
var sortedEntries = sortedEntries();
var index = binarySearch(sortedEntries, key);

var resultSet = createNavigableSet();

if (index < 0) {
// if key not found, get the insertion point(-index-1)
index = -index - 1;
} else if (orEqual) {
// key exists and includes equal, index+1
index++;
}

// Add all values if key less than the target key (or including the target key) to the
// result set
for (int i = 0; i < index; i++) {
resultSet.add(sortedEntries.get(i).getValue());
}
return resultSet;
} finally {
indexEntry.releaseReadLock();
}
}

@Override
public NavigableSet<String> greaterThan(String key, boolean orEqual) {
Assert.notNull(key, "Key must not be null.");
indexEntry.acquireReadLock();
try {
var sortedEntries = sortedEntries();
var index = binarySearch(sortedEntries, key);

var resultSet = createNavigableSet();

if (index < 0) {
// key does not exist, get the insertion point (-index-1)
index = -index - 1;
} else if (!orEqual) {
// key exists but does not include equal, index+1
index++;
}

for (int i = index; i < sortedEntries.size(); i++) {
resultSet.add(sortedEntries.get(i).getValue());
}

return resultSet;
} finally {
indexEntry.releaseReadLock();
}
}

@Override
public NavigableSet<String> range(String start, String end, boolean startInclusive,
boolean endInclusive) {
Assert.notNull(start, "The start must not be null.");
Assert.notNull(end, "The end must not be null.");
indexEntry.acquireReadLock();
try {
var sortedEntries = sortedEntries();

var resultSet = createNavigableSet();

for (Map.Entry<String, String> entry : sortedEntries) {
String key = entry.getKey();
boolean greaterThanStart =
startInclusive ? key.compareTo(start) >= 0 : key.compareTo(start) > 0;
boolean lessThanEnd =
endInclusive ? key.compareTo(end) <= 0 : key.compareTo(end) < 0;

if (greaterThanStart && lessThanEnd) {
resultSet.add(entry.getValue());
}
}
return resultSet;
} finally {
indexEntry.releaseReadLock();
}
}

@Override
public NavigableSet<String> find(String key) {
Assert.notNull(key, "The key must not be null.");
indexEntry.acquireReadLock();
try {
var resultSet = createNavigableSet();

for (Map.Entry<String, String> entry : sortedEntries()) {
if (entry.getKey().equals(key)) {
resultSet.add(entry.getValue());
}
}

return resultSet;
} finally {
indexEntry.releaseReadLock();
}
}

@Override
public NavigableSet<String> findIn(Collection<String> keys) {
if (keys == null || keys.isEmpty()) {
return createNavigableSet();
}
var keysToSearch = new HashSet<>(keys);
indexEntry.acquireReadLock();
try {
var resultSet = createNavigableSet();

for (Map.Entry<String, String> entry : sortedEntries()) {
if (keysToSearch.contains(entry.getKey())) {
resultSet.add(entry.getValue());
}
}

return resultSet;
} finally {
indexEntry.releaseReadLock();
}
}

@Override
public Set<String> getValues() {
indexEntry.acquireReadLock();
try {
Set<String> uniqueValues = new LinkedHashSet<>();
for (Map.Entry<String, String> entry : sortedEntries()) {
uniqueValues.add(entry.getValue());
}
return uniqueValues;
} finally {
indexEntry.releaseReadLock();
}
}

int binarySearch(List<Map.Entry<String, String>> sortedEntries, String key) {
var searchEntry = new AbstractMap.SimpleEntry<String, String>(key, null);
return Collections.binarySearch(sortedEntries, searchEntry,
Map.Entry.comparingByKey()
);
}

private ArrayList<Map.Entry<String, String>> sortedEntries() {
//index entries are sorted by key, so we can use the entries directly
return new ArrayList<>(indexEntry.entries());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Iterator;
import java.util.function.Function;
import org.springframework.lang.NonNull;
import run.halo.app.extension.Extension;

/**
Expand All @@ -20,7 +21,7 @@ public interface Indexer {
/**
* <p>Index the specified {@link Extension} by {@link IndexDescriptor}s.</p>
* <p>First, the {@link Indexer} will index the {@link Extension} by the
* {@link IndexDescriptor}s and record the index entries to {@link IndexerTransaction} and
* {@link IndexDescriptor}s and record the index entries to {@code IndexerTransaction} and
* commit the transaction, if any error occurs, the transaction will be rollback to keep the
* {@link Indexer} consistent.</p>
*
Expand All @@ -33,7 +34,7 @@ public interface Indexer {
* <p>Update indexes for the specified {@link Extension} by {@link IndexDescriptor}s.</p>
* <p>First, the {@link Indexer} will remove the index entries of the {@link Extension} by
* the old {@link IndexDescriptor}s and reindex the {@link Extension} to generate change logs
* to {@link IndexerTransaction} and commit the transaction, if any error occurs, the
* to {@code IndexerTransaction} and commit the transaction, if any error occurs, the
* transaction will be rollback to keep the {@link Indexer} consistent.</p>
*
* @param extension the {@link Extension} to be updated
Expand Down Expand Up @@ -73,19 +74,33 @@ public interface Indexer {
*/
void removeIndexRecords(Function<IndexDescriptor, Boolean> matchFn);

/**
* <p>Get the {@link IndexEntry} by index name if found and ready.</p>
*
* @param name an index name
* @return the {@link IndexEntry} if found
* @throws IllegalArgumentException if the index name is not found or the index is not ready
*/
@NonNull
IndexEntry getIndexEntry(String name);

/**
* <p>Gets an iterator over all the ready {@link IndexEntry}s, in no particular order.</p>
*
* @return an iterator over all the ready {@link IndexEntry}s
* @link {@link IndexDescriptor#isReady()}
* @see IndexDescriptor#isReady()
*/
Iterator<IndexEntry> readyIndexesIterator();

/**
* <p>Gets an iterator over all the {@link IndexEntry}s, in no particular order.</p>
*
* @return an iterator over all the {@link IndexEntry}s
* @link {@link IndexDescriptor#isReady()}
* @see IndexDescriptor#isReady()
*/
Iterator<IndexEntry> allIndexesIterator();

void acquireReadLock();

void releaseReadLock();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public All(String fieldName) {

@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
return indexView.getAllIdsForField(fieldName);
return indexView.getIdsForField(fieldName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package run.halo.app.extension.index.query;

import com.google.common.collect.Sets;
import java.util.NavigableSet;

public class Between extends SimpleQuery {
Expand All @@ -19,17 +18,8 @@ public Between(String fieldName, String lowerValue, boolean lowerInclusive,
this.upperInclusive = upperInclusive;
}


@Override
public NavigableSet<String> matches(QueryIndexView indexView) {
NavigableSet<String> allValues = indexView.getAllValuesForField(fieldName);
// get all values in the specified range
var subSet = allValues.subSet(lowerValue, lowerInclusive, upperValue, upperInclusive);

var resultSet = Sets.<String>newTreeSet();
for (String val : subSet) {
resultSet.addAll(indexView.getIdsForFieldValue(fieldName, val));
}
return resultSet;
return indexView.between(fieldName, lowerValue, lowerInclusive, upperValue, upperInclusive);
}
}
Loading

0 comments on commit 0596246

Please sign in to comment.