LoginSignup
7
2

offset vs cursor ページネーション

Last updated at Posted at 2024-04-24

はじめに

多くのダイナミックなデータを集約して視覚化するサービスに携わっており、検索機能を作成することで興味深い選択問題を出てました。

新しいデータが大量に追加されると、同じ場所(ページ)のデータをリクエストしても異なる結果が返ってくることが判明した。データの分析や比較がより複雑になっちゃう。

通常、誰もがオフセット・ページネーションに慣れているが、常に変化するデータ(挿入、削除)に対してより効果的な方法もある。カーソルのページネーションを使った方がいいかもしれない。両方について説明しよう。

オフセットページネーション:

この方法では、結果セットの先頭からスキップするアイテムの数(オフセット)と、各ページで返すアイテムの数を指定します。たとえば、100個のアイテムの結果セットがある場合、1ページ目を取得するためにはオフセットを0、サイズを10に設定します。次に、2ページ目を取得するためにはオフセットを10、サイズを10に設定し、以降も同様です。オフセットページネーションはシンプルですが、大きなオフセット値では効率が低下し、動的なデータセットでは問題が生じることがあります。

カーソルページネーション:

この方法では、一意の識別子(カーソル)を使用して次のページの結果を取得します。通常、カーソルは結果セット内の特定のアイテムを指します。次のページをリクエストする際には、前のページで取得した最後のアイテムに対応するカーソルを指定します。この方法は大きなデータセットに対して効率的であり、オフセットを使用しないため、アイテムの挿入や削除がページネーションのパフォーマンスに影響を与えません。

可視化

説明を理解するのが難しいかもしれないので、簡単な視覚化を作成しました。

Screen Recording 2024-04-24 at 17.06.36.gif

オフセット(左側)の場合、データが追加されるたびに(新しいリクエストが行われるたびに)ドキュメントのインデックスが変化することがわかります。
カーソルを使うことで、変化するデータセットをよりスムーズにナビゲートできる。

可視化を作成するために使用したコード
<!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>

まとめて

要約すると、両方の方法はページネーションを実現しますが、カーソルページネーションは大きなデータセットや動的なデータセットに対して効率的でスケーラブルなため、より好まれます。一方、オフセットページネーションはシンプルですが、大きくて複雑データセットでは効率が悪くなる場合があります。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2