作成しているゲームのハイスコアテーブルを設計する方法についての情報を探しています。
私が使用しているデータベースは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行を許可することです。これが現実的かどうかはわかりません。
私の最初の試みでは、テーブルは最も単純なフォームにあります(alias
とscore
のキーに注意してください)。
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)
私の問題で最も興味深い列はalias
とscore
です(私が知らない影響がある可能性があるため、すべてを偶然に含めました)。
具体的には、2つのクエリを実行する必要があります。 score
とOFFSET
および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)を説明すると、これらのクエリは次のようになります(それぞれ)
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
そして
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のエントリを持つテーブルから)。
テーブルのデザインが最適かどうかわかりません。何らかの形でパフォーマンスに役立つ場合は、変更できます。パフォーマンスを向上させるために、クエリを別の方法で記述することもできると思います。どうだか分かりません。どんなポインタでも大歓迎です!
実用的なパフォーマンスを得るために、答えの絶対的な正確さについて妥協する用意があると想定すると、次のことができます。
score
にインデックスを作成します(降順と仮定しますが、どちらの方法も実行可能です)。質問でscore
のインデックスに言及しているので、これらの仮定で問題ないと思います。
次に、ユーザーのスコアのグローバルランキングは次のようになります。
select
S.*
, count (R.id from highscores R
where R.score > S.score) as rank
from highscores S
where S.alias = 'somealias'
相関サブクエリを使用することはスピードを盲目的にする方法ではありませんが、これの利点は、すべてをランク付けする必要がなく、ユーザーの特定のスコアに関連するインデックス内のエントリを数える必要があることです。もちろん、クエリオプティマイザだけが確実に知っています。