RedisおよびRedis Stack

RedisおよびRedis Stack

初めに

RedisはRemote Dictionary Serverの略称であり、NoSQLデータベース管理システムの一つです。

Redisは高性能でメモリ上にキーバリュー型のデータを格納するために使われます。

Redisは、データベース、キャッシュ、メッセージブローカーなどとして使用できます。処理速度を向上させるために、RAMに格納されたデータベースとして使用もできます。

Redisは、1ミリ秒未満の速度で応答時間を提供します。Redisでは、RAM上に格納するので、速度がハードディスクドライブへの格納より速いです。HDD、SSDへのアクセスに比べると、それぞれで、約150,000倍、約500倍速くなります。

マイクロサービス間との連携に適します。

基本特徴

データモデル

Redisはテーブルがありません。Redisはキーバリュー形式でデータを保存します。

以下にRedisのデータ型を示します。

STRING型:文字列であり、整数でもあり、浮動小数点数でもあります。Redisは、文字列全体または文字列の要素を処理でき、整数や浮動小数点数のインクリメント・デクリメントを処理できます。

LIST型:文字列の連結リストです。 Redisは、リストの両側からのプッシュ、ポップ、オフセットによるトリミング、単一または複数のリスト要素の読取、値の探索、削除をサポートします。

SET型:並び替えない文字列の集合です。Redisは、要素の追加・読取・削除、および、要素が集合に存在するかの確認をサポートします。さらに、Redisは、intersect、union、differenceといった集合演算もサポートします。

HASH型:偶然に並び替えるキーと値のペアのハッシュテーブルを格納します。 Redisは、要素の追加・読取・削除、および、全値の読取をサポートします。

ZSET型(sorted set):要素が 1 つの文字列(メンバー)と 1 つの浮動小数点数(スコア)とのマップであるリストで、このスコアで並び替えられます。Redisは、要素の追加・読取・削除、および、文字列またはスコアのランクによる要素の抽出をサポートします。

Replication – Persistence:

Redisは、プライマリ・レプリカ構成を使って、データを異なるサーバーへと複製できるような非同期レプリケーションをサポートします。これにより、リクエストが各サーバーの間で分散されるので読み取る性能がされ、メインサーバーが障害によって停止された場合、迅速に復旧できます。 永続性については、Redisはポイント・イン・タイム・バックアップ(すなわち、Redisデータをディスクに複製)をサポートします。

In-Memory Data Store

RedisはデータをRAMに格納するため、データの読み書きはハードディスクドライブよりもはるかに高速です

ただし、すべてのデータはRAM上に格納されているので、サーバーが停止されたら、無くなりますが、その対策としては、Redis Persistenceを使うことで対応できます。

Persistent redis

Redisは、RAM上に格納されているキーバリュー型のデータを動作しますが、サーバー停止などの障害が起きた場合にデータを保管すること及びサーバー再起動の時にデータを再生成するため、ハードディスクドライブへの格納も必要になります。Redisは、データをハードディスクドライブに複製するため、RDBとAOFという二つの方式を提供してくれます。

RDB (Redis Database)

RDBは、一定期間ごとにDBのスナップショットを作成し、ディスクにデータを複製します。

RDBのメリット
RDBでは、DBを異なるバージョンで保存できます。これにより、障害が起きた時に便利です。

RDBは、Redisのパフォーマンスを最適化するのに役立ちます。Redisのメインプロセスは、クライアントからリクエストする追加、読取、削除といった基本的な操作を含めた動作をRAM上に行います。それに対して、サブプロセスがディスクにI/O操作を行います。このような構成はRedisのパフォーマンスの最適化にとても役立ちます。

RDBのデメリット
通常、ユーザーは5分 (またはそれ以上) ごとに RDBスナップショットを作成するように設定します。そのため、障害が起きた時、Redisが動かなくなり、最新の時刻のデータのみ失われます。

RDB は fork を使用して、ディスク I/O 操作用の子プロセスを作成する必要があります。データが大きすぎると、forkプロセスに時間がかかり、データ量と CPU パフォーマンスによっては、サーバーがクライアントからのリクエストに数ミリ秒または 1 秒応答できなくなります。

AOF (Append Only File)

AOFは、サーバーが受信したすべての書き込み操作を保存します。これらの操作は、サーバーの再起動時またはデータセットのリセット時に再実行されます。

AOFのメリット
AOF Redisは、データセットがより安定することを確保します。毎秒ごとにまたはクエリごとにログを出力するようにRedisを設定できます。

Redisは AOFログを既存のファイルの末尾に追加する形で出力するため、既存のファイルのシークプロセスは不要です。何らかの理由 (ディスク領域不足または他の理由) でログが途中で書かれたコマンドで終了した場合においても、redis-check-aof ツールを使用すると簡単に修正することができます。

Redis はバックグラウンド プロセスを提供し、ファイル サイズが大きすぎる場合に AOFファイルを登録します。 Redisは、古いファイルに追加をしている間、現在のデータセットを作成するのに必要な操作を使って完全に新しいものを生成し、この2番目のファイルの準備ができたらRedisが2つを切り替え新しいものへの追加を開始するため、この登録は完全に安全です。

AOFのデメリット
通常、AOFファイルは、同じデータセットの同等のRDBファイルより大きくなります。

AOFは、正確なfsyncポリシーに応じて、RDBよりも遅くなるかもしれません。一般的に毎秒ごとに設定されたfsyncのパフォーマンスは依然として高く、fsyncを無効にすると高負荷でもRDBとまったく同じ速度で動作するはずです。それでもRDBは書き込みの負荷が大きい場合でも最大レイテンシについてはより多くの保証を提供できます。

パフォーマンス

ほとんどのタスクがディスクドライブへのアクセスの繰り返しを必要とする従来のデータベースとは異なり、Redisは単純に計算結果を取得します。そのため、Redisのパフォーマンスは、低レンテンシと高スループットで一般的な読み書きのタスクが著しく高速になります。

その結果、より多くの操作をサポートし、応答時間を10倍高速化します。平均でミリ秒未満の読み書きの操作を実現でき、1 秒間に数百万件の操作をサポートできます。

Redisの一般的なユースケース

キャッシュ

処理待ちのタスク一覧

ゲームのリーダーボード

セッション管理

マシンラーニング

リアルタイム分析

など

Redis Stack

Redis Stackは、データモデルと処理エンジンを追加するRedisの拡張機能です。プログラマーがリクエストをミリ秒単位で確実に処理できるバックエンドデータプラットフォームを備えたリアルタイムアプリケーションを構築できるようにするために作成されました。

Redis StackにはRedisJSONRediSearchRedisGraphRedisTimeSeriesRedisBloomの5つのモジュールパッケージがあります。

さらに、Redis Stackには、データを視覚化・最適化するツールであるRedisInsightも含まれます。

RedisInsightの画面

Redis Stackがどのように動作するかを理解するために、node-redisライブラリでRedisJSONRediSearchモジュールを使用する方法を説明します。

node-redisライブラリのインストール

npm install redis

Redisサーバーへの接続

import { createClient } from 'redis';

const client = createClient({
  url: 'redis[s]://[[username][:password]@][host][:port][/db-number]'
});

client.on('error', (err) => console.log('Redis Client Error', err));

await client.connect();

RedisJSON

RedisJSONは、Redisで JSONをサポートするため提供される Redis モジュールです。 RedisJSONを使うと、他の Redis データ型と同様に、RedisでJSON値を登録・更新・取得することができます。また、RedisJSONはRediSearchと同時に動作し、JSON ドキュメントのインデックス作成とクエリを実行できるようにします。

ドキュメントはツリー構造のバイナリ データとして格納されるため、子要素に素早くアクセスできます。

JSONドキュメントのRedisへの保存

JSON.SETコマンドは、Redisキーの指定されたJSONパスにJSON値を格納します。

ここでは、JSONドキュメントをRedisキー”users“のルートディレクトリに保存します。

await client.json.set('noderedis:users', '$', {
    name: 'Alice',
    age: 29
});

RedisからJSONドキュメントを抽出

const results = await client.json.get('noderedis:users');

resultsの結果は 以下のように返却されます。

{ name: 'Alice', age: 29 }

RediSearch

RediSearchは、クエリ、サブインデックス作成、と検索機能を提供するRedisモジュールです。 RediSearchを使用するには、最初に、Redisデータのインデックスを宣言します。 その後、RediSearchクエリ言語を使用してそのデータをクエリできます。

RediSearchは圧縮された逆インデックスを使用して、少ないメモリで迅速にインデックスを作成します。

RediSearchは、正確なフレーズマッチング、あいまい検索 (類似検索)、やテキストクエリの数値フィルタリングなどを提供します。

Redis Hashesでのインデックス作成とクエリ

インデックス作成

検索を行う前に、RediSearchにデータのインデックスを作成する方法と、データを検索するためのキーを指定する必要があります。FT.CREATEコマンドは 、RediSearchインデックスを作成します。 これを使用してidx:animalsと呼ぶインデックスを作成する方法は次のとおりです。ここで、namespeciesageの各項目及び接頭辞noderedis:animalsで始まるRedisのキー名を含むハッシュにインデックスを付けます

await client.ft.create('idx:animals', {
  name: {
    type: SchemaFieldTypes.TEXT,
    sortable: true
  },
    species: SchemaFieldTypes.TAG,
    age: SchemaFieldTypes.NUMERIC
  }, {
    ON: 'HASH',
    PREFIX: 'noderedis:animals'
  }
);

インデックスを作成したあと、対応する接頭辞を付けるすべての新しいハッシュドキュメントが、作成時に自動的にインデックスされます。

ドキュメント追加

HSETコマンドを使って、新しいハッシュドキュメントを作成し、インデックスに追加します。

await client.hSet('noderedis:animals:1', {name: 'Fluffy', species: 'cat', age: 3});
await client.hSet('noderedis:animals:2', {name: 'Ginger', species: 'dog', age: 4});

インデックス検索

FT.SEARCHを使って、特定の単語を含むドキュメントのインデックスを検索ます。

const results = await client.ft.search(
    'idx:animals', 
    '@species:{dog}',
    {
      SORTBY: {
        BY: 'age',
        DIRECTION: 'DESC' // or 'ASC' (default if DIRECTION is not present)
      }
    }
  );

resultsの結果は以下のように返却されます。

{
  total: 1,
  documents: [
    {
      id: 'noderedis:animals:2',
      value: {
        name: 'Ginger',
        species: 'dog',
        age: '4'
      }
    }
  ]
}

RedisJSONでのインデックス作成とクエリ

インデックス作成

JSONドキュメントのインデックスを作成するために、以下のように実装します。

await client.ft.create('idx:users', {
    '$.name': {
        type: SchemaFieldTypes.TEXT,
        SORTABLE: 'UNF'
    },
    '$.age': {
        type: SchemaFieldTypes.NUMERIC,
        AS: 'age'
    },
}, {
    ON: 'JSON',
    PREFIX: 'noderedis:users'
});

インデックスクエリ

FT.SEARCHコマンドとRediSearchクエリ言語を使用します。

例:30歳未満のユーザーを検索するクエリは以下のようになります。

const results = await client.ft.search('idx:users', '@age:[0 30]');

resultsの結果は以下のように 返却されます。

{
  "total": 1,
  "documents": [
    {
      "id": "noderedis:users",
      "value": {
        "name": "Alice",
        "age": 29
      }
    }
  ]
}

結論

Redisは、多くのメリットを持ちオープンソースデータストアであるため、ソフトウェア開発において広く使用されています。

Redisは、リアルタイム性の求められるアプリケーションやデータキャッシュに最適です。簡単に設定でき、非常に高速に応答でき、柔軟なデータ構造をサポートするからです。

Redisは、Memcachedと比較してRAM上にデータを保存する同じメカニズムを使用しますが、サーバー側でより多くのデータ型と操作をサポートします。 また、Redisは、Memcachedにはないですが、データをバックアップするメカニズムも用意します。 したがって、複雑なデータ構造を持つシステムをキャッシュしたい場合は、Redisの方が適します

参考元

https://redis.io/docs/

https://redis.js.org/

https://byterot.blogspot.com/2012/11/nosql-benchmark-redis-mongodb-ravendb-cassandra-sqlserver

https://github.com/redis/node-redis