You can use Tablestore SDK for Java to scan matching rows in a search index concurrently and export the complete result set when result order is not required.
Prerequisites
Install Tablestore SDK for Java and initialize a client.
Parallel scan requires Tablestore SDK for Java 5.6.0 or later. For version information, see Version history.
How it works
Parallel scan scans all rows that match a query in a search index. The scan does not guarantee the global order of results and does not support sorting or aggregation. If you need ordered results, aggregation, or search results for end users, use the Search operation.
A single-worker scan is simpler to configure. A multi-worker scan reads multiple splits concurrently and typically provides higher scan throughput than a single-worker scan.
A parallel scan consists of the following steps:
Call
computeSplitsto obtain the maximum parallelismsplitsSizeand the task session IDsessionIdof the search index.Configure
ParallelScanRequest. For a single-worker scan, you can omitmaxParallelandcurrentParallelId. For a multi-worker scan, use the same query,sessionId, andmaxParallelfor all workers, and specify a differentcurrentParallelIdfor each worker.Call
createParallelScanIteratorto automatically read all pages, or callparallelScanand usenextTokento manually retrieve subsequent pages.Wait for all workers to finish and merge their unordered results.
Within a sessionId, the data snapshot is fixed when parallelScan is called for the first time. Rows that are added or updated while the task is running are not included in the snapshot. You can omit sessionId. However, if server-side load balancing or a similar change occurs during the scan, the results may contain a small number of duplicate rows. We recommend that you call computeSplits first and include the returned sessionId in subsequent requests.
The session may expire early when a dynamic schema change switches the index or when server-side failover or load balancing occurs. In this case, the server returns OTSSessionExpired. A client-side network error can also interrupt the scan. If one of these errors occurs, discard the incomplete results, call computeSplits again, and restart the entire scan task from the beginning. A maximum of 10 parallel scan tasks can run concurrently on a search index. For other limits, see Search index limits.
Call computeSplits to compute splits. Call parallelScan to manually retrieve pages, or call createParallelScanIterator to automatically retrieve all pages.
ComputeSplitsResponse computeSplits(ComputeSplitsRequest request)
ParallelScanResponse parallelScan(ParallelScanRequest request)
RowIterator createParallelScanIterator(ParallelScanRequest request)
The following sample scans all rows by using one worker and returns the category and price fields. RowIterator automatically retrieves subsequent pages.
String tableName = "example_table";
String indexName = "example_index";
ComputeSplitsRequest splitsRequest = ComputeSplitsRequest.newBuilder()
.tableName(tableName)
.splitsOptions(new SearchIndexSplitsOptions(indexName))
.build();
ComputeSplitsResponse splitsResponse =
client.computeSplits(splitsRequest);
ScanQuery scanQuery = ScanQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(2000)
.build();
ParallelScanRequest request = ParallelScanRequest.newBuilder()
.tableName(tableName)
.indexName(indexName)
.scanQuery(scanQuery)
.addColumnsToGet("category", "price")
.sessionId(splitsResponse.getSessionId())
.build();
RowIterator iterator = client.createParallelScanIterator(request);
while (iterator.hasNext()) {
Row row = iterator.next();
System.out.println(row);
}
Parameters
Split request
splitsRequest is of the ComputeSplitsRequest type and contains the following parameters.
|
Name |
Type |
Description |
|
tableName (required) |
String |
The name of the data table. |
|
splitsOptions (required) |
SplitsOptions |
The split configuration. For a search index, set this parameter to |
Search index split configuration
splitsRequest.splitsOptions is of the SearchIndexSplitsOptions type and contains the following parameter.
|
Name |
Type |
Description |
|
indexName (required) |
String |
The name of the search index. |
Scan request
request is of the ParallelScanRequest type and contains the following parameters.
|
Name |
Type |
Description |
|
tableName (required) |
String |
The name of the data table. |
|
indexName (required) |
String |
The name of the search index. |
|
scanQuery (required) |
ScanQuery |
The scan condition, number of rows returned per request, and parallelism configuration. |
|
columnsToGet (optional) |
SearchRequest.ColumnsToGet |
The columns to return. If this parameter is not specified, only primary key columns are returned. |
|
sessionId (optional) |
|
The task session ID returned by |
|
timeoutInMillisecond (optional) |
int |
The request-level timeout period in milliseconds. The default value is |
Scan configuration
request.scanQuery is of the ScanQuery type and contains the following parameters.
|
Name |
Type |
Description |
|
query (required) |
Query |
The query condition that defines the scan scope. Parallel scan supports term, match, range, geo, nested, and other queries. Configure the query in the same way as for the Search operation. To scan all rows in the search index, set the query type to |
|
limit (optional) |
Integer |
The maximum number of rows returned per request. Default value: 2000. We recommend that you use the default value. |
|
maxParallel (optional) |
Integer |
The parallelism of the scan task. The value cannot exceed |
|
currentParallelId (optional) |
Integer |
The ID of the current worker. This parameter is required if |
|
aliveTime (optional) |
Integer |
The maximum interval between two page requests for the scan task, in seconds. Valid values: 1 to 600. Default value: 60. The validity period is refreshed each time rows are successfully retrieved. |
|
token (optional) |
|
The pagination token. Omit this parameter from the first request. For manual pagination, set it to |
The maximum limit value supported by the server is 10000. We recommend that you do not set limit to this value.
Columns to return
request.columnsToGet is of the SearchRequest.ColumnsToGet type and contains the following parameters.
|
Name |
Type |
Description |
|
columns (optional) |
|
The names of search index fields to return. A field that exists only in the data table but is not included in the search index cannot be returned. Date, Geo-point, IP, Vector, JSON/Nested, and array fields can be returned. |
|
returnAllFromIndex (optional) |
boolean |
Specifies whether to return all fields in the search index. Default value: |
|
returnAll (optional) |
boolean |
Parallel scan does not support this parameter. Do not set it to |
Return values
Split information
computeSplits returns ComputeSplitsResponse, which contains the following fields.
|
Name |
Type |
Description |
|
sessionId |
|
The task session ID that is used to scan rows in the same data snapshot. |
|
splitsSize |
Integer |
The maximum parallelism supported by the search index. |
Scan results
parallelScan returns ParallelScanResponse, which contains the following fields.
|
Name |
Type |
Description |
|
rows |
|
The rows returned in the current response. |
|
nextToken |
|
The token for the next page. If this value is |
|
bodyBytes |
long |
The size of the response body in bytes. |
createParallelScanIterator returns RowIterator. The iterator automatically uses nextToken to retrieve subsequent pages and returns one Row per iteration. It does not support retrieving the total number of matching rows.
Examples
Scan with multiple workers
The following sample creates multiple scan tasks based on splitsSize. Each task uses a unique currentParallelId, and all tasks share the same sessionId and maxParallel. The thread pool does not exceed the number of CPU cores on the client to avoid excessive client load.
String tableName = "example_table";
String indexName = "example_index";
ComputeSplitsResponse splitsResponse = client.computeSplits(
ComputeSplitsRequest.newBuilder()
.tableName(tableName)
.splitsOptions(new SearchIndexSplitsOptions(indexName))
.build());
int maxParallel = splitsResponse.getSplitsSize();
int workerCount = Math.min(
maxParallel, Runtime.getRuntime().availableProcessors());
ExecutorService executor = Executors.newFixedThreadPool(workerCount);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
try {
for (int parallelId = 0; parallelId < maxParallel; parallelId++) {
final int currentParallelId = parallelId;
futures.add(executor.submit(new Callable<Integer>() {
@Override
public Integer call() {
ScanQuery scanQuery = ScanQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(2000)
.maxParallel(maxParallel)
.currentParallelId(currentParallelId)
.build();
ParallelScanRequest request =
ParallelScanRequest.newBuilder()
.tableName(tableName)
.indexName(indexName)
.scanQuery(scanQuery)
.returnAllColumnsFromIndex(true)
.sessionId(splitsResponse.getSessionId())
.build();
int rowCount = 0;
RowIterator iterator =
client.createParallelScanIterator(request);
while (iterator.hasNext()) {
Row row = iterator.next();
System.out.println(row);
rowCount++;
}
return rowCount;
}
}));
}
long totalRows = 0;
for (Future<Integer> future : futures) {
totalRows += future.get();
}
System.out.println("Total rows: " + totalRows);
} finally {
executor.shutdown();
}
Manually retrieve pages
The following sample directly calls parallelScan and passes nextToken from each response to the next request until the current worker has retrieved all rows.
String tableName = "example_table";
String indexName = "example_index";
ComputeSplitsResponse splitsResponse = client.computeSplits(
ComputeSplitsRequest.newBuilder()
.tableName(tableName)
.splitsOptions(new SearchIndexSplitsOptions(indexName))
.build());
ScanQuery scanQuery = ScanQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(2000)
.maxParallel(1)
.currentParallelId(0)
.build();
ParallelScanRequest request = ParallelScanRequest.newBuilder()
.tableName(tableName)
.indexName(indexName)
.scanQuery(scanQuery)
.addColumnsToGet("category", "price")
.sessionId(splitsResponse.getSessionId())
.build();
long totalRows = 0;
do {
ParallelScanResponse response = client.parallelScan(request);
for (Row row : response.getRows()) {
System.out.println(row);
totalRows++;
}
scanQuery.setToken(response.getNextToken());
} while (scanQuery.getToken() != null);
System.out.println("Total rows: " + totalRows);