web-dev-qa-db-ja.com

ハイスコ​​ア/リーダーボードテーブルの設計

作成しているゲームのハイスコアテーブルを設計する方法についての情報を探しています。

私が使用しているデータベースはpostgresです。これは、私が変更できる、または変更したいことではありません(非常に正当な理由がない限り)。

                                                          version
-----------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.3.4 on x86_64-Apple-darwin13.1.0, compiled by Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn), 64-bit

私の目標は、最大10 000 000行を許可することです。これが現実的かどうかはわかりません。

私の最初の試みでは、テーブルは最も単純なフォームにあります(aliasscoreのキーに注意してください)。

 Column  |          Type          |                        Modifiers
---------+------------------------+---------------------------------------------------------
id       | integer                | not null default nextval('highscores_id_seq'::regclass)
traceid  | integer                |
alias    | character varying(256) |
score    | integer                |
rows     | integer                |
level    | integer                |
duration | integer                |
Indexes:
    "highscores_pkey" PRIMARY KEY, btree (id)
    "highscores_alias_idx" btree (alias)
    "highscores_score_idx" btree (score)

私の問題で最も興味深い列はaliasscoreです(私が知らない影響がある可能性があるため、すべてを偶然に含めました)。

クエリ

具体的には、2つのクエリを実行する必要があります。 scoreOFFSETおよびLIMITでソートされたハイスコアを選択するもの(クエリごとに最大1000行)。私はこれを書きました:

SELECT *, RANK() over(ORDER BY score DESC) AS rank FROM highscores OFFSET 0 LIMIT 100

これは最上位の結果(これは非常に一般的です)でうまく機能し、OFFSETを大きくするとパフォーマンスが低下します。理想的ではありませんが、許容できます(キャッシュが機能していると思います)。

2番目のクエリはより複雑です。 alias(たとえば、ユーザーの名前)を指定すると、そのユーザーが達成したハイスコアと、グローバルテーブル内の各ハイスコアの位置(scoreで並べられた)も表示されます。

私はこれを書きました:

SELECT * FROM
  (SELECT *, RANK() OVER (ORDER BY score DESC) AS rank FROM highscores) AS tbl
WHERE alias = 'somealias'

正しい結果が得られます(例:rankがテーブルのグローバル位置に設定されます)。しかし、それはひどく遅いです。 30秒ほどかかりますが、これは実際には許容できません。

(ANALYZE、BUFFERS)を説明すると、これらのクエリは次のようになります(それぞれ)

link do depesz

Limit  (cost=0.42..303.61 rows=100 width=35) (actual time=0.029..0.490 rows=100 loops=1)
  Buffers: shared hit=110
  ->  WindowAgg  (cost=0.42..606388.37 rows=200002 width=35) (actual time=0.028..0.469 rows=100 loops=1)
        Buffers: shared hit=110
        ->  Index Scan Backward using highscores_score_idx on highscores  (cost=0.42..603388.34 rows=200002 width=35) (actual time=0.015..0.264 rows=100 loops=1)
              Buffers: shared hit=110

そして

depeszへのリンク

Subquery Scan on tbl  (cost=67440.85..73440.91 rows=1 width=43) (actual time=960.290..960.290 rows=0 loops=1)
    Filter: ((tbl.alias)::text = 'somealias'::text)
    Rows Removed by Filter: 200002
    Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
    ->  WindowAgg  (cost=67440.85..70940.89 rows=200002 width=35) (actual time=560.130..920.911 rows=200002 loops=1)
          Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
          ->  Sort  (cost=67440.85..67940.86 rows=200002 width=35) (actual time=560.117..652.521 rows=200002 loops=1)
                Sort Key: highscores.score
                Sort Method: external merge  Disk: 8208kB
                Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
                ->  Seq Scan on highscores  (cost=0.00..38890.02 rows=200002 width=35) (actual time=111.099..215.351 rows=200002 loops=1)
                    Buffers: shared hit=3102 read=33788width=35)

(ランダムに生成された200 000のエントリを持つテーブルから)。

助けて

テーブルのデザインが最適かどうかわかりません。何らかの形でパフォーマンスに役立つ場合は、変更できます。パフォーマンスを向上させるために、クエリを別の方法で記述することもできると思います。どうだか分かりません。どんなポインタでも大歓迎です!

5

実用的なパフォーマンスを得るために、答えの絶対的な正確さについて妥協する用意があると想定すると、次のことができます。

  • scoreにインデックスを作成します(降順と仮定しますが、どちらの方法も実行可能です)。
  • このインデックスを定期的に再構築するメンテナンスジョブを作成します(ランクの結果を古くする意欲に依存する頻度)

質問でscoreのインデックスに言及しているので、これらの仮定で問題ないと思います。

次に、ユーザーのスコアのグローバルランキングは次のようになります。

select 
  S.*
, count (R.id from highscores R
         where R.score > S.score) as rank
from highscores S
where S.alias = 'somealias'

相関サブクエリを使用することはスピードを盲目的にする方法ではありませんが、これの利点は、すべてをランク付けする必要がなく、ユーザーの特定のスコアに関連するインデックス内のエントリを数える必要があることです。もちろん、クエリオプティマイザだけが確実に知っています。

2
Joel Brown