こんにちは! Redisの深い世界へようこそ。
今日は、Redisを使いこなす上で避けて通れない「Lua(ルア)スクリプト」と、そのレプリケーション(複製)の裏側にある、ちょっとした魔法のような機能`redis.replicate_commands()`についてお話しします。
「なんだか難しそうな名前だな…」と思いましたか?
大丈夫です。専門用語はできるだけ使わず、日常のたとえ話から優しく解きほぐしていきますね。ここをクリアすれば、あなたもRedisの仕組みを深く理解したワンランク上のエンジニアです。一緒にマスターしていきましょう!
—
データの「お留守番」の仕組み:レプリケーションってなに?
まずは前提として、Redisの「レプリケーション」についておさらいしておきましょう。
Redisは、メモリ上にデータを置いてパパッと超高速に処理するデータベースです。でも、もしそのメインのコンピュータ(親分)が突然壊れてしまったら大変ですよね。そこで、全く同じデータを常にコピーして持っている「子分(レプリカ)」を控えておく仕組みがあります。これがレプリケーションです。
親分に起きた変更は、リアルタイムで子分にも伝えられ、常に同じ状態をキープします。
—
Luaスクリプト:Redisの「一気通貫の魔法」
さて、Redisでは「Luaスクリプト」という小さなプログラムをRedisサーバーの中で直接実行させることができます。
日常に例えてみましょう。
あなたは文房具店の店長です。お客さんから「このノートを買うのと同時に、鉛筆の在庫を1本減らして、さらに今日の売上ボードに500円と書き込んで!」と、3つのお願いをひとまとめに頼まれました。
これを別々にやると、「ノートは売れたけど、鉛筆の在庫を減らすのを忘れた!」なんてミスが起きるかもしれません。
Luaスクリプトを使えば、この「3つのお願い」を1つのカプセルに閉じ込めて、Redisに「この一連の作業を、途中で邪魔されずに一気にやりきって!」とお願いできるのです。
—
ここで起きる「ちょっとした事件」
さて、ここからが本題です。
このLuaスクリプトを使って、親分側で次のような処理をしたとします。
1. 「今の正確な現在時刻」を取得する
2. その時刻を使って、データをガチャガチャと加工する
3. データを保存する
親分の世界では、このスクリプトは一瞬で実行され、何の問題もありません。
しかし、これを子分に伝えるときに事件が起きます。
従来のRedisは、親分で起きたことを子分に伝えるとき、「親分で実行されたLuaスクリプトそのものを、子分もう一度最初から最後まで実行しなさい」という方法をとっていました。
ここで問題です。
親分がスクリプトを実行した「時刻」と、子分がそのスクリプトを「後から実行した時刻」は、ほんの数ミリ秒ですがズレますよね?
さらに、スクリプトの中で「ランダムな数字(乱数)」を使っていたらどうでしょう? 親分と子分で違う結果になってしまい、親分と子分のデータが食い違ってしまうのです。これはデータベースの世界では大罪です。
—
救世主登場:`redis.replicate_commands()` とは?
この問題を華麗に解決するのが、今回紹介する `redis.replicate_commands()` です。
先ほどのたとえ話に戻りましょう。
「親分がやった手順(スクリプト)そのもの」を子分にコピーさせるのではなく、「そのスクリプトの中で実行された、個別の具体的なコマンド(結果)」だけを子分に伝えるモードに切り替える呪文、それが `redis.replicate_commands()` です。
料理に例えるならこうです。
- 今までのやり方(スクリプト全体をコピー):
「このレシピの冊子をそのまま子分に渡すから、子分も全く同じ手順で一から料理を作り直して!」(時間がズレると味が変わっちゃうかも)
- `redis.replicate_commands()` を使ったやり方:
「レシピは私が読むから、そこで使った『塩小さじ1』『砂糖大さじ2』という具体的な結果だけを子分にメモで渡して、それをそのまま真似させよう!」
これなら、時刻がズレようがランダムな数字が出ようが、親分で確定した「結果のコマンド」だけが子分にそのまま伝わるので、親分と子分のデータが完璧に一致します。
—
実際のコードで見てみよう
百聞は一見に如かず。実際にLuaスクリプトの中でどう使うのかを見てみましょう。
— 1. 最初にこの魔法の呪文を呼び出し、
— 「これからはスクリプト全体じゃなく、個別のコマンド単位で子分に伝えてね」と設定します
redis.replicate_commands()
— 2. 例えば、実行するたびに結果が変わるような処理や、時間を扱う処理を書きます
local current_time = redis.call(‘TIME’)[1]
redis.call(‘SET’, ‘last_access’, current_time)
— 3. 別な処理
redis.call(‘INCR’, ‘visit_count’)
return “OK”
このスクリプトをRedisに投げると、内部的には `SET` コマンドと `INCR` コマンドが実行されたという事実だけが、綺麗に子分へとレプリケートされます。非常に安全ですね!
—
先輩エンジニアからのアドバイス:いつ使うべき?
「じゃあ、常にこの設定を入れればいいんだね?」と思ったそこのあなた。さすが、鋭い着眼点です。
基本的にはその通りなのですが、この `redis.replicate_commands()` を使うと、個別のコマンド単位でネットワークを流れるデータが増えるため、スクリプトの実行スピードがほんの少しだけ落ちる場合があります。
だからこそ、以下のようなケースでピンポイントで使うのがプロの技です。
- スクリプト内で `TIME`(時刻)や `RANDOMKEY`(ランダムなキー)など、実行するたびに結果が変わるものを使いたいとき
- 子分とのデータの食い違いを絶対に防ぎたいとき
—
おわりに
いかがでしたでしょうか?
`redis.replicate_commands()` は、一見すると地味な設定に見えますが、「親分と子分の信頼関係(データの整合性)」を裏でしっかりと守るための、非常に重要で知的な機能です。
この仕組みが頭に入っていれば、複雑なLuaスクリプトを組むことになっても、「おっと、ここではレプリケーションのモードに気をつけないとだな」と自信を持ってコードを書けるはずです。
ここをクリアできれば、Redisの裏側の挙動に対する理解はもうバッチリマスターですよ!
日々の開発ライフを、Redisとともに快適に楽しんでいきましょう。
コメント