Skip to content

Commit 2dcdb45

Browse files
authored
feat(jsonrpc): optimize event log query (#6370)
* feat(api): optimize partialMatch * feat(api): remove skipGroups in partialMatch * feat(api): fix bug * feat(api): add test * feat(api): add unit test * feat(api): add inline comments * feat(api): remove unnecessary code
1 parent c7f133d commit 2dcdb45

File tree

2 files changed

+159
-23
lines changed

2 files changed

+159
-23
lines changed

framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogBlockQuery.java

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import java.util.ArrayList;
44
import java.util.BitSet;
5+
import java.util.HashMap;
6+
import java.util.HashSet;
57
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Set;
610
import java.util.concurrent.ExecutionException;
711
import java.util.concurrent.ExecutorService;
812
import java.util.concurrent.Future;
@@ -121,44 +125,76 @@ private BitSet subMatch(int[][] bitIndexes) throws ExecutionException, Interrupt
121125
}
122126

123127
/**
124-
* every section has a compound query of sectionBloomStore, works parallel
125-
* "and" condition in second dimension of query, "or" condition in first dimension
126-
* return a BitSet whose capacity is blockPerSection
128+
* Match blocks using optimized bloom filter operations. This method reduces database queries
129+
* and BitSet operations by handling duplicate bit indexes and skipping invalid groups.
130+
*
131+
* @param bitIndexes A 2D array where:
132+
* - First dimension represents different topic/address (OR)
133+
* - Second dimension contains bit indexes within each topic/address (AND)
134+
* Example: [[1,2,3], [4,5,6]] means (1 AND 2 AND 3) OR (4 AND 5 AND 6)
135+
* @param section The section number in the bloom filter store to query
136+
* @return A BitSet representing the matching blocks in this section
137+
* @throws ExecutionException If there's an error in concurrent execution
138+
* @throws InterruptedException If the concurrent execution is interrupted
127139
*/
128140
private BitSet partialMatch(final int[][] bitIndexes, int section)
129141
throws ExecutionException, InterruptedException {
130-
List<List<Future<BitSet>>> bitSetList = new ArrayList<>();
131-
142+
// 1. Collect all unique bitIndexes
143+
Set<Integer> uniqueBitIndexes = new HashSet<>();
132144
for (int[] index : bitIndexes) {
133-
List<Future<BitSet>> futureList = new ArrayList<>();
134-
for (final int bitIndex : index) { //must be 3
135-
Future<BitSet> bitSetFuture =
136-
sectionExecutor.submit(() -> sectionBloomStore.get(section, bitIndex));
137-
futureList.add(bitSetFuture);
145+
for (int bitIndex : index) { //normally 3, but could be less due to hash collisions
146+
uniqueBitIndexes.add(bitIndex);
147+
}
148+
}
149+
150+
// 2. Submit concurrent requests for all unique bitIndexes
151+
Map<Integer, Future<BitSet>> bitIndexResults = new HashMap<>();
152+
for (int bitIndex : uniqueBitIndexes) {
153+
Future<BitSet> future
154+
= sectionExecutor.submit(() -> sectionBloomStore.get(section, bitIndex));
155+
bitIndexResults.put(bitIndex, future);
156+
}
157+
158+
// 3. Wait for all results and cache them
159+
Map<Integer, BitSet> resultCache = new HashMap<>();
160+
for (Map.Entry<Integer, Future<BitSet>> entry : bitIndexResults.entrySet()) {
161+
BitSet result = entry.getValue().get();
162+
if (result != null) {
163+
resultCache.put(entry.getKey(), result);
138164
}
139-
bitSetList.add(futureList);
140165
}
141166

142-
BitSet bitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
143167

144-
for (List<Future<BitSet>> futureList : bitSetList) {
145-
// initial a BitSet with all 1
146-
BitSet subBitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
147-
subBitSet.set(0, SectionBloomStore.BLOCK_PER_SECTION);
168+
// 4. Process valid groups with reused BitSet objects
169+
BitSet finalResult = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
170+
BitSet tempBitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
171+
172+
for (int[] index : bitIndexes) {
173+
174+
// init tempBitSet with all 1
175+
tempBitSet.set(0, SectionBloomStore.BLOCK_PER_SECTION);
176+
148177
// and condition in second dimension
149-
for (Future<BitSet> future : futureList) {
150-
BitSet one = future.get();
151-
if (one == null) { //match nothing
152-
subBitSet.clear();
178+
for (int bitIndex : index) {
179+
BitSet cached = resultCache.get(bitIndex);
180+
if (cached == null) { //match nothing
181+
tempBitSet.clear();
153182
break;
154183
}
155184
// "and" condition in second dimension
156-
subBitSet.and(one);
185+
tempBitSet.and(cached);
186+
if (tempBitSet.isEmpty()) {
187+
break;
188+
}
157189
}
190+
158191
// "or" condition in first dimension
159-
bitSet.or(subBitSet);
192+
if (!tempBitSet.isEmpty()) {
193+
finalResult.or(tempBitSet);
194+
}
160195
}
161-
return bitSet;
196+
197+
return finalResult;
162198
}
163199

164200
/**
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.tron.core.jsonrpc;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.BitSet;
5+
import java.util.concurrent.ExecutorService;
6+
import java.util.concurrent.Executors;
7+
import javax.annotation.Resource;
8+
import org.junit.Assert;
9+
import org.junit.Before;
10+
import org.junit.Test;
11+
import org.tron.common.BaseTest;
12+
import org.tron.core.Constant;
13+
import org.tron.core.config.args.Args;
14+
import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest;
15+
import org.tron.core.services.jsonrpc.filters.LogBlockQuery;
16+
import org.tron.core.services.jsonrpc.filters.LogFilterWrapper;
17+
import org.tron.core.store.SectionBloomStore;
18+
19+
public class LogBlockQueryTest extends BaseTest {
20+
21+
@Resource
22+
SectionBloomStore sectionBloomStore;
23+
private ExecutorService sectionExecutor;
24+
private Method partialMatchMethod;
25+
private static final long CURRENT_MAX_BLOCK_NUM = 50000L;
26+
27+
static {
28+
Args.setParam(new String[] {"--output-directory", dbPath()}, Constant.TEST_CONF);
29+
}
30+
31+
@Before
32+
public void setup() throws Exception {
33+
sectionExecutor = Executors.newFixedThreadPool(5);
34+
35+
// Get private method through reflection
36+
partialMatchMethod = LogBlockQuery.class.getDeclaredMethod("partialMatch",
37+
int[][].class, int.class);
38+
partialMatchMethod.setAccessible(true);
39+
40+
BitSet bitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
41+
bitSet.set(0, SectionBloomStore.BLOCK_PER_SECTION);
42+
sectionBloomStore.put(0, 1, bitSet);
43+
sectionBloomStore.put(0, 2, bitSet);
44+
sectionBloomStore.put(0, 3, bitSet);
45+
BitSet bitSet2 = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
46+
bitSet2.set(0);
47+
sectionBloomStore.put(1, 1, bitSet2);
48+
sectionBloomStore.put(1, 2, bitSet2);
49+
sectionBloomStore.put(1, 3, bitSet2);
50+
}
51+
52+
@Test
53+
public void testPartialMatch() throws Exception {
54+
// Create a basic LogFilterWrapper
55+
LogFilterWrapper logFilterWrapper = new LogFilterWrapper(
56+
new FilterRequest("0x0", "0x1", null, null, null),
57+
CURRENT_MAX_BLOCK_NUM, null, false);
58+
59+
LogBlockQuery logBlockQuery = new LogBlockQuery(logFilterWrapper, sectionBloomStore,
60+
CURRENT_MAX_BLOCK_NUM, sectionExecutor);
61+
62+
int section = 0;
63+
64+
// Create a hit condition
65+
int[][] bitIndexes = new int[][] {
66+
{1, 2, 3}, // topic0
67+
{4, 5, 6} // topic1
68+
};
69+
70+
// topic0 hit section 0
71+
BitSet result = (BitSet) partialMatchMethod.invoke(logBlockQuery, bitIndexes, section);
72+
Assert.assertNotNull(result);
73+
Assert.assertEquals(SectionBloomStore.BLOCK_PER_SECTION, result.cardinality());
74+
75+
// topic0 hit section 1
76+
result = (BitSet) partialMatchMethod.invoke(logBlockQuery, bitIndexes, 1);
77+
Assert.assertNotNull(result);
78+
Assert.assertEquals(1, result.cardinality());
79+
80+
// not exist section 2
81+
result = (BitSet) partialMatchMethod.invoke(logBlockQuery, bitIndexes, 2);
82+
Assert.assertNotNull(result);
83+
Assert.assertTrue(result.isEmpty());
84+
85+
//not hit
86+
bitIndexes = new int[][] {
87+
{1, 2, 4}, // topic0
88+
{3, 5, 6} // topic1
89+
};
90+
result = (BitSet) partialMatchMethod.invoke(logBlockQuery, bitIndexes, section);
91+
Assert.assertNotNull(result);
92+
Assert.assertTrue(result.isEmpty());
93+
94+
// null condition
95+
bitIndexes = new int[0][];
96+
result = (BitSet) partialMatchMethod.invoke(logBlockQuery, bitIndexes, section);
97+
Assert.assertNotNull(result);
98+
Assert.assertTrue(result.isEmpty());
99+
}
100+
}

0 commit comments

Comments
 (0)