Cassandraを使用してページネーションを実現するにはどうすればよいのでしょうか。
ブログがあるとしましょう。ブログには、1ページあたり最大10件の投稿がリストされています。次の投稿にアクセスするには、ユーザーはページネーションメニューをクリックして、ページ2(投稿11-20)、ページ3(投稿21-30)などにアクセスする必要があります。
MySQLでSQLを使用すると、次のことができます。
SELECT * FROM posts LIMIT 20,10;
LIMITの最初のパラメーターは結果セットの先頭からオフセットされ、2番目の引数はフェッチする行の量です。上記の例では、行20から10行が返されます。
CQLで同じ効果を得るにはどうすればよいですか?
Googleでいくつかの解決策を見つけましたが、それらはすべて「前のクエリの最後の結果」が必要です。別の10件の結果セットにページ分割する「次へ」ボタンがある場合は機能しますが、ページ1からページ5にジャンプする場合はどうなりますか?
CQLでトークン関数を使用してみてください: https://docs.datastax.com/en/cql/3.1/cql/cql_reference/select_r.html#reference_ds_d35_v2q_xj__paging-through-unordered-results
別の提案として、DSEを使用している場合、solrはディープページングをサポートしています。 https://cwiki.Apache.org/confluence/display/solr/Pagination+of+Results
Cassandra 2.0+。を使用している場合、トークンを使用する必要はありません。
Cassandra 2.0には自動ページングがあります。トークン関数を使用してページングを作成する代わりに、組み込み機能になりました。
開発者は、サイズがメモリよりも大きいことを気にすることなく、結果セット全体を反復処理できます。クライアントコードが結果を反復処理するときに、いくつかの余分な行をフェッチできますが、古い行は削除されます。
これをJavaで見ると、SELECTステートメントがすべての行を返し、取得された行数が100に設定されていることに注意してください。
ここでは簡単な文を示しましたが、準備された文で同じコードを記述し、バインドされた文と組み合わせることができます。必要でない場合は、自動ページングを無効にすることができます。また、さまざまなフェッチサイズ設定をテストすることも重要です。これは、記憶を十分に小さくしたいが、データベースへのラウンドトリップが多すぎるほど小さくないようにしたいためです。 this ブログ投稿で、ページングがサーバー側でどのように機能するかを確認してください。
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);
}
手動ページング
ドライバーは、最後のページがフェッチされたときに結果セットのどこにいたかを表すPagingStateオブジェクトを公開します。
ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
このオブジェクトは、文字列またはバイト配列にシリアル化できます。
String string = pagingState.toString();
byte[] bytes = pagingState.toBytes();
このシリアル化された形式は、後で再利用するために、何らかの形式の永続ストレージに保存できます。その値が後で取得されると、その値をデシリアライズしてステートメントに再注入できます。
PagingState pagingState = PagingState.fromString(string);
Statement st = new SimpleStatement("your query");
st.setPagingState(pagingState);
ResultSet rs = session.execute(st);
ページング状態は、まったく同じステートメント(同じクエリ文字列、同じパラメーター)でのみ再利用できることに注意してください。また、これは不透明な値であり、収集、保存、再利用のみを目的としています。その内容を変更しようとしたり、別のステートメントで再利用しようとすると、ドライバーはエラーを発生させます。
このドキュメント「ページング状態トークンを使用して次の結果を取得する」を読んだ場合、
https://datastax.github.io/php-driver/features/result_paging/
「ページング状態トークン」を使用して、アプリケーションレベルでページ分割することができます。 PHPロジックは次のようになります。
<?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']);
}
}
?>
countはCQLで利用可能ですが、これまでのところoffset部分の良い解決策を見ていません...
だから...私が考えていた1つの解決策は、バックグラウンドプロセスを使用してページのセットを作成することでした。
あるテーブルでは、ページ1、2、... 10への参照のセットとしてブログページAを作成します。次に、ページ11〜20などを指すブログページBの別のエントリを作成します。
つまり、行キーをページ番号に設定して独自のインデックスを作成します。ページごとに10、20、または30の参照を表示するようにユーザーに選択できるようにするため、ある程度柔軟に設定できます。たとえば、30に設定すると、セット1、2、および3をページAとして、セット4、5、6をページBとして表示します。
そして、すべてを処理するバックエンドプロセスがある場合は、新しいページが追加され、古いページがブログから削除されるときにリストを更新できます。このプロセスは非常に高速(1,000,000行の場合でも1分など)で、リストに表示するページをほとんど瞬時に見つけることができます。 (明らかに、数千のユーザーがそれぞれ数百のページを投稿するようにする場合、その数は急速に増加する可能性があります。)
複雑になるのは、複雑なWHERE句を提供する場合です。デフォルトでは、ブログには最新のものから古いものへのすべての投稿のリストが表示されます。タグCassandraで投稿のリストを提供することもできます。順序を逆にするなどの場合があります。インデックスを作成するための高度な方法がない限り、これは難しくなります。私の最後には、Cに似た言語があり、(a)値を選択し、(b)ソートするために、行の値を覗き込んで突きます。言い換えれば、私の終わりには、SQLにあるものと同じくらい複雑なWHERE句をすでに持つことができます。ただし、リストをページに分割していません。次のステップは...
ノードjs(koa js、marko js)でのcassandra-nodeドライバーの使用:ページネーションの問題
スキップ機能がないため、回避する必要があります。以下は、だれでもアイデアを得ることができる場合のノードアプリの手動ページングの実装です。
ここで説明する2つのソリューションがありますが、以下のソリューション1のコードのみを提供しました。
解決策1:next
およびprevious
レコードのページ状態を維持する(スタックまたは最適なデータ構造を維持する)
解決策2:制限付きですべてのレコードをループし、すべての可能なページ状態を変数に保存し、pageStatesに関連するページを生成します
このコメント付きコードをモデルで使用すると、ページのすべての状態を取得できます
//for the next flow
//if (result.nextPage) {
// Retrieve the following pages:
// the same row handler from above will be used
// result.nextPage();
//}
ルーター関数
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;
};
モデル関数
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;
};
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>
ノードjsとcassandra db、このソリューションは確実に改善できます。ソリューション1はページングのアイデアから始めるためのサンプルコードを使用しています。