Resultados de paginación en Cassandra(CQL)
cql3 datastax-enterprise (6)
Uso del controlador cassandra-node para el nodo js (koa js, marko js): Problema de paginación
Debido a la ausencia de funcionalidad de omisión, tenemos que solucionar el problema. A continuación se muestra la implementación de la paginación manual para la aplicación de nodo en caso de que alguien pueda tener una idea.
- código para la lista de usuarios simples
- navegar entre los estados de la página siguiente y anterior
- fácil de replicar
Hay dos soluciones que voy a declarar aquí, pero a continuación le doy el código para la solución 1 a continuación:
Solución 1: mantener los estados de página para previous
registros next
y previous
(mantener la pila o la estructura de datos que mejor se ajuste)
Solución 2: recorra todos los registros con límite y guarde todos los estados de página posibles en variable y genere páginas en relación con sus estados de página.
Usando este código comentado en el modelo, podemos obtener todos los estados para las páginas.
//for the next flow
//if (result.nextPage) {
// Retrieve the following pages:
// the same row handler from above will be used
// result.nextPage();
//}
Funciones de enrutador
var userModel = require(''/models/users'');
public.get(''/users'', users);
public.post(''/users'', filterUsers);
var users = function* () {//get request
var data = {};
var pageState = { "next": "", "previous": "" };
try {
var userCount = yield userModel.Count();//count all users with basic count query
var currentPage = 1;
var pager = yield generatePaging(currentPage, userCount, pagingMaxLimit);
var userList = yield userModel.List(pager);
data.pageNumber = currentPage;
data.TotalPages = pager.TotalPages;
console.log(''--------------what now--------------'');
data.pageState_next = userList.pageStates.next;
data.pageState_previous = userList.pageStates.previous;
console.log("next ", data.pageState_next);
console.log("previous ", data.pageState_previous);
data.previousStates = null;
data.isPrevious = false;
if ((userCount / pagingMaxLimit) > 1) {
data.isNext = true;
}
data.userList = userList;
data.totalRecords = userCount;
console.log(''--------------------userList--------------------'', data.userList);
//pass to html template
}
catch (e) {
console.log("err ", e);
log.info("userList error : ", e);
}
this.body = this.stream(''./views/userList.marko'', data);
this.type = ''text/html'';
};
//post filter and get list
var filterUsers = function* () {
console.log("<------------------Form Post Started----------------->");
var data = {};
var totalCount;
data.isPrevious = true;
data.isNext = true;
var form = this.request.body;
console.log("----------------formdata--------------------", form);
var currentPage = parseInt(form.hdpagenumber);//page number hidden in html
console.log("-------before current page------", currentPage);
var pageState = null;
try {
var statesArray = [];
if (form.hdallpageStates && form.hdallpageStates !== '''') {
statesArray = form.hdallpageStates.split('','');
}
console.log(statesArray);
//develop stack to track paging states
if (form.hdpagestateRequest === ''next'') {
console.log(''--------------------------next---------------------'');
currentPage = currentPage + 1;
statesArray.push(form.hdpageState_next);
pageState = form.hdpageState_next;
}
else if (form.hdpagestateRequest === ''previous'') {
console.log(''--------------------------pre---------------------'');
currentPage = currentPage - 1;
var p_st = statesArray.length - 2;//second last index
console.log(''this index of array to be removed '', p_st);
pageState = statesArray[p_st];
statesArray.splice(p_st, 1);
//pageState = statesArray.pop();
}
else if (form.hdispaging === ''false'') {
currentPage = 1;
pageState = null;
statesArray = [];
}
data.previousStates = statesArray;
console.log("paging true");
totalCount = yield userModel.Count();
var pager = yield generatePaging(form.hdpagenumber, totalCount, pagingMaxLimit);
data.pageNumber = currentPage;
data.TotalPages = pager.TotalPages;
//filter function - not yet constructed
var searchUsers = yield userModel.searchList(pager, pageState);
data.usersList = searchUsers;
if (searchUsers.pageStates) {
data.pageStates = searchUsers.pageStates;
data.next = searchUsers.nextPage;
data.pageState_next = searchUsers.pageStates.next;
data.pageState_previous = searchUsers.pageStates.previous;
//show previous and next buttons accordingly
if (currentPage == 1 && pager.TotalPages > 1) {
data.isPrevious = false;
data.isNext = true;
}
else if (currentPage == 1 && pager.TotalPages <= 1) {
data.isPrevious = false;
data.isNext = false;
}
else if (currentPage >= pager.TotalPages) {
data.isPrevious = true;
data.isNext = false;
}
else {
data.isPrevious = true;
data.isNext = true;
}
}
else {
data.isPrevious = false;
data.isNext = false;
}
console.log("response ", searchUsers);
data.totalRecords = totalCount;
//pass to html template
}
catch (e) {
console.log("err ", e);
log.info("user list error : ", e);
}
console.log("<------------------Form Post Ended----------------->");
this.body = this.stream(''./views/userList.marko'', data);
this.type = ''text/html'';
};
//Paging function
var generatePaging = function* (currentpage, count, pageSizeTemp) {
var paging = new Object();
var pagesize = pageSizeTemp;
var totalPages = 0;
var pageNo = currentpage == null ? null : currentpage;
var skip = pageNo == null ? 0 : parseInt(pageNo - 1) * pagesize;
var pageNumber = pageNo != null ? pageNo : 1;
totalPages = pagesize == null ? 0 : Math.ceil(count / pagesize);
paging.skip = skip;
paging.limit = pagesize;
paging.pageNumber = pageNumber;
paging.TotalPages = totalPages;
return paging;
};
Funciones del modelo
var clientdb = require(''../utils/cassandradb'')();
var Users = function (options) {
//this.init();
_.assign(this, options);
};
Users.List = function* (limit) {//first time
var myresult; var res = [];
res.pageStates = { "next": "", "previous": "" };
const options = { prepare: true, fetchSize: limit };
console.log(''----------did i appeared first?-----------'');
yield new Promise(function (resolve, reject) {
clientdb.eachRow(''SELECT * FROM users_lookup_history'', [], options, function (n, row) {
console.log(''----paging----rows'');
res.push(row);
}, function (err, result) {
if (err) {
console.log("error ", err);
}
else {
res.pageStates.next = result.pageState;
res.nextPage = result.nextPage;//next page function
}
resolve(result);
});
}).catch(function (e) {
console.log("error ", e);
}); //promise ends
console.log(''page state '', res.pageStates);
return res;
};
Users.searchList = function* (pager, pageState) {//paging filtering
console.log("|------------Query Started-------------|");
console.log("pageState if any ", pageState);
var res = [], myresult;
res.pageStates = { "next": "" };
var query = "SELECT * FROM users_lookup_history ";
var params = [];
console.log(''current pageState '', pageState);
const options = { pageState: pageState, prepare: true, fetchSize: pager.limit };
console.log(''----------------did i appeared first?------------------'');
yield new Promise(function (resolve, reject) {
clientdb.eachRow(query, [], options, function (n, row) {
console.log(''----Users paging----rows'');
res.push(row);
}, function (err, result) {
if (err) {
console.log("error ", err);
}
else {
res.pageStates.next = result.pageState;
res.nextPage = result.nextPage;
}
//for the next flow
//if (result.nextPage) {
// Retrieve the following pages:
// the same row handler from above will be used
// result.nextPage();
//}
resolve(result);
});
}).catch(function (e) {
console.log("error ", e);
info.log(''something'');
}); //promise ends
console.log(''page state '', pageState);
console.log("|------------Query Ended-------------|");
return res;
};
Lado html
<div class="box-footer clearfix">
<ul class="pagination pagination-sm no-margin pull-left">
<if test="data.isPrevious == true">
<li><a class=''submitform_previous'' href="">Previous</a></li>
</if>
<if test="data.isNext == true">
<li><a class="submitform_next" href="">Next</a></li>
</if>
</ul>
<ul class="pagination pagination-sm no-margin pull-right">
<li>Total Records : $data.totalRecords</li>
<li> | Total Pages : $data.TotalPages</li>
<li> | Current Page : $data.pageNumber</li>
</ul>
</div>
No tengo mucha experiencia con node js y cassandra db, esta solución seguramente se puede mejorar. Solución 1 es código de ejemplo de trabajo para comenzar con la idea de paginación. Aclamaciones
Me pregunto cómo puedo lograr la paginación usando Cassandra.
Digamos que tengo un blog. El blog enumera un máximo de 10 mensajes por página. Para acceder a las siguientes publicaciones, un usuario debe hacer clic en el menú de paginación para acceder a la página 2 (publicaciones 11-20), página 3 (publicaciones 21-30), etc.
Usando SQL bajo MySQL, podría hacer lo siguiente:
SELECT * FROM posts LIMIT 20,10;
El primer parámetro de LIMIT se desplaza desde el principio del conjunto de resultados y el segundo argumento es la cantidad de filas a recuperar. El ejemplo anterior devuelve 10 filas a partir de la fila 20.
¿Cómo puedo lograr el mismo efecto en CQL?
He encontrado algunas soluciones en Google, pero todas requieren tener "el último resultado de la consulta anterior". Funciona para tener el botón "siguiente" para paginar a otro conjunto de 10 resultados, pero ¿qué sucede si deseo pasar de la página 1 a la página 5?
Aunque el recuento está disponible en CQL, hasta ahora no he visto una buena solución para la parte de compensación ...
Entonces ... una solución que he estado contemplando fue crear conjuntos de páginas usando un proceso en segundo plano.
En alguna tabla, crearía la página A del blog como un conjunto de referencias a la página 1, 2, ... 10. Luego, otra entrada para la página B del blog que apunta a las páginas 11 a 20, etc.
En otras palabras, crearía mi propio índice con una clave de fila establecida para el número de página. Todavía puede hacerlo un poco flexible, ya que puede ofrecerle al usuario que elija ver 10, 20 o 30 referencias por página. Por ejemplo, cuando se establece en 30, se muestran los conjuntos 1, 2 y 3 como página A, los conjuntos 4, 5, 6 como página B, etc.)
Y si tiene un proceso de back-end para manejar todo eso, puede actualizar sus listas a medida que se agregan nuevas páginas y se eliminan páginas antiguas del blog. El proceso debería ser realmente rápido (como 1 minuto para 1,000,000 filas, aunque sea lento ...) y luego puede encontrar las páginas para mostrar en su lista casi instantáneamente. (Obviamente, si va a tener miles de usuarios que publican cientos de páginas ... ese número puede crecer rápidamente).
Donde se vuelve más complicado es si desea ofrecer una cláusula WHERE compleja. Por defecto, un blog te muestra una lista de todas las publicaciones desde la más reciente a la más antigua. También puedes ofrecer listas de mensajes con etiqueta Cassandra . Tal vez quiera invertir el orden, etc. Eso lo hace difícil a menos que tenga alguna forma avanzada de crear su (s) índice (s). En mi extremo tengo un lenguaje tipo C que va y veo los valores en una fila para (a) seleccionarlos y, si se selecciona (b), ordenarlos. En otras palabras, en mi extremo ya puedo tener cláusulas WHERE tan complejas como las que tendría en SQL. Sin embargo, todavía no he dividido mis listas en páginas. El siguiente paso supongo ...
Intente usar la función de token en CQL: https://docs.datastax.com/en/cql/3.1/cql/cql_reference/select_r.html#reference_ds_d35_v2q_xj__paging-through-unordered-results
Otra sugerencia, si está utilizando DSE, solr admite la paginación profunda: https://cwiki.apache.org/confluence/display/solr/Pagination+of+Results
No es necesario utilizar tokens, si está utilizando Cassandra 2.0+.
Cassandra 2.0 tiene paginación automática. En lugar de usar la función de token para crear la paginación, ahora es una característica incorporada.
Ahora los desarrolladores pueden recorrer todo el conjunto de resultados, sin tener que preocuparse de que su tamaño sea mayor que la memoria. A medida que el código del cliente se repite en los resultados, se pueden recuperar algunas filas adicionales, mientras que las antiguas se eliminan.
Mirando esto en Java, tenga en cuenta que la instrucción SELECT devuelve todas las filas, y el número de filas recuperadas se establece en 100.
He mostrado una declaración simple aquí, pero el mismo código puede escribirse con una declaración preparada, junto con una declaración encuadernada. Es posible deshabilitar la paginación automática, si no se desea. También es importante probar varias configuraciones de tamaño de captura, ya que querrá mantener la memoria lo suficientemente pequeña, pero no tan pequeña como para que se realicen demasiados viajes de ida y vuelta a la base de datos. Echa un vistazo a this publicación del blog para ver cómo funciona la paginación del lado del servidor
Statement stmt = new SimpleStatement(
"SELECT * FROM raw_weather_data"
+ " WHERE wsid= ''725474:99999''"
+ " AND year = 2005 AND month = 6");
stmt.setFetchSize(24);
ResultSet rs = session.execute(stmt);
Iterator<Row> iter = rs.iterator();
while (!rs.isFullyFetched()) {
rs.fetchMoreResults();
Row row = iter.next();
System.out.println(row);
}
Si lee este documento "Use el token de estado de paginación para obtener el siguiente resultado",
https://datastax.github.io/php-driver/features/result_paging/
Podemos usar el "token de estado de paginación" para paginar a nivel de aplicación. Así que la lógica de PHP debería verse como,
<?php
$limit = 10;
$offset = 20;
$cluster = Cassandra::cluster()->withContactPoints(''127.0.0.1'')->build();
$session = $cluster->connect("simplex");
$statement = new Cassandra/SimpleStatement("SELECT * FROM paging_entries Limit ".($limit+$offset));
$result = $session->execute($statement, new Cassandra/ExecutionOptions(array(''page_size'' => $offset)));
// Now $result has all rows till "$offset" which we can skip and jump to next page to fetch "$limit" rows.
while ($result->pagingStateToken()) {
$result = $session->execute($statement, new Cassandra/ExecutionOptions($options = array(''page_size'' => $limit,''paging_state_token'' => $result->pagingStateToken())));
foreach ($result as $row) {
printf("key: ''%s'' value: %d/n", $row[''key''], $row[''value'']);
}
}
?>
Paginación manual
El controlador expone un objeto PagingState que representa dónde estábamos en el conjunto de resultados cuando se recuperó la última página:
ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
Este objeto puede ser serializado a una cadena o una matriz de bytes:
String string = pagingState.toString();
byte[] bytes = pagingState.toBytes();
Esta forma serializada se puede guardar en alguna forma de almacenamiento persistente para reutilizarla más tarde. Cuando ese valor se recupere más tarde, podemos deserializarlo y reinyectarlo en una declaración:
PagingState pagingState = PagingState.fromString(string);
Statement st = new SimpleStatement("your query");
st.setPagingState(pagingState);
ResultSet rs = session.execute(st);
Tenga en cuenta que el estado de paginación solo se puede reutilizar con la misma declaración exacta (la misma cadena de consulta, los mismos parámetros). Además, es un valor opaco que solo debe recopilarse, almacenarse y reutilizarse. Si intenta modificar su contenido o reutilizarlo con una declaración diferente, el controlador generará un error.