Skip to content

feature(db): optimize properties query frequency #5378

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public abstract class AbstractSnapshot<K, V> implements Snapshot {

protected WeakReference<Snapshot> next;

protected boolean isOptimized;

@Override
public Snapshot advance() {
return new SnapshotImpl(this);
Expand All @@ -34,4 +36,9 @@ public void setNext(Snapshot next) {
public String getDbName() {
return db.getDbName();
}

@Override
public boolean isOptimized(){
return isOptimized;
}
}
4 changes: 4 additions & 0 deletions chainbase/src/main/java/org/tron/core/db2/core/Snapshot.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ static boolean isImpl(Snapshot snapshot) {
void updateSolidity();

String getDbName();

boolean isOptimized();

void reloadToMem();
}
23 changes: 23 additions & 0 deletions chainbase/src/main/java/org/tron/core/db2/core/SnapshotImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public class SnapshotImpl extends AbstractSnapshot<Key, Value> {
}
previous = snapshot;
snapshot.setNext(this);
isOptimized = snapshot.isOptimized();
if (isOptimized && root == previous) {
Streams.stream(root.iterator()).forEach( e -> put(e.getKey(),e.getValue()));
}
}

@Override
Expand All @@ -40,6 +44,7 @@ public byte[] get(byte[] key) {
private byte[] get(Snapshot head, byte[] key) {
Snapshot snapshot = head;
Value value;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why reserve these if they are useless?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this part of the commented code

while (Snapshot.isImpl(snapshot)) {
if ((value = ((SnapshotImpl) snapshot).db.get(Key.of(key))) != null) {
return value.getBytes();
Expand Down Expand Up @@ -83,6 +88,19 @@ public void merge(Snapshot from) {
Streams.stream(fromImpl.db).forEach(e -> db.put(e.getKey(), e.getValue()));
}

public void mergeAhead(Snapshot from) {
if (from instanceof SnapshotRoot) {
return ;
}
SnapshotImpl fromImpl = (SnapshotImpl) from;
Streams.stream(fromImpl.db).forEach(e -> {
if (db.get(e.getKey()) == null) {
db.put(e.getKey(), e.getValue());
}
}
);
}

@Override
public Snapshot retreat() {
return previous;
Expand Down Expand Up @@ -177,4 +195,9 @@ public String getDbName() {
public Snapshot newInstance() {
return new SnapshotImpl(this);
}

@Override
public void reloadToMem() {
mergeAhead(previous);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ public synchronized void commit() {
}

--activeSession;

dbs.forEach(db -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why set the reload logic in commit(), commit() only invoked by the end of pushing a block, not a transaction.

Are there other reasons for doing this, can you explain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because if the transaction ends without a commit,
Revoke is needed at this time,
session.close is called,
Then call snapshotManager.revoke,
Then there is no need to perform reload logic.
So the reload logic is only executed when the transaction commits.

@tomatoishealthy @halibobo1205

if (db.getHead().isOptimized()) {
db.getHead().reloadToMem();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isOptimized judged twice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK,I get it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed this

}
});
}

public synchronized void pop() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public SnapshotRoot(DB<byte[], byte[]> db) {
if (CACHE_DBS.contains(this.db.getDbName())) {
this.cache = CacheManager.allocate(CacheType.findByType(this.db.getDbName()));
}
isOptimized = "properties".equalsIgnoreCase(db.getDbName());
}

private boolean needOptAsset() {
Expand Down Expand Up @@ -221,4 +222,7 @@ public String getDbName() {
public Snapshot newInstance() {
return new SnapshotRoot(db.newInstance());
}

@Override
public void reloadToMem() { }
}
201 changes: 201 additions & 0 deletions framework/src/test/java/org/tron/core/db2/SnapshotImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package org.tron.core.db2;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.io.File;
import java.lang.reflect.Constructor;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.tron.common.application.Application;
import org.tron.common.application.ApplicationFactory;
import org.tron.common.application.TronApplicationContext;
import org.tron.common.utils.FileUtil;
import org.tron.core.Constant;
import org.tron.core.config.DefaultConfig;
import org.tron.core.config.args.Args;
import org.tron.core.db2.core.Snapshot;
import org.tron.core.db2.core.SnapshotImpl;
import org.tron.core.db2.core.SnapshotManager;
import org.tron.core.db2.core.SnapshotRoot;

public class SnapshotImplTest {
private RevokingDbWithCacheNewValueTest.TestRevokingTronStore tronDatabase;
private TronApplicationContext context;
private Application appT;
private SnapshotManager revokingDatabase;

@Before
public void init() {
Args.setParam(new String[]{"-d", "output_revokingStore_test"}, Constant.TEST_CONF);
context = new TronApplicationContext(DefaultConfig.class);
appT = ApplicationFactory.create(context);

tronDatabase = new RevokingDbWithCacheNewValueTest.TestRevokingTronStore(
"testSnapshotRoot-testMerge");
revokingDatabase = context.getBean(SnapshotManager.class);
revokingDatabase.enable();
revokingDatabase.add(tronDatabase.getRevokingDB());
}

@After
public void removeDb() {
Args.clearParam();
context.destroy();
FileUtil.deleteDir(new File("output_revokingStore_test"));

tronDatabase.close();
revokingDatabase.shutdown();
}

/**
* linklist is: from -> root
* root:key1=>value1, key2=>value2
* from:key3=>value3, key4=>value4
* after construct, getSnapshotImplIns(root);
* from: key1=>value1, key2=>value2, key3=>value3, key4=>value4
* from: get key1 or key2, traverse 0 times
*/
@Test
public void testMergeRoot() {
// linklist is: from -> root
SnapshotRoot root = new SnapshotRoot(tronDatabase.getDb());
//root.setOptimized(true);

root.put("key1".getBytes(), "value1".getBytes());
root.put("key2".getBytes(), "value2".getBytes());
SnapshotImpl from = getSnapshotImplIns(root);
from.put("key3".getBytes(), "value3".getBytes());
from.put("key4".getBytes(), "value4".getBytes());

byte[] s1 = from.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s1));
byte[] s2 = from.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s2));
}

/**
* linklist is: from2 -> from -> root
* root:
* from:key1=>value1, key2=>value2
* from2:key3=>value3,key4=>value4
* before merge: from2.mergeAhead(from);
* from2: get key1 or key2, traverse 1 times
* after merge
* from2:key1=>value1, key2=>value2, value3=>value3,key4=>value4
* from2: get key1 or key2, traverse 0 times
*
*/
@Test
public void testMergeAhead() {

// linklist is: from2 -> from -> root
SnapshotRoot root = new SnapshotRoot(tronDatabase.getDb());
SnapshotImpl from = getSnapshotImplIns(root);
from.put("key1".getBytes(), "value1".getBytes());
from.put("key2".getBytes(), "value2".getBytes());

SnapshotImpl from2 = getSnapshotImplIns(from);
from2.put("key3".getBytes(), "value3".getBytes());
from2.put("key4".getBytes(), "value4".getBytes());

/*
// before merge get data in from is success,traverse 0 times
byte[] s1 = from.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s1));
byte[] s2 = from.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s2));
// before merge get data in from2 is success, traverse 0 times
byte[] s3 = from2.get("key3".getBytes());
assertEquals(new String("value3".getBytes()), new String(s3));
byte[] s4 = from2.get("key4".getBytes());
assertEquals(new String("value4".getBytes()), new String(s4));
*/

// before merge from2 get data is success, traverse 1 times
byte[] s11 = from2.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s11));
byte[] s12 = from2.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s12));
// this can not get key3 and key4
assertNull(from.get("key3".getBytes()));
assertNull(from.get("key4".getBytes()));

// do mergeAhead
from2.mergeAhead(from);
/*
// after merge get data in from is success, traverse 0 times
s1 = from.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s1));
s2 = from.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s2));

// after merge get data in from2 is success, traverse 0 times
s3 = from2.get("key3".getBytes());
assertEquals(new String("value3".getBytes()), new String(s3));
s4 = from2.get("key4".getBytes());
assertEquals(new String("value4".getBytes()), new String(s4));
*/

// after merge from2 get data is success, traverse 0 times
byte[] s1 = from2.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s1));
byte[] s2 = from2.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s2));

// this can not get key3 and key4
assertNull(from.get("key3".getBytes()));
assertNull(from.get("key4".getBytes()));
}

/**
* from: key1=>value1, key2=>value2, key3=>value31
* from2: key3=>value32,key4=>value4
* after merge: from2.mergeAhead(from);
* from2: key1=>value1, key2=>value2, key3=>value32, key4=>value4
*/
@Test
public void testMergeOverride() {
// linklist is: from2 -> from -> root
SnapshotRoot root = new SnapshotRoot(tronDatabase.getDb());
SnapshotImpl from = getSnapshotImplIns(root);
from.put("key1".getBytes(), "value1".getBytes());
from.put("key2".getBytes(), "value2".getBytes());
from.put("key3".getBytes(), "value31".getBytes());

SnapshotImpl from2 = getSnapshotImplIns(from);
from2.put("key3".getBytes(), "value32".getBytes());
from2.put("key4".getBytes(), "value4".getBytes());
// do mergeAhead
from2.mergeAhead(from);

// after merge from2 get data is success, traverse 0 times
byte[] s1 = from2.get("key1".getBytes());
assertEquals(new String("value1".getBytes()), new String(s1));
byte[] s2 = from2.get("key2".getBytes());
assertEquals(new String("value2".getBytes()), new String(s2));
byte[] s3 = from2.get("key3".getBytes());
assertEquals(new String("value32".getBytes()), new String(s3));
byte[] s4 = from2.get("key4".getBytes());
assertEquals(new String("value4".getBytes()), new String(s4));
}

/**
* The constructor of SnapshotImpl is not public
* so reflection is used to construct the object here.
*/
private SnapshotImpl getSnapshotImplIns(Snapshot snapshot) {
Class clazz = SnapshotImpl.class;
try {
Constructor constructor = clazz.getDeclaredConstructor(Snapshot.class);
constructor.setAccessible(true);
return (SnapshotImpl) constructor.newInstance(snapshot);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

}