-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-32350][FOLLOW-UP] Fix count update issue and partition the value list to a set of small batches for LevelDB writeAll #29425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.lang.ref.SoftReference; | ||
| import java.nio.ByteBuffer; | ||
| import java.util.*; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||
|
|
@@ -31,6 +32,7 @@ | |
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.google.common.base.Preconditions; | ||
| import com.google.common.base.Throwables; | ||
| import com.google.common.collect.Iterables; | ||
| import org.fusesource.leveldbjni.JniDBFactory; | ||
| import org.iq80.leveldb.DB; | ||
| import org.iq80.leveldb.Options; | ||
|
|
@@ -154,7 +156,7 @@ public void write(Object value) throws Exception { | |
| try (WriteBatch batch = db().createWriteBatch()) { | ||
| byte[] data = serializer.serialize(value); | ||
| synchronized (ti) { | ||
| updateBatch(batch, value, data, value.getClass(), ti.naturalIndex(), ti.indices()); | ||
| updateBatch(batch, value, data, value.getClass(), ti.naturalIndex(), ti.indices(), null); | ||
| db().write(batch); | ||
| } | ||
| } | ||
|
|
@@ -164,35 +166,44 @@ public void writeAll(List<?> values) throws Exception { | |
| Preconditions.checkArgument(values != null && !values.isEmpty(), | ||
| "Non-empty values required."); | ||
|
|
||
| // Group by class, in case there are values from different classes in the values | ||
| // Group by class, in case there are values from different classes in the values. | ||
| // Typical usecase is for this to be a single class. | ||
| // A NullPointerException will be thrown if values contain null object. | ||
| for (Map.Entry<? extends Class<?>, ? extends List<?>> entry : | ||
| values.stream().collect(Collectors.groupingBy(Object::getClass)).entrySet()) { | ||
|
|
||
| final Iterator<?> valueIter = entry.getValue().iterator(); | ||
| final Iterator<byte[]> serializedValueIter; | ||
|
|
||
| // Deserialize outside synchronized block | ||
| List<byte[]> list = new ArrayList<>(entry.getValue().size()); | ||
| for (Object value : values) { | ||
| list.add(serializer.serialize(value)); | ||
| } | ||
| serializedValueIter = list.iterator(); | ||
|
|
||
| final Class<?> klass = entry.getKey(); | ||
| final LevelDBTypeInfo ti = getTypeInfo(klass); | ||
|
|
||
| synchronized (ti) { | ||
| final LevelDBTypeInfo.Index naturalIndex = ti.naturalIndex(); | ||
| final Collection<LevelDBTypeInfo.Index> indices = ti.indices(); | ||
| // Partition the large value list to a set of smaller batches, to reduce the memory | ||
| // pressure caused by serialization and give fairness to other writing threads. | ||
| for (List<?> batchList : Iterables.partition(entry.getValue(), 128)) { | ||
| final Iterator<?> valueIter = batchList.iterator(); | ||
| final Iterator<byte[]> serializedValueIter; | ||
|
|
||
| try (WriteBatch batch = db().createWriteBatch()) { | ||
| while (valueIter.hasNext()) { | ||
| updateBatch(batch, valueIter.next(), serializedValueIter.next(), klass, | ||
| naturalIndex, indices); | ||
| // Deserialize outside synchronized block | ||
| List<byte[]> serializedValueList = new ArrayList<>(batchList.size()); | ||
| for (Object value : batchList) { | ||
| serializedValueList.add(serializer.serialize(value)); | ||
| } | ||
| serializedValueIter = serializedValueList.iterator(); | ||
|
|
||
| final LevelDBTypeInfo ti = getTypeInfo(klass); | ||
| synchronized (ti) { | ||
| final LevelDBTypeInfo.Index naturalIndex = ti.naturalIndex(); | ||
| final Collection<LevelDBTypeInfo.Index> indices = ti.indices(); | ||
|
|
||
| try (WriteBatch batch = db().createWriteBatch()) { | ||
| // A hash map to update the delta of each countKey, wrap countKey with type byte[] | ||
| // as ByteBuffer because ByteBuffer is comparable. | ||
| Map<ByteBuffer, Long> counts = new HashMap<>(); | ||
| while (valueIter.hasNext()) { | ||
| updateBatch(batch, valueIter.next(), serializedValueIter.next(), klass, | ||
| naturalIndex, indices, counts); | ||
| } | ||
| for (Map.Entry<ByteBuffer, Long> countEntry : counts.entrySet()) { | ||
| naturalIndex.updateCount(batch, countEntry.getKey().array(), countEntry.getValue()); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @HeartSaVioR Here I use naturalIndex.updateCount() to put the count information of all indexes to the batch. When implementing this I found we can lift the method updateCount() and long getCount(byte[] key) from LevelDBTypeInfo.Index to LevelDBTypeInfo, as these methods are not accessing any member of LevelDBTypeInfo.Index. Doing that would allow us to call ti.updateCount() to update count for all indexes, which would make more sense. However, it's totally optional. |
||
| } | ||
| db().write(batch); | ||
| } | ||
| db().write(batch); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -204,7 +215,8 @@ private void updateBatch( | |
| byte[] data, | ||
| Class<?> klass, | ||
| LevelDBTypeInfo.Index naturalIndex, | ||
| Collection<LevelDBTypeInfo.Index> indices) throws Exception { | ||
| Collection<LevelDBTypeInfo.Index> indices, | ||
| Map<ByteBuffer, Long> counts) throws Exception { | ||
| Object existing; | ||
| try { | ||
| existing = get(naturalIndex.entityKey(null, value), klass); | ||
|
|
@@ -216,7 +228,7 @@ private void updateBatch( | |
| byte[] naturalKey = naturalIndex.toKey(naturalIndex.getValue(value)); | ||
| for (LevelDBTypeInfo.Index idx : indices) { | ||
| byte[] prefix = cache.getPrefix(idx); | ||
| idx.add(batch, value, existing, data, naturalKey, prefix); | ||
| idx.add(batch, value, existing, data, naturalKey, prefix, counts); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a comment in #28412 (review) . I think we should add the unit test cases for verifying the code work as our design. We might need to update the implementation to provide the private APIs for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I will add unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @gatorsmile , unit tests for HybridStore are added in #29509. And the unit test for writeAll() method of levelDB is added in current pr.