はじめに
多くのダイナミックなデータを集約して視覚化するサービスに携わっており、検索機能を作成することで興味深い選択問題を出てました。
新しいデータが大量に追加されると、同じ場所(ページ)のデータをリクエストしても異なる結果が返ってくることが判明した。データの分析や比較がより複雑になっちゃう。
通常、誰もがオフセット・ページネーションに慣れているが、常に変化するデータ(挿入、削除)に対してより効果的な方法もある。カーソルのページネーションを使った方がいいかもしれない。両方について説明しよう。
オフセットページネーション:
この方法では、結果セットの先頭からスキップするアイテムの数(オフセット)と、各ページで返すアイテムの数を指定します。たとえば、100個のアイテムの結果セットがある場合、1ページ目を取得するためにはオフセットを0、サイズを10に設定します。次に、2ページ目を取得するためにはオフセットを10、サイズを10に設定し、以降も同様です。オフセットページネーションはシンプルですが、大きなオフセット値では効率が低下し、動的なデータセットでは問題が生じることがあります。
カーソルページネーション:
この方法では、一意の識別子(カーソル)を使用して次のページの結果を取得します。通常、カーソルは結果セット内の特定のアイテムを指します。次のページをリクエストする際には、前のページで取得した最後のアイテムに対応するカーソルを指定します。この方法は大きなデータセットに対して効率的であり、オフセットを使用しないため、アイテムの挿入や削除がページネーションのパフォーマンスに影響を与えません。
可視化
説明を理解するのが難しいかもしれないので、簡単な視覚化を作成しました。
オフセット(左側)の場合、データが追加されるたびに(新しいリクエストが行われるたびに)ドキュメントのインデックスが変化することがわかります。
カーソルを使うことで、変化するデータセットをよりスムーズにナビゲートできる。
可視化を作成するために使用したコード
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ページネーション視覚化: Offset vs Cursor</title>
<style>
.document { width: 15px; height: 10px; background-color: blue; margin: 1px; float: left; color: white; text-align: center; font-size: 8px; }
.container { width: 220px; height: 660px; overflow: hidden; float: left; margin-right: 10px; }
</style>
</head>
<body>
<div class="container" id="offsetContainer"></div>
<div class="container" id="cursorContainer"></div>
<button onclick="next()">+1</button>
<button onclick="previous()">-1</button>
<script>
const documentsOffset = [];
const documentsCursor = [];
const offsetContainer = document.getElementById('offsetContainer');
const cursorContainer = document.getElementById('cursorContainer');
let intervalIdOffset = null;
let intervalIdCursor = null;
const cursorInitialIndices = [];
let counter = 0;
let page = 0;
const next = () => {
counter += 10;
page += 10;
};
const previous = () => {
counter -= 10;
page -= 10;
if (page < 0) {
page = 0;
}
if (counter < 0) {
counter = 0;
}
};
const createDocuments = () => {
for (let i = 0; i < 100; i++) {
addDocument(i + 1, true); // offset
addDocument(i + 1, false); // cursor
}
for (let i = 0; i < 10; i++) {
cursorInitialIndices.push(i);
}
};
const addDocument = (index, isOffset) => {
const doc = document.createElement('div');
doc.className = 'document';
doc.textContent = index;
if (isOffset) {
offsetContainer.insertBefore(doc, offsetContainer.firstChild);
documentsOffset.unshift(doc);
} else {
cursorContainer.insertBefore(doc, cursorContainer.firstChild);
documentsCursor.unshift(doc);
}
};
const startOffsetPagination = () => {
const step = 10;
intervalIdOffset = setInterval(() => {
clearHighlight(documentsOffset);
for (let i = 0; i < step && i < documentsOffset.length; i++) {
documentsOffset[i + page].style.backgroundColor = 'red';
}
}, 100);
};
const startCursorPagination = () => {
const step = 10;
intervalIdCursor = setInterval(() => {
clearHighlight(documentsCursor);
cursorInitialIndices.forEach(index => {
if (index + counter < documentsCursor.length) {
documentsCursor[index + counter].style.backgroundColor = 'green';
}
});
}, 100);
};
const clearHighlight = (docs) => {
docs.forEach(doc => {
doc.style.backgroundColor = 'blue';
});
};
createDocuments();
startOffsetPagination();
startCursorPagination();
// 新しいデータの追加を開始し、ライブ環境をシミュレートする
setInterval(() => {
counter += 1;
addDocument(documentsOffset.length + 1, true); // offset追加
addDocument(documentsCursor.length + 1, false); // cursor追加
}, 2000);
</script>
</body>
</html>
まとめて
要約すると、両方の方法はページネーションを実現しますが、カーソルページネーションは大きなデータセットや動的なデータセットに対して効率的でスケーラブルなため、より好まれます。一方、オフセットページネーションはシンプルですが、大きくて複雑データセットでは効率が悪くなる場合があります。