redisデータベースのキー値設計

6898 ワード

豊富なデータ構造はredisの設計を非常に面白くした.リレーショナル・データベースのように、DEVとDBAは深いコミュニケーションが必要ではなく、review各行のsql文もmemcachedのようにDBAの参加は必要ありません.redisのDBAは、データ構造を熟知し、使用シーンを理解する必要があります.
次に、kvデータベースによく見られる例を挙げて、キー値の設計について話し、リレーショナル・データベースと比較して、リレーショナル・データベースの不足点を発見します.

ユーザーログインシステム


ユーザーのログイン情報を記録するシステムで、ビジネスを簡素化した後、テーブルを1枚だけ残します.

リレーショナル・データベースの設計

mysql> select * from login;
+---------+----------------+-------------+---------------------+
| user_id | name           | login_times | last_login_time     |
+---------+----------------+-------------+---------------------+
|       1 | ken thompson   |           5 | 2011-01-01 00:00:00 |
|       2 | dennis ritchie |           1 | 2011-02-01 00:00:00 |
|       3 | Joe Armstrong  |           2 | 2011-03-01 00:00:00 |
+---------+----------------+-------------+---------------------+

user_idテーブルのプライマリ・キー、nameはユーザー名、login_を表しますtimesはそのユーザのログイン回数を表し、ユーザがログインするたびにlogin_timesは自己増加し、last_login_timeが現在時刻に更新されます.

redisの設計


リレーショナルデータをKVデータベースに変換するには、次のようにします.
keyテーブル名:プライマリ・キー値:カラム名
value列値
普通はコロンを使って分割符を作りますが、これは文にならないルールです.例えばphp-admin for redisシステムでは、デフォルトではコロンで分割され、user:1 user:2などのkeyがグループ化されます.そこで、以上の関係データをkvデータに変換して以下のように記録する.
Set login:1:login_times 5
Set login:2:login_times 1
Set login:3:login_times 2

Set login:1:last_login_time 2011-1-1
Set login:2:last_login_time 2011-2-1
Set login:3:last_login_time 2011-3-1

set login:1:name ”ken thompson“
set login:2:name “dennis ritchie”
set login:3:name ”Joe Armstrong“

これにより、プライマリ・キーが既知の場合、get、setにより、ユーザのログイン回数と最終ログイン時間と名前を取得または変更することができる.
一般ユーザは自分のidを知ることができず,自分のユーザ名しか知らないため,nameからidへのマッピング関係も必要であり,ここでの設計は上記とは異なる.
set "login:ken thompson:id"      1
set "login:dennis ritchie:id"    2
set "login: Joe Armstrong:id"    3

このように,ユーザがログインするたびにビジネスロジックは以下のように(python版),rはredisオブジェクト,nameは既に知られているユーザ名である.
#     id
uid = r.get("login:%s:id" % name)
#         
ret = r.incr("login:%s:login_times" % uid)
#            
ret = r.set("login:%s:last_login_time" % uid, datetime.datetime.now())

既知のidのみが必要な場合、あるユーザの最後のログイン時間を更新または取得し、ログイン回数、リレーショナルタイプ、kvデータベースに違いはありません.一つはbtree pk、一つはhashで、効果はすべてとても良いです.
最近ログインしたN人のユーザーを検索する必要があるとします.開発者は見てみましょう.やはり簡単です.sqlでできます.
select * from login order by last_login_time desc limit N

DBAは需要を理解した後、以降の表が大きい場合を考慮してlast_login_timeにインデックスを作成します.実行計画はインデックスleafblockの右端からNレコードにアクセスし、N回表して効果的です.
2日後には、ログイン回数が最も多い人が誰なのかを知る必要があります.同じ関係型はどう処理しますか?DEVは簡単だ
select * from login order by login_times desc limit N

DBAで見ると、またlogin_timeにインデックスを作成します.ちょっと問題があると思いますか.表の各フィールドには素引があります.
リレーショナル・データベースのデータ・ストレージの柔軟性が問題の源であり、データの格納方法は行ごとに並べられたスタック・テーブルのみです.統合されたデータ構造は、インデックスを使用してsqlのアクセスパスを変更してカラムに迅速にアクセスする必要があることを意味し、アクセスパスの増加は統計情報を使用して支援しなければならないことを意味し、多くの問題が発生します.
インデックスなし、統計計画なし、実行計画なし、これがkvデータベースです.
redisでは以上のニーズをどのように満たすのでしょうか.最新のN本のデータを求める需要に対して、チェーンテーブルの後進後出の特徴は非常に適している.私たちは上のログインコードの後にコードを追加して、ログインのチェーンテーブルを維持して、彼の長さを制御して、中に永遠に保存されているのは最近のN人のログインユーザーです.
#            
ret = r.lpush("login:last_login_times", uid)
#      N 
ret = redis.ltrim("login:last_login_times", 0, N-1)

これにより、最新のログイン者のidを取得する必要があります.以下のコードでいいです.
last_login_list = r.lrange("login:last_login_times", 0, N-1)

また、登録回数が最も多い人を求めるには、ソート、ポイントランキングなどのニーズに対してsorted setが非常に適しており、ユーザーと登録回数を1つのsorted setに統一的に格納しています.
zadd login:login_times 5 1
zadd login:login_times 1 2
zadd login:login_times 2 3

これにより、あるユーザーがログインした場合、sorted setを追加的に維持し、コードはこのようにします.
#           1
ret = r.zincrby("login:login_times", 1, uid)

では、最もログイン数の多いユーザーを獲得するにはどうすればいいのでしょうか.
ret = r.zrevrange("login:login_times", 0, N-1)

DEVは2行のコードを追加する必要があり、DBAはインデックスなどを考慮する必要はないことがわかります.

TAGシステム


tagはインターネットアプリケーションで特に多く見られ、従来の関係型データベースで設計すると少し不倫している.本を探す例でredisのこの方面の優位性を見てみましょう.

リレーショナル・データベースの設計


2枚の表、1枚のbookの明細、1枚のtag表は、1冊のtagを表し、1冊の本に複数のtagが存在することを示す.
mysql> select * from book;
+------+-------------------------------+----------------+
| id   | name                          | author         |
+------+-------------------------------+----------------+
|    1 | The Ruby Programming Language | Mark Pilgrim   |
|    1 | Ruby on rail                  | David Flanagan |
|    1 | Programming Erlang            | Joe Armstrong  |
+------+-------------------------------+----------------+

mysql> select * from tag;
+---------+---------+
| tagname | book_id |
+---------+---------+
| ruby    |       1 |
| ruby    |       2 |
| web     |       2 |
| erlang  |       3 |
+---------+---------+

       ,    ruby  web     ,              ?
select b.name, b.author  from tag t1, tag t2, book b
where t1.tagname = 'web' and t2.tagname = 'ruby' and t1.book_id = t2.book_id and b.id = t1.book_id

tagテーブルは2回関連してからbookに関連していますが、このsqlは複雑ですが、要求すればrubyですが、web方面の本ではありませんか?
リレーショナル・データは、これらの集合操作にはあまり適していません.

redisの設計


まずbookのデータは必ず保存しなければなりません.上と同じです.
set book:1:name    ”The Ruby Programming Language”
Set book:2:name     ”Ruby on rail”
Set book:3:name     ”Programming Erlang”

set book:1:author    ”Mark Pilgrim”
Set book:2:author     ”David Flanagan”
Set book:3:author     ”Joe Armstrong”

tagテーブルは集合を使用してデータを格納します.集合は交差を求め、集合するのが得意ですから.
sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3

では、rubyに属し、webに属する本ですか?
inter_list = redis.sinter("tag.web", "tag:ruby")

つまりrubyに属しますが、webに属していない本ですか?
inter_list = redis.sdiff("tag.ruby", "tag:web")

rubyとwebに属する本の合集?
inter_list = redis.sunion("tag.ruby", "tag:web")

簡単ではいけませんね.
以上の2つの例から、いくつかのシーンでは、リレーショナル・データベースがあまり適切ではないことがわかります.ニーズを満たすシステムを設計できるかもしれませんが、いつも変な感じがして、硬いカバーを運んでいるような感じがします.
特にログインシステムという例では,業務にインデックスを頻繁に作成する.複雑なシステムに置くとddl(インデックスの作成)が実行計画を変更する可能性があります.他のsqlが異なる実行計画を採用し、業務が複雑な古いシステムを採用しているため、この問題は予測しにくい.sqlは奇妙だ.DBAにこのシステムのすべてのsqlを理解するように要求するのは難しい.この問題はoracleで特に深刻で、DBAごとに遭遇したことがあると推定されています.MySQLのようなシステムではddlは不便です(現在はonline ddlの方法がありますが).大きな時計に出会って、DBAは夜明けに起きて業務の低ピーク期に操作して、このことは私は少なくしたことがありません.このような需要はredisに入れるとよく処理され、DBAは容量を見積もるだけでよい.
将来のOLT Pシステムはkvと関係型の緊密な結合であるべきである.