Skip to content

Commit 2fa67c8

Browse files
hextrazaSebastian Benjamin
andauthored
Add executor for multithreaded search (#329)
* Add executor for multithreaded search * Update utils.ts with console.error * Add executor graceful shutdown --------- Co-authored-by: Sebastian Benjamin <sebastiancbenjamin@gmail.com>
1 parent 1f45656 commit 2fa67c8

File tree

4 files changed

+69
-35
lines changed

4 files changed

+69
-35
lines changed

jbrowse/src/client/JBrowse/VariantSearch/components/VariantTableWidget.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ const VariantTableWidget = observer(props => {
7676
session.hideWidget(widget)
7777
}
7878

79+
function resetPaginationToFirstPage() {
80+
setPageSizeModel(prev => ({
81+
page: 0,
82+
pageSize: prev.pageSize,
83+
}));
84+
}
85+
7986
function handleQuery(passedFilters, pushToHistory, pageQueryModel = pageSizeModel, sortQueryModel = sortModel) {
8087
const { page = pageSizeModel.page, pageSize = pageSizeModel.pageSize } = pageQueryModel;
8188
const { field = "genomicPosition", sort = false } = sortQueryModel[0] ?? {};
@@ -461,7 +468,7 @@ const VariantTableWidget = observer(props => {
461468
columnVisibilityModel={columnVisibilityModel}
462469
pageSizeOptions={[10,25,50,100]}
463470
paginationModel={ pageSizeModel }
464-
rowCount={ totalHits }
471+
rowCount={ -1 }
465472
paginationMode="server"
466473
onPaginationModelChange = {(newModel) => {
467474
setPageSizeModel(newModel)
@@ -485,6 +492,7 @@ const VariantTableWidget = observer(props => {
485492
onSortModelChange={(newModel) => {
486493
setSortModel(newModel)
487494
handleQuery(filters, true, { page: 0, pageSize: pageSizeModel.pageSize }, newModel);
495+
resetPaginationToFirstPage()
488496
}}
489497
localeText={{
490498
MuiTablePagination: {
@@ -515,7 +523,10 @@ const VariantTableWidget = observer(props => {
515523
fieldTypeInfo: fieldTypeInfo,
516524
allowedGroupNames: allowedGroupNames,
517525
promotedFilters: promotedFilters,
518-
handleQuery: (filters) => handleQuery(filters, true, { page: 0, pageSize: pageSizeModel.pageSize}, sortModel)
526+
handleQuery: (filters) => {
527+
handleQuery(filters, true, { page: 0, pageSize: pageSizeModel.pageSize}, sortModel)
528+
resetPaginationToFirstPage()
529+
}
519530
}}
520531
/>
521532
);

jbrowse/src/client/JBrowse/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ export async function fetchLuceneQuery(filters, sessionId, trackGUID, offset, pa
366366
successCallback(jsonRes)
367367
},
368368
failure: function(res) {
369-
failureCallback("There was an error: " + res.status + "\n Status Body: " + res.responseText + "\n Session ID:" + sessionId)
369+
console.error("There was an error: " + res.status + "\n Status Body: " + res.responseText + "\n Session ID:" + sessionId)
370+
failureCallback("There was an error: status " + res.status)
370371
},
371372
params: {
372373
"searchString": encoded,

jbrowse/src/org/labkey/jbrowse/JBrowseLuceneSearch.java

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
1717
import org.apache.lucene.search.BooleanClause;
1818
import org.apache.lucene.search.BooleanQuery;
19+
import org.apache.lucene.search.ConstantScoreQuery;
1920
import org.apache.lucene.search.IndexSearcher;
2021
import org.apache.lucene.search.LRUQueryCache;
2122
import org.apache.lucene.search.MatchAllDocsQuery;
@@ -24,6 +25,7 @@
2425
import org.apache.lucene.search.ScoreDoc;
2526
import org.apache.lucene.search.Sort;
2627
import org.apache.lucene.search.SortField;
28+
import org.apache.lucene.search.TopDocs;
2729
import org.apache.lucene.search.TopFieldDocs;
2830
import org.apache.lucene.search.UsageTrackingQueryCachingPolicy;
2931
import org.apache.lucene.store.Directory;
@@ -65,6 +67,8 @@
6567
import java.util.Map;
6668
import java.util.Set;
6769
import java.util.StringTokenizer;
70+
import java.util.concurrent.ExecutorService;
71+
import java.util.concurrent.Executors;
6872
import java.util.regex.Matcher;
6973
import java.util.regex.Pattern;
7074

@@ -75,6 +79,7 @@
7579
public class JBrowseLuceneSearch
7680
{
7781
private static final Logger _log = LogHelper.getLogger(JBrowseLuceneSearch.class, "Logger related to JBrowse/Lucene indexing and queries");
82+
private static final ExecutorService SEARCH_EXECUTOR = Executors.newFixedThreadPool(JBrowseServiceImpl.get().getCoresForLuceneSearches());
7883
private final JBrowseSession _session;
7984
private final JsonFile _jsonFile;
8085
private final User _user;
@@ -108,6 +113,11 @@ public static JBrowseLuceneSearch create(String sessionId, String trackId, User
108113
private static synchronized CacheEntry getCacheEntryForSession(String trackObjectId, File indexPath) throws IOException {
109114
CacheEntry cacheEntry = _cache.get(trackObjectId);
110115

116+
if (SEARCH_EXECUTOR.isShutdown() || SEARCH_EXECUTOR.isTerminated())
117+
{
118+
throw new IllegalStateException("The server is shutting down!");
119+
}
120+
111121
// Open directory of lucene path, get a directory reader, and create the index search manager
112122
if (cacheEntry == null)
113123
{
@@ -116,7 +126,7 @@ private static synchronized CacheEntry getCacheEntryForSession(String trackObjec
116126
Directory indexDirectory = FSDirectory.open(indexPath.toPath());
117127
LRUQueryCache queryCache = new LRUQueryCache(maxCachedQueries, maxRamBytesUsed);
118128
IndexReader indexReader = DirectoryReader.open(indexDirectory);
119-
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
129+
IndexSearcher indexSearcher = new IndexSearcher(indexReader, SEARCH_EXECUTOR);
120130
indexSearcher.setQueryCache(queryCache);
121131
indexSearcher.setQueryCachingPolicy(new ForceMatchAllDocsCachingPolicy());
122132
cacheEntry = new CacheEntry(queryCache, indexSearcher, indexPath);
@@ -252,7 +262,7 @@ private SearchConfig createSearchConfig(User u, String searchString, final int p
252262

253263
if (searchString.equals(ALL_DOCS))
254264
{
255-
booleanQueryBuilder.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
265+
booleanQueryBuilder.add(new ConstantScoreQuery(new MatchAllDocsQuery()), BooleanClause.Occur.MUST);
256266
}
257267

258268
// Split input into tokens, 1 token per query separated by &
@@ -321,41 +331,46 @@ else if (numericQueryParserFields.containsKey(fieldName))
321331
}
322332

323333
private JSONObject paginateJSON(SearchConfig c) throws IOException, ParseException {
324-
// Get chunks of size {pageSize}. Default to 1 chunk -- add to the offset to get more.
325-
// We then iterate over the range of documents we want based on the offset. This does grow in memory
326-
// linearly with the number of documents, but my understanding is that these are just score,id pairs
327-
// rather than full documents, so mem usage *should* still be pretty low.
328-
// Perform the search with sorting
329-
TopFieldDocs topDocs = c.cacheEntry.indexSearcher.search(c.query, c.pageSize * (c.offset + 1), c.sort);
334+
IndexSearcher searcher = c.cacheEntry.indexSearcher;
335+
TopDocs topDocs;
336+
337+
if (c.offset == 0) {
338+
topDocs = searcher.search(c.query, c.pageSize, c.sort);
339+
} else {
340+
TopFieldDocs prev = searcher.search(c.query, c.pageSize * c.offset, c.sort);
341+
long totalHits = prev.totalHits.value;
342+
ScoreDoc[] prevHits = prev.scoreDocs;
343+
344+
if (prevHits.length < c.pageSize * c.offset)
345+
{
346+
JSONObject results = new JSONObject();
347+
results.put("data", Collections.emptyList());
348+
results.put("totalHits", totalHits);
349+
return results;
350+
}
351+
352+
ScoreDoc lastDoc = prevHits[c.pageSize * c.offset - 1];
353+
topDocs = searcher.searchAfter(lastDoc, c.query, c.pageSize, c.sort);
354+
}
355+
330356
JSONObject results = new JSONObject();
357+
List<JSONObject> data = new ArrayList<>(topDocs.scoreDocs.length);
331358

332-
// Iterate over the doc list, (either to the total end or until the page ends) grab the requested docs,
333-
// and add to returned results
334-
List<JSONObject> data = new ArrayList<>();
335-
for (int i = c.pageSize * c.offset; i < Math.min(c.pageSize * (c.offset + 1), topDocs.scoreDocs.length); i++)
359+
for (ScoreDoc sd : topDocs.scoreDocs)
336360
{
361+
Document doc = searcher.storedFields().document(sd.doc);
337362
JSONObject elem = new JSONObject();
338-
Document doc = c.cacheEntry.indexSearcher.storedFields().document(topDocs.scoreDocs[i].doc);
339-
340-
for (IndexableField field : doc.getFields())
363+
for (IndexableField f : doc.getFields())
341364
{
342-
String fieldName = field.name();
343-
String[] fieldValues = doc.getValues(fieldName);
344-
if (fieldValues.length > 1)
345-
{
346-
elem.put(fieldName, fieldValues);
347-
}
348-
else
349-
{
350-
elem.put(fieldName, fieldValues[0]);
351-
}
365+
String name = f.name();
366+
String[] vals = doc.getValues(name);
367+
elem.put(name, vals.length > 1 ? Arrays.asList(vals) : vals[0]);
352368
}
353369
data.add(elem);
354370
}
355371

356372
results.put("data", data);
357373
results.put("totalHits", topDocs.totalHits.value);
358-
359374
return results;
360375
}
361376

@@ -679,17 +694,20 @@ public String getName()
679694
return "JBrowse-Lucene Shutdown Listener";
680695
}
681696

682-
@Override
683-
public void shutdownPre()
684-
{
685-
686-
}
687-
688697
@Override
689698
public void shutdownStarted()
690699
{
691700
_log.info("Clearing all open JBrowse/Lucene cached readers");
692701
JBrowseLuceneSearch.emptyCache();
702+
703+
try
704+
{
705+
SEARCH_EXECUTOR.shutdown();
706+
}
707+
catch (Exception e)
708+
{
709+
_log.error("Error shutting down SEARCH_EXECUTOR", e);
710+
}
693711
}
694712
}
695713

jbrowse/src/org/labkey/jbrowse/JBrowseServiceImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,8 @@ public boolean isAvailable(Container c)
435435
return c.getActiveModules().contains(ModuleLoader.getInstance().getModule(JBrowseModule.class));
436436
}
437437
}
438+
439+
public int getCoresForLuceneSearches() {
440+
return Runtime.getRuntime().availableProcessors();
441+
}
438442
}

0 commit comments

Comments
 (0)