Si no requiere un orden específico en los resultados de la consulta, utilice la función de escaneo paralelo para obtenerlos de manera eficiente.
Tablestore SDK for Java V5.6.0 o posterior es compatible con la función de escaneo paralelo. Antes de usar esta función, asegúrese de contar con la versión correcta de Tablestore SDK for Java. Para obtener más información sobre el historial de versiones de Tablestore SDK for Java, consulte Version history of Tablestore SDK for Java.
Contexto
La función de índice de búsqueda permite llamar a la operación Search para aprovechar todas las capacidades de consulta y análisis, como la clasificación y la agregación. Los resultados se devuelven desde la operación Search en un orden determinado.
En algunos casos, por ejemplo, al conectar Tablestore a un entorno de cómputo como Spark o Presto, o cuando se desea consultar un grupo específico de objetos, la velocidad de consulta puede ser más importante que el orden de los resultados. Para mejorar la velocidad, Tablestore proporciona la operación ParallelScan dentro de la función de índice de búsqueda.
A diferencia de la operación Search, ParallelScan admite todas las funciones de consulta, pero no ofrece capacidades analíticas como clasificación ni agregación. Esto permite aumentar la velocidad de consulta más de cinco veces. Es posible llamar a la operación ParallelScan para exportar cientos de millones de filas de datos en menos de un minuto. Además, la capacidad de exportación de datos escala horizontalmente sin límites superiores.
El número máximo de filas que devuelve cada llamada a ParallelScan es mayor que el de la operación Search. Mientras que Search devuelve hasta 100 filas por llamada, ParallelScan devuelve hasta 2,000 filas. La función de escaneo paralelo permite usar varios hilos para iniciar solicitudes en una sesión de forma paralela, lo cual acelera la exportación de datos.
Escenarios
Utilice la operación Search si necesita clasificar o agregar los resultados de la consulta, o si la solicitud proviene de un usuario final.
Utilice la operación ParallelScan si no requiere ordenar los resultados y desea devolver todas las coincidencias de forma eficiente, o bien cuando un entorno de cómputo como Spark o Presto extrae los datos.
Funciones
A continuación se describen las diferencias entre la operación ParallelScan y la operación Search.
-
Resultados estables
Las tareas de escaneo paralelo mantienen estado. Dentro de una sesión, el conjunto de resultados de los datos escaneados queda determinado por el estado de los datos al momento de iniciar la primera solicitud. Si se insertan o modifican datos después de enviar dicha solicitud, el conjunto de resultados no se ve afectado.
-
Sesiones
ImportanteSi resulta difícil obtener el ID de sesión, llame a la operación ParallelScan para iniciar una solicitud sin este identificador. No obstante, al enviar una solicitud sin ID de sesión, existe una probabilidad muy baja de que aparezcan datos duplicados en el conjunto de resultados obtenido.
Las operaciones relacionadas con el escaneo paralelo utilizan sesiones. El ID de sesión sirve para determinar el conjunto de resultados de los datos escaneados. El siguiente proceso describe cómo obtener y usar un ID de sesión:
Llame a la operación ComputeSplits para consultar el número máximo de tareas de escaneo paralelo y el ID de sesión actual.
Inicie varias solicitudes de escaneo paralelo para leer datos. Debe especificar el ID de sesión actual y los ID de las tareas de escaneo paralelo en estas solicitudes.
Tablestore devuelve el código de error OTSSessionExpired cuando ocurren excepciones de red, excepciones de hilos, modificaciones dinámicas en los esquemas o cambios de índice durante el proceso de escaneo paralelo, lo que detiene el escaneo de datos. En estos casos, inicie otra tarea de escaneo paralelo para volver a escanear los datos.
Las tareas de escaneo paralelo que comparten el mismo ID de sesión y el mismo valor del parámetro ScanQuery se consideran una sola tarea. Una tarea de escaneo paralelo comienza cuando se envía la primera solicitud ParallelScan y termina cuando se escanean todos los datos o cuando expira el token.
-
Número máximo de tareas de escaneo paralelo en una sola solicitud
El número máximo de tareas de escaneo paralelo en una sola solicitud admitido por la operación ParallelScan se determina mediante la respuesta de la solicitud ComputeSplits. Un mayor volumen de datos requiere más tareas de escaneo paralelo dentro de una sesión.
Una sola solicitud se define mediante una instrucción de consulta. Por ejemplo, si usa la operación Search para consultar resultados donde el valor de city es Hangzhou, todos los datos que coincidan con esta condición se devolverán en el resultado. Sin embargo, si utiliza la operación ParallelScan y el número de tareas de escaneo paralelo en una sesión es 2, cada solicitud ParallelScan devolverá la mitad de los resultados. El conjunto completo de resultados se compone de los dos conjuntos obtenidos en paralelo.
-
Rendimiento
La velocidad de consulta de una solicitud ParallelScan que incluye una tarea de escaneo paralelo es cinco veces más rápida que la de una solicitud Search. Al utilizar la función de escaneo paralelo, la velocidad de consulta aumenta conforme crece el número de tareas de escaneo paralelo en la sesión. Por ejemplo, si una sesión incluye ocho tareas de escaneo paralelo, la velocidad de consulta puede mejorar cuatro veces.
-
Costo
Las solicitudes ParallelScan consumen menos recursos y tienen un precio menor. Para exportar grandes volúmenes de datos, utilice la operación ParallelScan.
Límites
El número máximo de tareas de escaneo paralelo es 10. Puede ajustar este límite según sus necesidades empresariales.
-
Los índices de búsqueda solo pueden devolver columnas existentes. Sin embargo, no es posible devolver columnas de tipo DATE ni NESTED.
La operación ParallelScan puede devolver valores de columnas ARRAY y GEOPOINT. No obstante, los valores devueltos están formateados y pueden diferir de los valores escritos originalmente en la tabla de datos. Por ejemplo, si escribe [1,2, 3, 4] en una columna ARRAY, la operación ParallelScan devuelve [1,2,3,4] como valor. Si escribe
10,50en una columna GEOPOINT, la operación ParallelScan devuelve10.0,50.0como valor.Establezca el parámetro ReturnType en RETURN_ALL_INDEX o RETURN_SPECIFIED, pero no en RETURN_ALL.
El número máximo de filas que puede devolver cada llamada a ParallelScan se especifica mediante el parámetro limit. El valor predeterminado del parámetro limit es 2,000. Si especifica un valor superior a 2,000, el rendimiento prácticamente no cambia aunque aumente el límite.
Operaciones de API
Llame a las siguientes operaciones de API para utilizar la función de escaneo paralelo:
ComputeSplits: Llame a esta operación para consultar el número máximo de tareas de escaneo paralelo para una sola solicitud ParallelScan.
ParallelScan: Llame a esta operación para exportar datos.
Uso de SDK de Tablestore
Utilice los siguientes SDK de Tablestore para escanear datos en paralelo:
Tablestore SDK for Java: Parallel scan
Tablestore SDK for Go: Parallel scan
Tablestore SDK for Python: Parallel scan
Tablestore SDK for Node.js: Parallel scan
Tablestore SDK for .NET: Parallel scan
Tablestore SDK for PHP: Parallel scan
Parámetros
|
Parámetro |
Descripción |
|
|
tableName |
Nombre de la tabla de datos. |
|
|
indexName |
Nombre del índice de búsqueda. |
|
|
scanQuery |
query |
Instrucción de consulta para el índice de búsqueda. La operación admite consultas por término, consultas difusas, consultas por rango, consultas geográficas y consultas anidadas, similares a las de la operación Search. |
|
limit |
Número máximo de filas que puede devolver cada llamada a ParallelScan. |
|
|
maxParallel |
Número máximo de tareas de escaneo paralelo por solicitud. Este valor varía según el volumen de datos; un mayor volumen requiere más tareas de escaneo paralelo por solicitud. Use la operación ComputeSplits para consultar el número máximo de tareas de escaneo paralelo por solicitud. |
|
|
currentParallelId |
ID de la tarea de escaneo paralelo en la solicitud. Valores válidos: [0, Valor de maxParallel) |
|
|
token |
Token utilizado para paginar los resultados de la consulta. Los resultados de la solicitud ParallelScan contienen el token para la página siguiente. Use este token para recuperar la siguiente página. |
|
|
aliveTime |
Periodo de validez de la tarea de escaneo paralelo actual. Este periodo también corresponde a la validez del token. Unidad: segundos. Valor predeterminado: 60. Se recomienda usar el valor predeterminado. Si no se inicia la siguiente solicitud dentro del periodo de validez, ya no será posible consultar más datos. El tiempo de validez del token se actualiza cada vez que se envía una solicitud. Nota
Las sesiones caducan antes de tiempo si se modifican dinámicamente los índices de conmutación en los esquemas, si falla un servidor individual o si se realiza un balanceo de carga en el lado del servidor. En este caso, debe recrear las sesiones. |
|
|
columnsToGet |
Nombre de la columna que se devolverá en el resultado de agrupación. Agregue el nombre de la columna a Columns. Si desea que se devuelvan todas las columnas del índice de búsqueda, utilice la operación ReturnAllFromIndex, que es más concisa. Importante
No se puede usar ReturnAll en este contexto. |
|
|
sessionId |
ID de sesión de la tarea de escaneo paralelo. Llame a la operación ComputeSplits para crear una sesión y consultar el número máximo de tareas de escaneo paralelo admitidas por la solicitud. |
|
Ejemplo
Según sus necesidades empresariales, escanee datos mediante un solo hilo o utilizando varios hilos simultáneamente.
Escaneo de datos con un solo hilo
Al usar el escaneo paralelo, el código para una solicitud con un solo hilo es más sencillo que el de una solicitud con múltiples hilos. Los parámetros currentParallelId y maxParallel no son obligatorios para una solicitud de un solo hilo. La solicitud ParallelScan con un solo hilo ofrece un throughput mayor que la solicitud Search. Sin embargo, su throughput es inferior al de una solicitud ParallelScan con múltiples hilos.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.alicloud.openservices.tablestore.SyncClient;
import com.alicloud.openservices.tablestore.model.ComputeSplitsRequest;
import com.alicloud.openservices.tablestore.model.ComputeSplitsResponse;
import com.alicloud.openservices.tablestore.model.Row;
import com.alicloud.openservices.tablestore.model.SearchIndexSplitsOptions;
import com.alicloud.openservices.tablestore.model.iterator.RowIterator;
import com.alicloud.openservices.tablestore.model.search.ParallelScanRequest;
import com.alicloud.openservices.tablestore.model.search.ParallelScanResponse;
import com.alicloud.openservices.tablestore.model.search.ScanQuery;
import com.alicloud.openservices.tablestore.model.search.SearchRequest.ColumnsToGet;
import com.alicloud.openservices.tablestore.model.search.query.MatchAllQuery;
import com.alicloud.openservices.tablestore.model.search.query.Query;
import com.alicloud.openservices.tablestore.model.search.query.QueryBuilders;
public class Test {
public static List<Row> scanQuery(final SyncClient client) {
String tableName = "<TableName>";
String indexName = "<IndexName>";
// Query the session ID and the maximum number of parallel scan tasks supported by the request.
ComputeSplitsRequest computeSplitsRequest = new ComputeSplitsRequest();
computeSplitsRequest.setTableName(tableName);
computeSplitsRequest.setSplitsOptions(new SearchIndexSplitsOptions(indexName));
ComputeSplitsResponse computeSplitsResponse = client.computeSplits(computeSplitsRequest);
byte[] sessionId = computeSplitsResponse.getSessionId();
int splitsSize = computeSplitsResponse.getSplitsSize();
/*
* Create a parallel scan request.
*/
ParallelScanRequest parallelScanRequest = new ParallelScanRequest();
parallelScanRequest.setTableName(tableName);
parallelScanRequest.setIndexName(indexName);
ScanQuery scanQuery = new ScanQuery();
// This query determines the range of the data to scan. You can create a nested and complex query.
Query query = new MatchAllQuery();
scanQuery.setQuery(query);
// Specify the maximum number of rows that can be returned by each ParallelScan call.
scanQuery.setLimit(2000);
parallelScanRequest.setScanQuery(scanQuery);
ColumnsToGet columnsToGet = new ColumnsToGet();
columnsToGet.setColumns(Arrays.asList("col_1", "col_2"));
parallelScanRequest.setColumnsToGet(columnsToGet);
parallelScanRequest.setSessionId(sessionId);
/*
* Use builder to create a parallel scan request that has the same features as the preceding request.
*/
ParallelScanRequest parallelScanRequestByBuilder = ParallelScanRequest.newBuilder()
.tableName(tableName)
.indexName(indexName)
.scanQuery(ScanQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(2000)
.build())
.addColumnsToGet("col_1", "col_2")
.sessionId(sessionId)
.build();
List<Row> result = new ArrayList<>();
/*
* Use the native API operation to scan data.
*/
{
ParallelScanResponse parallelScanResponse = client.parallelScan(parallelScanRequest);
// Query the token of ScanQuery for the next request.
byte[] nextToken = parallelScanResponse.getNextToken();
// Obtain the data.
List<Row> rows = parallelScanResponse.getRows();
result.addAll(rows);
while (nextToken != null) {
// Specify the token.
parallelScanRequest.getScanQuery().setToken(nextToken);
// Continue to scan the data.
parallelScanResponse = client.parallelScan(parallelScanRequest);
// Obtain the data.
rows = parallelScanResponse.getRows();
result.addAll(rows);
nextToken = parallelScanResponse.getNextToken();
}
}
/*
* Recommended method.
* Use an iterator to scan all matched data. This method has the same query speed but is easier to use compared with the previous method.
*/
{
RowIterator iterator = client.createParallelScanIterator(parallelScanRequestByBuilder);
while (iterator.hasNext()) {
Row row = iterator.next();
result.add(row);
// Obtain the specific values.
String col_1 = row.getLatestColumn("col_1").getValue().asString();
long col_2 = row.getLatestColumn("col_2").getValue().asLong();
}
}
/*
* If the operation fails, retry the operation. If the caller of this function has a retry mechanism or if you do not want to retry the failed operation, you can ignore this part.
* To ensure availability, we recommend that you start a new parallel scan task when exceptions occur.
* The following exceptions may occur when you send a ParallelScan request:
* 1. A session exception occurs on the server side. The error code is OTSSessionExpired.
* 2. An exception such as a network exception occurs on the client side.
*/
try {
// Execute the processing logic.
{
RowIterator iterator = client.createParallelScanIterator(parallelScanRequestByBuilder);
while (iterator.hasNext()) {
Row row = iterator.next();
// Process rows of data. If you have sufficient memory resources, you can add the rows to a list.
result.add(row);
}
}
} catch (Exception ex) {
// Retry the processing logic.
{
result.clear();
RowIterator iterator = client.createParallelScanIterator(parallelScanRequestByBuilder);
while (iterator.hasNext()) {
Row row = iterator.next();
// Process rows of data. If you have sufficient memory resources, you can add the rows to a list.
result.add(row);
}
}
}
return result;
}
}
Escaneo de datos con múltiples hilos
import com.alicloud.openservices.tablestore.SyncClient;
import com.alicloud.openservices.tablestore.model.ComputeSplitsRequest;
import com.alicloud.openservices.tablestore.model.ComputeSplitsResponse;
import com.alicloud.openservices.tablestore.model.Row;
import com.alicloud.openservices.tablestore.model.SearchIndexSplitsOptions;
import com.alicloud.openservices.tablestore.model.iterator.RowIterator;
import com.alicloud.openservices.tablestore.model.search.ParallelScanRequest;
import com.alicloud.openservices.tablestore.model.search.ScanQuery;
import com.alicloud.openservices.tablestore.model.search.query.QueryBuilders;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
public class Test {
public static void scanQueryWithMultiThread(final SyncClient client, String tableName, String indexName) throws InterruptedException {
// Query the number of CPU cores on the client.
final int cpuProcessors = Runtime.getRuntime().availableProcessors();
// Specify the number of parallel threads for the client. We recommend that you specify the number of CPU cores on the client as the number of parallel threads for the client to prevent impact on the query performance.
final Semaphore semaphore = new Semaphore(cpuProcessors);
// Query the session ID and the maximum number of parallel scan tasks supported by the request.
ComputeSplitsRequest computeSplitsRequest = new ComputeSplitsRequest();
computeSplitsRequest.setTableName(tableName);
computeSplitsRequest.setSplitsOptions(new SearchIndexSplitsOptions(indexName));
ComputeSplitsResponse computeSplitsResponse = client.computeSplits(computeSplitsRequest);
final byte[] sessionId = computeSplitsResponse.getSessionId();
final int maxParallel = computeSplitsResponse.getSplitsSize();
// Create an AtomicLong object if you need to obtain the row count for your business.
AtomicLong rowCount = new AtomicLong(0);
/*
* If you want to perform multithreading by using a function, you can build an internal class to inherit the threads.
* You can also build an external class to organize the code.
*/
final class ThreadForScanQuery extends Thread {
private final int currentParallelId;
private ThreadForScanQuery(int currentParallelId) {
this.currentParallelId = currentParallelId;
this.setName("ThreadForScanQuery:" + maxParallel + "-" + currentParallelId); // Specify the thread name.
}
@Override
public void run() {
System.out.println("start thread:" + this.getName());
try {
// Execute the processing logic.
{
ParallelScanRequest parallelScanRequest = ParallelScanRequest.newBuilder()
.tableName(tableName)
.indexName(indexName)
.scanQuery(ScanQuery.newBuilder()
.query(QueryBuilders.range("col_long").lessThan(10_0000)) // Specify the data to query.
.limit(2000)
.currentParallelId(currentParallelId)
.maxParallel(maxParallel)
.build())
.addColumnsToGet("col_long", "col_keyword", "col_bool") // Specify the fields to return from the search index. To return all fields from the search index, set returnAllColumnsFromIndex to true.
//.returnAllColumnsFromIndex(true)
.sessionId(sessionId)
.build();
// Use an iterator to obtain all the data.
RowIterator ltr = client.createParallelScanIterator(parallelScanRequest);
long count = 0;
while (ltr.hasNext()) {
Row row = ltr.next();
// Add a custom processing logic. The following sample code shows how to add a custom processing logic to count the number of rows:
count++;
}
rowCount.addAndGet(count);
System.out.println("thread[" + this.getName() + "] finished. this thread get rows:" + count);
}
} catch (Exception ex) {
// If exceptions occur, you can retry the processing logic.
} finally {
semaphore.release();
}
}
}
// Simultaneously execute threads. Valid values of currentParallelId: [0, Value of maxParallel).
List<ThreadForScanQuery> threadList = new ArrayList<ThreadForScanQuery>();
for (int currentParallelId = 0; currentParallelId < maxParallel; currentParallelId++) {
ThreadForScanQuery thread = new ThreadForScanQuery(currentParallelId);
threadList.add(thread);
}
// Simultaneously initiate the threads.
for (ThreadForScanQuery thread : threadList) {
// Specify a value for semaphore to limit the number of threads that can be initiated at the same time to prevent bottlenecks on the client.
semaphore.acquire();
thread.start();
}
// The main thread is blocked until all threads are complete.
for (ThreadForScanQuery thread : threadList) {
thread.join();
}
System.out.println("all thread finished! total rows:" + rowCount.get());
}
}