ソートされたリストを保存するためのデータベースを設計する方法は?


42

データベース内にソートされたリストを保存したいと思っています。次の操作を効率的に実行したい。

  1. Insert(x)-レコードxをテーブルに挿入します
  2. Delete(x)-テーブルからレコードxを削除します
  3. Before(x、n)-ソートされたリストでレコードxの前にある 'n'レコードを返します。
  4. After(x、n)-ソートされたリストのレコードxに続く 'n'レコードを返します。
  5. First(n)-ソートされたリストから最初の 'n'レコードを返します。
  6. Last(n)-ソートされたリストから最後の 'n'レコードを返します。
  7. Compare(x、y)-テーブルの2つのレコードxとyが与えられ、x> yかどうかを見つけます。

私が考えることができる簡単な方法は、ある種の「ランク」属性をテーブルに保存し、その属性でソートすることでクエリすることです。ただし、この方法では、ランク付きのレコードの挿入/変更はコストのかかる操作になります。より良い方法はありますか?

具体的には、AmazonのSimpleDBを使用してテーブルを実装することを検討しています。ただし、リレーショナルデータベースの一般的な回答も役立つはずです。

負荷プロファイルの更新:

これはWebアプリケーション用に計画しているので、アプリを使用するユーザーの数に依存します。

10万人のアクティブユーザーがいる場合(スーパーオプティミズム:P)、1日あたりの私のおおよその見積もりは

50万の選択、10万の挿入と削除、50万の更新

テーブルは合計で500kまで成長すると予想されます。

更新、挿入、および比較の操作を最適化しようとしています。アイテムのランクは常に変化するため、テーブルを更新し続ける必要があります。


予想される負荷プロファイルについて少し詳しく説明します。1日あたりの選択/挿入/更新の数 どの操作を最適化したいですか?1日あたりのテーブルのサイズはどれくらいになると予想されますか?
ニックチャマス

これはプレイヤーランキングボード用ですか?とにかく、予想される負荷プロファイルに基づいたフィードバックで、以下の回答を更新しました。
ニックチャマス

いいえ、プレーヤーのランキングボードではありません。
チッティ

最終的にどのアプローチを使用しましたか?
ニックチャマス

ここで何が尋ねられているのか、あなたがする必要があることの洗濯リストから何をする必要がないのかさえ分かりません。
エヴァンキャロル

回答:


22

ランクは完全に任意ではなく、他のいくつかのプロパティ(例えば、名前、プレーヤーのスコアなど)からの代わりに誘導された場合、その後に良い見てジョエルの答え

それは場合、あなたのデータの任意のプロパティ、そのレコードのあなたのテーブルの列として格納する必要があります。AmazonのSimpleDBが典型的なRDBMSに似ていると仮定すると、この列にインデックスを付け、適切なインデックス戦略で上記のすべてのクエリをすばやく満たすことができます。これはRDBMSの正常な動作です。

挿入と更新のアクティビティが多いだけでなく、読み取りアクティビティが比較的高いことを想定しているため、次のことをお勧めします。

  • クエリの大部分がランクに反する場合は特に、ランクでテーブルをクラスター化します。そうでない場合、またはクラスタリングキーの選択がSimpleDBで使用できない場合は、ランクを先頭列としてインデックスを作成します。これにより、クエリ3〜6が満たされます。
  • 最初にレコードのインデックス、次にランク(または、SQL Serverの世界では、単にレコードと- INCLUDEランク、または単にランクでクラスター化した場合はレコードのみ)は、クエリ7を満たします。
  • 操作1と2は、データを適切に間隔を空ける(つまりFILLFACTOR、SQL Serverで設定する)ことで最適化できます。これは、ランクでクラスター化する場合に特に重要です。
  • ランクを挿入または更新するときは、ランクの挿入または更新に対応するために既存のレコードを再ランク付けする必要が生じる可能性を最小限に抑えるために、ランク番号間のギャップをできるだけ維持します。たとえば、1000のステップでレコードをランク​​付けする場合、最小限のチャンスでその半分ほどの変更と挿入に十分な余地を残し、それらの変更に直接関係しないレコードを再ランク付けする必要があります。
  • 毎晩、すべてのレコードを再ランク付けして、それらの間のランクギャップをリセットします。
  • 大量の再ランク付けの頻度とランクギャップサイズを調整して、既存のレコードの数に対する挿入または更新の予想数に対応できます。したがって、レコード数が10万で、挿入と更新がその10%であると予想される場合は、10Kの新しいランクに十分なスペースを残して、毎晩再ランク付けしてください。
  • 50万件のレコードを再ランク付けするのはコストのかかる操作ですが、そのようなデータベースでは、1日1回または1週間に1回は営業時間外に行う必要があります。ランクギャップを維持するためのこの時間外の大量再ランク付けにより、通常およびピーク時間中にランクの更新または挿入ごとに多くのレコードを再ランク付けする必要がなくなります。

100K +サイズのテーブルで100K +の読み取りが予想される場合、リンクリストアプローチの使用はお勧めしません。それらのサイズにうまく対応できません。


ランクは変更可能です。ランクが絶えず変化し、新しいレコードが絶えず挿入されることを期待しています。ランク付きの新しい要素を挿入した場合、ソート順で新しいレコードの下にあるすべてのレコードのランクを変更する必要がある場合が心配です。データベースに数千のレコードがある場合、それはコストのかかる操作ではありませんか?
-chitti

@chitti-ああ、それは心配です。ランキング(たとえば、0、1000、2000、3000など)を空けて、ランクのギャップがいっぱいになると、すべてのレコードを定期的に再ランク付けできます。ただし、数万件以上のレコードが予想される場合、これはスケーリングされません。
ニックチャマス

1
@chitti-これは実際、ちょっとおかしいです。これは、データベースエンジンがデータのインデックス作成時に処理する問題です。データが追加または変更されると、データベースエンジンはデータを並べ替え、並べ替えるからです。見上げるFILLFACTORと、基本的には、インデックス内のレコード用に余分なスペースを作成することを意味します。これは、先ほど説明したランクギャップがランクの変更と挿入のためのスペースを作成するためです。
ニックチャマス

2
更新された回答をありがとう。「ランク」は、私のデータの任意のプロパティです。カスタムインデックス列が必要なものであるとほぼ確信しています。同様の質問でこのSOリンクをチェックしてください。一番上の答えは、そのようなランク列の処理方法に関する推奨事項を提供します。
チッティ

@chitti- そのSO質問に対する受け入れられた答えは素晴らしい。ここでは、ランクの割り当てと変更の柔軟性を大幅に拡張するために、整数の代わりに小数を使用するという追加の提案とともに、ここで詳述した同じアプローチを提案します。素晴らしい発見。
ニックチャマス

13

私は通常、あなたが説明する「ランク」方式を使用します。アイテムを並べ替える必要があるときに行を更新することに煩わされるのではなく、リスト内のすべてのレコードを削除し、新しいアイテムを適切な順序で再挿入することで、しばしば逃げることができました。このメソッドは、検索用に明らかに最適化されています。

別のアプローチは、テーブルの「先行」再帰的外部キー列を使用して、レコードをリンクリストとしてモデル化することです。

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

リストを簡単に取得し、ほとんどオーバーヘッドなしで項目を追加および削除できますが、適切な順序でレコードを取得するのは難しいでしょう。おそらく、多くのエイリアステーブル結合を使用して、1つのクエリでそれを行う巧妙な方法があるでしょう。

ツリースタイルの関係(カテゴリ、フォルダー、セット、およびサブセット)をモデル化するときに、後者のアプローチをよく使用します。私は通常、アプリケーションで完全なツリーを再構築するための何らかの再帰関数を持っています。


2
リンクリストモデルはすっきりしています。SQL Serverでこのような階層を順番に取得するには、再帰CTEを使用します。
ニックチャマス

ただし、そのような階層を構築するのは、背の高いテーブルではかなりコストがかかります。利点は、ランクの変更/挿入などを簡単に行えることです。チッティの予想される負荷プロファイルに応じて、これは実際には最良のアプローチかもしれません。
ニックチャマス

リンクリストオプションは、比較を除くすべての操作に最適なアイデアのように見えます。比較する2つの要素間のパスをトレースする必要なく、Compareをどのように実装するか考えていますか?
-chitti

Compare()の意味を誤解しない限り、Compare()が簡単だと思うアイテムのIDがある場合。「x> yを見つける」と言ったとき、「xがyに先行するかどうかを調べる」という意味でしたか?リストをたどるカスタムインデックスまたはストアドプロシージャ(または@Nickが言及している興味深いCTE機能)がなければ、その簡単さはわかりません。
-bpanulla

5
このタイプのソリューションは、グラフデータモデル(en.wikipedia.org/wiki/Graph_theory)にも近似しています。グラフノードとエッジを格納するために最適化されたストレージシステムは、RDBMSよりも優れたソリューションである可能性があります。Neo4Jのようなトリプルおよびクアッドストアとグラフデータベースは、この点で非常に優れています。
-bpanulla

6

私がすべきことは、ランクを計算するために使用されるプロパティ保存し、それらの上にインデックスを構築することだと思います。ランク付けされた順序でデータベースにデータを物理的に保存するように強制したり、手動で管理されたリンクリストを使用したりするのではなく、データベースエンジンに設計どおりの動作をさせてみませんか?


2
「ランクの計算に使用されるプロパティ」が任意である場合はどうなりますか?例:ユーザーの任意のアクションに基づいて並べ替えられるショッピングカートエントリのセット。
-chitti

ランクがarbitrary意的だと言うとき、どういう意味ですか?ランクの計算に使用するアルゴリズムが必要です。たとえば、「ショッピングカートのエントリに基づいて」-どのように基づいていますか?ランク計算のドライバーであるデータベースに何かが保存されている必要があります。いくつかのことを組み合わせることもできますが、これらのことを何らかの方法で顧客テーブルまたは顧客に関連するテーブルに保存する必要があります。データ内にある場合は、それを計算する関数を作成できます。計算できる場合は、保存してインデックスを作成できます。
ジョエルブラウン

ショッピングカート内のアイテムの順序を維持する必要があり、その順序は、ユーザーがWeb UIを使用して「任意に」変更できるとしましょう。このようなアイテムのリストをデータベースにどのように保存し、ソート順をどのように維持しますか?
chitti

私があなたを正しく理解していれば、ショッピングカート内のアイテムの順序を「任意に変更」することは、ユーザーがリスト内でアイテムを上下にドラッグし、好きな場所にドロップできることを意味します。私はそれが少し不自然だと思う。なぜユーザーはそれをするのですか?彼らがそれを行うことができた場合、彼らはそれをたくさんしますか?カート内のアイテムの単純なシーケンスを使用することは、実際にパフォーマンスの問題の多くですか?1からカート内のアイテム数までのシーケンス番号+ FKから注文までが、必要なインデックスを提供するように思えます。ドラッグしてアイテムを更新するだけです。
ジョエルブラウン

3
ショッピングカートは、「ランク」がarbitrary意的である場合があることを示すために示した例にすぎません。それは素晴らしい例ではなかったかもしれません。netflix dvdキューは、より良い例です。議論のために、ユーザーが任意に並べ替えることができる10万個のアイテムを持つnetflixキューを想像してください。彼は1分ごとにそれを行います。この架空のアプリケーションで、順序付けられた映画のリストを保存するデータベースをどのように設計しますか?
チッティ

1

これらは、simpleDBのような非RDBMSの制限です。必要な機能をsimpleDBのDB側に実装することはできません。プログラミング側/アプリケーションから実装する必要があります。

のようなRDBMSの場合SQL server、必要な機能はクラスター化インデックスの基本です。

  • Insert(x)-レコードxをテーブルに挿入>単純挿入。
  • Delete(x)-テーブルからレコードxを削除>単純な削除。
  • Before(x、n)-ソートされたリストでレコードxの前にある 'n'レコードを返します。>上位n個の結果を選択します。ここで、xは値より小さく、order by句です。

  • After(x、n)-ソートされたリストのレコードxに続く 'n'レコードを返します。>上位n個の結果を選択します。ここで、xはvalueより大きく、order by句です。

  • First(n)-ソートされたリストから最初の 'n'レコードを返します。>上位n個の結果を選択します。

  • Last(n)-ソートされたリストから最後の「n」レコードを返します。> descで注文した後、上位n個の結果を選択します。

  • Compare(x、y)-テーブルの2つのレコードxとyが与えられ、x> yかどうかを調べます。> TSQL IFステートメント。

SimpleDBは、自動インデックス、ソート、および基本的なクエリ言語を提供します。RDBMSを選択しても、私の問題は残ります。問題は、データベース内のデータのランキングが任意に変更され、インデックスを作成できる単一のプロパティ(カスタムランク列を使用しない限り)としてキャプチャできないためです。
-chitti

0

以下は、挿入ごとにPostgresテーブルを再ランク付けするために使用したものです。

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

私のユースケースでは、パフォーマンスは問題ではありませんが、決して壊れたり、奇妙に動作したりしないという自信が重要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.