CLOVER🍀

That was when it all began.

ValkeyとRedisのクライアントライブラリーValkey GLIDEをPythonラッパーで試す

これは、なにをしたくて書いたもの?

Valkeyのクライアントライブラリーとして、Valkey GLIDEというものがあるようなので試してみることにしました。

各言語のRedisクライアントが、どんどんredis Organizationに移っていっていたのでちょっと気になっていたんですよね。

Redis · GitHub

Valkey GLIDE

Valkey GLIDEのGitHubリポジトリーはこちら。

GitHub - valkey-io/valkey-glide: An open source Valkey client library that supports Valkey and Redis open source 6.2, 7.0 and 7.2. Valkey GLIDE is designed for reliability, optimized performance, and high-availability, for Valkey and Redis OSS based applications. GLIDE is a multi language client library, written in Rust with programming language bindings, such as Java and Python

Valkey GLIDEはValkeyとRedisをサポートするクライアントライブラリーです。

GLIDEというのは「General Language Independent Driver for the Enterprise」の略らしいです。

Valkey General Language Independent Driver for the Enterprise (GLIDE), is an open-source Valkey client library.

現時点でValkey 7.2、8.0、Redis 6.2、7.0、7.2をサポートしています。

複数の言語向けに提供されていて、JavaPython、Node.jsがあります。リポジトリーを見ているとGo、C#もありそうですね。

AWSによるブログなどはこちら。

Valkey GLIDE – Valkey と Redis OSS向けのオープンソースクライアントライブラリのご紹介 | Amazon Web Services ブログ

Valkey と Redis OSS のクライアントである Valkey GLIDE v1.1 が Node.js のサポートを開始 - AWS

ValkeyおよびRedisのすべてのコマンドのサポート、クラスターやレプリカからの読み取りへの対応、ステートフル接続を使った自動的な
pub/sub再接続などを機能として持っているようです。

内部構成としては、Rustで実装されたredis-rsをコアとしていて、各言語に対してラッパーと呼ばれるバインディングとコミュニケーション
レイヤーで構成されているようです。

ドキュメントはこちら。

Home · valkey-io/valkey-glide Wiki · GitHub

General Concepts · valkey-io/valkey-glide Wiki · GitHub

各ラッパーのドキュメント。

Java Wrapper · valkey-io/valkey-glide Wiki · GitHub

NodeJS wrapper · valkey-io/valkey-glide Wiki · GitHub

Python wrapper · valkey-io/valkey-glide Wiki · GitHub

各言語の実装状況は、こちらのページで確認できるようです。

ValKey Commands Implementation Progress · valkey-io/valkey-glide Wiki · GitHub

これを見ると、そのうちPHPのラッパーも実装されそうですね。

redis-rsについて

Valkey GLIDEはRustで実装されたredis-rsをコアとしていると書きましたが、redis-rsはValkey GLIDEのリポジトリーに取り込まれています。

https://github.com/valkey-io/valkey-glide/tree/v1.2.0/glide-core/redis-rs

オリジナルのredis-rsはこちらです。

GitHub - redis-rs/redis-rs: Redis library for rust

なのですが、Valkey GLIDEはredis-rsのフォークを使用していました。

GitHub - amazon-contributing/redis-rs: Redis library for rust

それがこちらのPull RequestでValkey GLIDEの中に取り込まれたようです。

Make redis-rs part of this repo by eifrah-aws · Pull Request #2456 · valkey-io/valkey-glide · GitHub

ドキュメントを見ていると、redis-rs 0.25.2からのフォークに見えますね。

ドキュメントなどを見るのはこれくらいにして、試してみましょう。今回はPythonラッパーを使うことにします。

環境

今回の環境はこちら。

$ python3 --version
Python 3.12.3


$ pip3 --version
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)

Valkey。

$ bin/valkey-server --version
Valkey server v=8.0.1 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=62879af2f2d0ec0f

Valkeyの設定は以下とし、172.17.0.2で動作しているものとします。

conf/valkey.conf

bind 0.0.0.0

user default off
user valkey-user on >password +@all ~* &*

Valkey GLIDEのPythonラッパーを試す

では、Valkey GLIDEのPythonラッパーを試してみます。

valkey-glide/python at v1.2.0 · valkey-io/valkey-glide · GitHub

Python wrapper · valkey-io/valkey-glide Wiki · GitHub

インストール。

$ pip3 install valkey-glide

型定義はValkey GLIDEに含まれていそうです。

あとはpytestとMypyをインストール。

$ pip3 install pytest pytest-asyncio mypy

Valkey GLIDEではasyncawaitを使うことになりそうなのでpytest-asyncioをインストールしています。

GitHub - pytest-dev/pytest-asyncio: Asyncio support for pytest

インストールしたライブラリーの一覧。

$ pip3 list
Package           Version
----------------- -------
iniconfig         2.0.0
mypy              1.13.0
mypy-extensions   1.0.0
packaging         24.2
pip               24.0
pluggy            1.5.0
protobuf          5.29.1
pytest            8.3.4
pytest-asyncio    0.24.0
typing_extensions 4.12.2
valkey-glide      1.2.0

まずはテストコードの雛形。

tests/test_glide.py

from glide import GlideClient, GlideClientConfiguration, NodeAddress, ServerCredentials, Transaction
import pytest

## ここに、テストを書く!

まずはValkeyへの接続、切断から。

@pytest.mark.asyncio
async def test_connect_valkey() -> None:
    addresses = [NodeAddress("172.17.0.2", 6379)]
    credentials = ServerCredentials("password", "valkey-user")
    config = GlideClientConfiguration(addresses, credentials=credentials)
    client = await GlideClient.create(config)

    await client.close()

asyncawaitを使うことになるんですね。

このあたりを参考にしています。

Python wrapper / Client Initialization / Standalone

Python wrapper / Advanced Configuration Settings / Authentication

ところで、pytestを実行するとこんな警告が出力されていたので

/path/to/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))

pytest.iniを作成してこのように設定。

pytest.ini

[pytest]
asyncio_default_fixture_loop_scope = function

デフォルト値らしいですけど。

Configuration — pytest-asyncio 0.25.2.dev5+g2188cdb documentation

set、getコマンド。

@pytest.mark.asyncio
async def test_set_get() -> None:
    addresses = [NodeAddress("172.17.0.2", 6379)]
    credentials = ServerCredentials("password", "valkey-user")
    config = GlideClientConfiguration(addresses, credentials=credentials)
    client = await GlideClient.create(config)

    try:
        await client.set("key1", "value1")
        assert await client.get("key1") == b"value1"

        await client.delete(["key1"])

        assert await client.get("key1") == None
    finally:
        await client.close()

最後にdeleteも入れていますけど。

値は自動的にエンコードされるようですが、取得した時にはデコードされていないようです。

hset、hget。

@pytest.mark.asyncio
async def test_hset_hget() -> None:
    addresses = [NodeAddress("172.17.0.2", 6379)]
    credentials = ServerCredentials("password", "valkey-user")
    config = GlideClientConfiguration(addresses, credentials=credentials)
    client = await GlideClient.create(config)

    try:
        await client.hset("hkey1", {"key1": "value1", "key2": "value2"})
        assert await client.hget("hkey1", "key1") == b"value1"
        assert await client.hgetall("hkey1") == {b"key1": b"value1", b"key2": b"value2"}

        await client.delete(["hkey1"])
        assert await client.hgetall("hkey1") == {}
    finally:
        await client.close()

トランザクション

@pytest.mark.asyncio
async def test_transaction() -> None:
    addresses = [NodeAddress("172.17.0.2", 6379)]
    credentials = ServerCredentials("password", "valkey-user")
    config = GlideClientConfiguration(addresses, credentials=credentials)
    client = await GlideClient.create(config)

    try:
        transaction = Transaction()

        transaction.set("key1", "value1")
        transaction.set("key2", "value2")

        transaction.get("key1").get("key2")

        result = await client.exec(transaction)

        assert result == ["OK", "OK", b"value1", b"value2"]

        await client.delete(["key1", "key2"])
    finally:
        await client.close()

Python wrapper / Valkey commands / Transaction

トランザクションといっても、multi/execのことですが。

Valkey Documentation · Transactions

こんなところでしょうか。

おわりに

ValkeyとRedisのクライアントライブラリー、Valkey GLIDEをPythonラッパーで試してみました。

Valkey GLIDEの概要がわかったのと、Pythonラッパーの場合はasyncawaitを使ったスタイルになることがわかりました。

今後は、Valkeyを使う時はValkey GLIDEに慣れていってみようと思います。