Skip to content

Commit 79b7a7a

Browse files
committed
GH-5553: Track LMDB index recommendation demand
1 parent 72572e7 commit 79b7a7a

2 files changed

Lines changed: 369 additions & 38 deletions

File tree

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java

Lines changed: 270 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424

2525
import java.io.IOException;
2626
import java.nio.ByteBuffer;
27+
import java.util.ArrayList;
28+
import java.util.Collection;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.ConcurrentMap;
33+
import java.util.concurrent.atomic.LongAdder;
2734

2835
import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager;
2936
import org.eclipse.rdf4j.sail.SailException;
@@ -41,6 +48,8 @@
4148
*/
4249
class LmdbRecordIterator implements RecordIterator {
4350
private static final Logger log = LoggerFactory.getLogger(LmdbRecordIterator.class);
51+
private static final List<Character> COMPONENT_ORDER = List.of('s', 'p', 'o', 'c');
52+
private static final ConcurrentMap<String, LongAdder> RECOMMENDED_INDEX_TRACKER = new ConcurrentHashMap<>();
4453
private final Pool pool;
4554

4655
private final TripleIndex index;
@@ -142,6 +151,14 @@ class LmdbRecordIterator implements RecordIterator {
142151
}
143152
}
144153

154+
static ConcurrentMap<String, LongAdder> getRecommendedIndexTracker() {
155+
return RECOMMENDED_INDEX_TRACKER;
156+
}
157+
158+
static void resetIndexRecommendationTracker() {
159+
RECOMMENDED_INDEX_TRACKER.clear();
160+
}
161+
145162
private static String computeIndexName(TripleIndex index, long subj, long pred, long obj, long context) {
146163
String actual = new String(index.getFieldSeq());
147164
int boundCount = countBound(subj, pred, obj, context);
@@ -154,12 +171,12 @@ private static String computeIndexName(TripleIndex index, long subj, long pred,
154171
return actual;
155172
}
156173

157-
String recommendation = buildRecommendedIndex(subj, pred, obj, context);
158-
if (recommendation == null || recommendation.equals(actual)) {
174+
CandidateIndex recommendation = selectRecommendedIndex(actual, subj, pred, obj, context);
175+
if (recommendation == null) {
159176
return actual;
160177
}
161178

162-
return actual + " (scan; consider " + recommendation + ")";
179+
return actual + " (scan; consider " + recommendation.name + ")";
163180
}
164181

165182
private static int countBound(long subj, long pred, long obj, long context) {
@@ -179,29 +196,264 @@ private static int countBound(long subj, long pred, long obj, long context) {
179196
return count;
180197
}
181198

182-
private static String buildRecommendedIndex(long subj, long pred, long obj, long context) {
183-
StringBuilder recommendation = new StringBuilder(4);
184-
appendIfBound(recommendation, 's', subj >= 0);
185-
appendIfBound(recommendation, 'p', pred >= 0);
186-
appendIfBound(recommendation, 'o', obj >= 0);
187-
appendIfBound(recommendation, 'c', context >= 0);
188-
189-
if (recommendation.length() == 0) {
199+
private static CandidateIndex selectRecommendedIndex(String actual, long subj, long pred, long obj,
200+
long context) {
201+
List<CandidateIndex> candidates = buildCandidateIndexes(subj, pred, obj, context);
202+
if (candidates.isEmpty()) {
190203
return null;
191204
}
192205

193-
for (char component : new char[] { 's', 'p', 'o', 'c' }) {
194-
if (recommendation.indexOf(String.valueOf(component)) < 0) {
195-
recommendation.append(component);
206+
CandidateIndex best = null;
207+
for (CandidateIndex candidate : candidates) {
208+
if (candidate.name.equals(actual)) {
209+
continue;
210+
}
211+
212+
if (best == null || candidate.count > best.count
213+
|| (candidate.count == best.count && candidate.patternScore > best.patternScore)
214+
|| (candidate.count == best.count && candidate.patternScore == best.patternScore
215+
&& candidate.orderDeviation < best.orderDeviation)
216+
|| (candidate.count == best.count && candidate.patternScore == best.patternScore
217+
&& candidate.orderDeviation == best.orderDeviation
218+
&& candidate.name.compareTo(best.name) < 0)) {
219+
best = candidate;
220+
}
221+
}
222+
223+
if (best == null) {
224+
best = candidates.get(0);
225+
}
226+
227+
for (CandidateIndex candidate : candidates) {
228+
candidate.counter.increment();
229+
}
230+
231+
return best;
232+
}
233+
234+
private static List<CandidateIndex> buildCandidateIndexes(long subj, long pred, long obj, long context) {
235+
List<Character> boundComponents = gatherBoundComponents(subj, pred, obj, context);
236+
if (boundComponents.isEmpty()) {
237+
return Collections.emptyList();
238+
}
239+
240+
List<Character> preferredOrder = determinePreferredOrder(subj, pred, obj, context, boundComponents);
241+
242+
List<String> boundPermutations = permuteBoundComponents(boundComponents);
243+
List<String> unboundPermutations = permuteUnboundComponents(boundComponents);
244+
245+
if (unboundPermutations.isEmpty()) {
246+
unboundPermutations = Collections.singletonList("");
247+
}
248+
249+
List<CandidateIndex> result = new ArrayList<>(boundPermutations.size() * unboundPermutations.size());
250+
251+
for (String bound : boundPermutations) {
252+
for (String suffix : unboundPermutations) {
253+
String candidate = bound + suffix;
254+
addCandidate(result, candidate, preferredOrder, subj, pred, obj, context);
255+
}
256+
}
257+
258+
return result;
259+
}
260+
261+
private static void addCandidate(Collection<CandidateIndex> candidates, String candidate,
262+
List<Character> preferredOrder, long subj, long pred, long obj, long context) {
263+
LongAdder counter = RECOMMENDED_INDEX_TRACKER.computeIfAbsent(candidate, key -> new LongAdder());
264+
long count = counter.sum();
265+
int score = computePatternScore(candidate, subj, pred, obj, context);
266+
int deviation = computeDeviation(candidate, preferredOrder);
267+
candidates.add(new CandidateIndex(candidate, count, score, deviation, counter));
268+
}
269+
270+
private static List<Character> gatherBoundComponents(long subj, long pred, long obj, long context) {
271+
List<Character> bound = new ArrayList<>(4);
272+
if (subj >= 0) {
273+
bound.add('s');
274+
}
275+
if (pred >= 0) {
276+
bound.add('p');
277+
}
278+
if (obj >= 0) {
279+
bound.add('o');
280+
}
281+
if (context >= 0) {
282+
bound.add('c');
283+
}
284+
return bound;
285+
}
286+
287+
private static List<Character> determinePreferredOrder(long subj, long pred, long obj, long context,
288+
List<Character> boundComponents) {
289+
List<Character> order = new ArrayList<>(COMPONENT_ORDER.size());
290+
291+
if (subj >= 0) {
292+
order.add('s');
293+
}
294+
295+
boolean subjectBound = subj >= 0;
296+
boolean contextBound = context >= 0;
297+
boolean predicateBound = pred >= 0;
298+
boolean objectBound = obj >= 0;
299+
300+
if (contextBound && !subjectBound) {
301+
if (objectBound) {
302+
order.add('o');
303+
}
304+
if (predicateBound) {
305+
order.add('p');
306+
}
307+
if (contextBound) {
308+
order.add('c');
309+
}
310+
} else {
311+
if (predicateBound) {
312+
order.add('p');
313+
}
314+
if (objectBound) {
315+
order.add('o');
316+
}
317+
if (contextBound) {
318+
order.add('c');
319+
}
320+
}
321+
322+
for (Character component : boundComponents) {
323+
if (!order.contains(component)) {
324+
order.add(component);
325+
}
326+
}
327+
328+
for (Character component : COMPONENT_ORDER) {
329+
if (!order.contains(component)) {
330+
order.add(component);
196331
}
197332
}
198333

199-
return recommendation.toString();
334+
return order;
335+
}
336+
337+
private static List<String> permuteBoundComponents(List<Character> boundComponents) {
338+
char[] items = toCharArray(boundComponents);
339+
boolean[] used = new boolean[items.length];
340+
StringBuilder current = new StringBuilder(items.length);
341+
List<String> result = new ArrayList<>();
342+
permuteCharacters(items, used, current, result);
343+
return result;
344+
}
345+
346+
private static List<String> permuteUnboundComponents(List<Character> boundComponents) {
347+
List<Character> unbound = new ArrayList<>(COMPONENT_ORDER);
348+
for (Character component : boundComponents) {
349+
unbound.remove(component);
350+
}
351+
352+
if (unbound.isEmpty()) {
353+
return Collections.emptyList();
354+
}
355+
356+
char[] items = toCharArray(unbound);
357+
boolean[] used = new boolean[items.length];
358+
StringBuilder current = new StringBuilder(items.length);
359+
List<String> permutations = new ArrayList<>();
360+
permuteCharacters(items, used, current, permutations);
361+
return permutations;
362+
}
363+
364+
private static void permuteCharacters(char[] items, boolean[] used, StringBuilder current,
365+
List<String> result) {
366+
if (current.length() == items.length) {
367+
result.add(current.toString());
368+
return;
369+
}
370+
371+
for (int i = 0; i < items.length; i++) {
372+
if (!used[i]) {
373+
used[i] = true;
374+
current.append(items[i]);
375+
permuteCharacters(items, used, current, result);
376+
current.deleteCharAt(current.length() - 1);
377+
used[i] = false;
378+
}
379+
}
380+
}
381+
382+
private static int computeDeviation(String sequence, List<Character> preferredOrder) {
383+
int deviation = 0;
384+
for (int i = 0; i < sequence.length(); i++) {
385+
char component = sequence.charAt(i);
386+
int preferred = preferredOrder.indexOf(component);
387+
if (preferred < 0) {
388+
preferred = preferredOrder.size();
389+
}
390+
deviation += Math.abs(preferred - i);
391+
}
392+
return deviation;
393+
}
394+
395+
private static int computePatternScore(String indexName, long subj, long pred, long obj, long context) {
396+
int score = 0;
397+
for (int i = 0; i < indexName.length(); i++) {
398+
char component = indexName.charAt(i);
399+
switch (component) {
400+
case 's':
401+
if (subj >= 0) {
402+
score++;
403+
} else {
404+
return score;
405+
}
406+
break;
407+
case 'p':
408+
if (pred >= 0) {
409+
score++;
410+
} else {
411+
return score;
412+
}
413+
break;
414+
case 'o':
415+
if (obj >= 0) {
416+
score++;
417+
} else {
418+
return score;
419+
}
420+
break;
421+
case 'c':
422+
if (context >= 0) {
423+
score++;
424+
} else {
425+
return score;
426+
}
427+
break;
428+
default:
429+
throw new IllegalArgumentException("invalid component '" + component + "' in index: "
430+
+ indexName);
431+
}
432+
}
433+
return score;
434+
}
435+
436+
private static char[] toCharArray(List<Character> components) {
437+
char[] items = new char[components.size()];
438+
for (int i = 0; i < components.size(); i++) {
439+
items[i] = components.get(i);
440+
}
441+
return items;
200442
}
201443

202-
private static void appendIfBound(StringBuilder builder, char component, boolean bound) {
203-
if (bound) {
204-
builder.append(component);
444+
private static final class CandidateIndex {
445+
final String name;
446+
final long count;
447+
final int patternScore;
448+
final int orderDeviation;
449+
final LongAdder counter;
450+
451+
CandidateIndex(String name, long count, int patternScore, int orderDeviation, LongAdder counter) {
452+
this.name = name;
453+
this.count = count;
454+
this.patternScore = patternScore;
455+
this.orderDeviation = orderDeviation;
456+
this.counter = counter;
205457
}
206458
}
207459

0 commit comments

Comments
 (0)