保存するデータが複数のフィールドを持つ場合、単純な文字列では不都合な選択を迫られます:オブジェクト全体をJSONにシリアライズして変更のたびに書き換えるか、フィールドを複数の独立したキーに分散させるかです。ハッシュは、オブジェクトに適したデータ構造です。単一のキーの下にフィールドと値のペアを保存し、個々のフィールドの読み書きを可能にし、関連データをグループ化します。これはユーザー記録、セッション、またはキャッシュされた行に最適です。

これは 文字列とカウンター に続く、Redis マスタークラスの第4部です。

基本操作

ハッシュは、単一のキーの下でフィールドから値へのマップです:

HSET user:1 name "Aman" plan "pro" logins "42"
HGET user:1 plan          # "pro"
HGETALL user:1            # すべてのフィールドと値
HMGET user:1 name plan    # 複数のフィールドを一度に
HDEL user:1 logins        # フィールドを削除

Enter fullscreen mode Exit fullscreen mode

シリアライズされたJSON文字列に対する利点は、直接的なフィールドアクセスです。1つのフィールドを読み取るには、HGET を使い、オブジェクト全体を取得して解析する必要はありません。1つのフィールドを更新するには、HSET を使い、ブロブ全体を読み取って修正して書き込む必要はありません。大規模なオブジェクトでは、これにより高速化され、同時実行時の安全性も向上します。異なるフィールドを更新する2つのクライアントが互いに干渉することがないためです。

JSONを文字列に保存するだけではなぜいけないのか

これは人々が悩む選択肢なので、明確にしておきましょう。オブジェクトをJSON文字列として保存するのは、キャッシュエントリのようにオブジェクト全体を一度に読み書きする場合、シンプルで問題ありません。ハッシュの方が優れているのは以下の場合です:

  • 個々のフィールドを頻繁に読み書きする場合。HGET user:1 plan は、1つのフィールドを読み取るためにJSONブロブ全体を取得して解析するより優れています。
  • 複数のクライアントが異なるフィールドを同時に更新する場合。JSONでは、2つの書き込み側がそれぞれオブジェクト全体を読み取って修正して書き込み、一方の変更が失われます。ハッシュでは、異なるフィールドへのHSETは独立しており安全です。
  • 数値フィールドをインクリメントする場合。HINCRBY はフィールドをアトミックに増加させますが、JSON-in-a-stringでは読み取り-修正-書き込みの競合なしにはできません。

ルール:オブジェクト全体へのアクセスの場合、JSON文字列で問題ありません。フィールドレベルのアクセスや同時フィールド更新の場合は、ハッシュを使用します。

アトミックなフィールド操作

文字列と同様、ハッシュにもアトミックな数値操作がありますが、フィールドに限定されます:

HINCRBY user:1 logins 1          # logins フィールドをアトミックに +1
HINCRBY cart:99 item:42 2        # item 42 の数量を +2
HINCRBYFLOAT account:1 balance 9.99

Enter fullscreen mode Exit fullscreen mode

これは、内部にカウンターを持つオブジェクトに非常に便利です。ハッシュとして保存されたショッピングカート(フィールドが製品、値が数量)では、HINCRBY で1つの商品の数量をアトミックに調整でき、他のフィールドは影響を受けません。ユーザーごとの統計オブジェクトでは、loginsposts を独立して安全に増加させることができます。オブジェクトのグループ化とフィールドごとのアトミックカウンターを同時に実現できます。

メモリ効率

ハッシュにはスケール時の優れた特性があります。小さなハッシュの場合、Redisはコンパクトなメモリエンコーディング(listpack)を使用し、各フィールドを個別のトップレベルキーとして保存するよりもはるかにスペース効率が良いです。そのため、オブジェクトのフィールドを1つのハッシュにグループ化すると、同等の独立したキーのセットよりもメモリ使用量が少なくなります。

これが、多くの小さなオブジェクトを保存する場合に、フィールドごとに1つのキーではなくハッシュを使用することが一般的な推奨理由です。100万のユーザー記録をハッシュとして保存すると、同じデータを数百万の独立した文字列キーに分散させるよりも明らかにメモリ使用量が少なく、管理も整理されます(ユーザーごとに1つのキー、ユーザーを完全に削除するには1つのDEL)。

有効期限:唯一の注意点

知っておくべき制限があります。歴史的に、Redisの有効期限(TTL)はキーレベルで適用され、ハッシュの個別フィールドには適用されません。user:1 ハッシュ全体に有効期限を設定することはできましたが、そのlogins フィールドだけに設定することはできませんでした。これは、フィールドごとの有効期限には回避策や異なる構造が必要だったことを意味します。

最近の Redis バージョン(7.4以降)では、HEXPIRE で個々のハッシュフィールドにTTLを設定する機能が追加され、歴史的な制限が解消されました:

HEXPIRE user:1 3600 FIELDS 1 session_token   # 1時間後に1つのフィールドを期限切れにする

Enter fullscreen mode Exit fullscreen mode

古いバージョンを使用している場合は、TTLがキーレベルであることを覚えておいてください。フィールドごとのデータを期限切れにするには、それらのフィールドを独自のキーの下に保存するか、HEXPIRE をサポートするバージョンに移行する必要があります。ほとんどのオブジェクト保存用途では、ハッシュ全体を期限切れにする(セッション全体、キャッシュされたレコード全体)ため、これはめったに問題になりません。

ハッシュを使用する場合

フィールドを個別にアクセスまたは更新するオブジェクトを保存する場合にハッシュを使用します:

  • ユーザー記録とプロファイル:フィールドを1つずつ読み書きする場合。
  • セッション:複数の属性を持つセッションオブジェクト、単位として期限切れにする場合。
  • キャッシュされたデータベース行:行の列をフィールドとして、個別に更新可能な場合。
  • 埋め込みカウンターを持つオブジェクト:カート、エンティティごとの統計、アトミックに増加させる数値フィールドを持つもの。

オブジェクト全体を一度に読み書きする場合のみで、単一のシリアライズされたブロブのシンプルさがフィールドレベルのアクセスを上回る場合は、代わりにJSON文字列を使用します。

ハッシュは、フィールドレベルのアクセスやフィールドごとのアトミック更新を犠牲にすることなく、Redisが構造化されたオブジェクトを保存する方法です。関連データを1つのキーの下にグループ化し、小さなオブジェクトのメモリを節約し、同時書き込み側が異なるフィールドに安全に触れることを可能にします。値が単一のものではなくオブジェクトである場合、ハッシュはほぼ常に正しい構造です。

次に、リストについて説明します:キューや最近のアイテムフィードを支える順序付きシーケンスと、それらを単純なワークキューに変えるブロッキング操作です。

主要なポイント

  • ハッシュは1つのキーの下にフィールド値のペアを保存し、シリアライズされたブロブ全体ではなく個々のフィールドへの直接アクセスを提供します。
  • フィールドを個別にアクセスする場合、または複数のクライアントが異なるフィールドを同時に更新する場合、JSON文字列よりもハッシュを優先します。
  • HINCRBY は単一のフィールドをアトミックにインクリメントします。カートやエンティティごとの統計などの埋め込みカウンターを持つオブジェクトに最適です。
  • 小さなハッシュはコンパクトなメモリエンコーディングを使用するため、フィールドをグループ化すると、多くの独立したキーよりもスペース効率が良くなります。
  • TTLは歴史的にキーレベルです。Redis 7.4+ではHEXPIREでフィールドごとの有効期限が追加されましたが、通常はハッシュ全体を期限切れにします。

よくある質問

RedisハッシュをJSON文字列の代わりに使用すべきなのはどのような場合ですか?

個々のフィールドを頻繁に読み書きする場合、または複数のクライアントが異なるフィールドを同時に更新する場合、ハッシュを使用します。ハッシュは独立したフィールドアクセスと更新を可能にするためです。オブジェクト全体を一度に読み書きする場合にのみJSON文字列を使用します。

Redisハッシュの単一フィールドをインクリメントできますか?

はい、HINCRBY(またはHINCRBYFLOAT)を使用すると、他のフィールドに影響を与えずに1つのフィールドの数値をアトミックに増加させることができます。これは、カートの商品ごとの数量のような埋め込みカウンターを持つオブジェクトに最適です。

ハッシュは独立したキーよりもメモリ効率が良いですか?

多くの小さなオブジェクトの場合、はい。Redisは小さなハッシュをコンパクトにエンコードするため、オブジェクトのフィールドを1つのハッシュにグループ化すると、各フィールドを独自のトップレベルキーとして保存するよりもメモリ使用量が少なく、データは論理的にグループ化されます。

単一のハッシュフィールドに有効期限を設定できますか?

歴史的にはできませんでした。TTLはキー全体にのみ適用されました。Redis 7.4以降では、フィールドごとのTTLにHEXPIREが追加されました。古いバージョンでは、ハッシュ全体を期限切れにするか、フィールドごとに期限切れにするデータを別々のキーの下に保存する必要があります。

Redisにユーザーオブジェクトを保存するにはどうすればよいですか?

user:1 のようなキーのハッシュを使用し、各属性(nameplanlogins)のフィールドを設定します。任意のフィールドを直接読み書きし、数値フィールドをアトミックにインクリメントでき、単一のDELでユーザー全体を削除できます。

参考文献


This article was originally published on amanksingh.com/blog/redis-hashes.

著者について

Aman Kumar Singh は、インドのノイダを拠点とするチームリーダー兼シニアソフトウェアエンジニアで、TypeScript、Next.js、NestJS、PostgreSQL、Redisを使用したフルスタックエンジニアリング、システム設計、本番SaaSについて執筆しています。