🦁

SwiftでValkeyを使う方法を簡単に紹介

に公開

Valkey

ValkeyとはRedis v7.2.4からのフォークです。
Redisはv8からのライセンスがAGPLv3に変わり、ValkeyのBSDとは異なり、ライセンス周りがかなり自由です。

https://valkey.io

Valkey for Swift

Swiftで書かれたクライアントのSDKが公式で公開されています。こちらの使い方を簡単に紹介します。

https://github.com/valkey-io/valkey-swift

Valkey(Redis)の基礎

RDBS(SQL)と異なり、Key/Value形式で管理されています。
保存/取得のコマンドがRDBS(SQL)より、用途別で細かく用意されています。
すべては紹介しきれないため、よく使いそうなものだけ紹介します。

https://valkey.io/commands/

https://redis.io/learn/howtos/quick-start/cheat-sheet

Valkey-Swift

Valkey-Swiftは最初にrun()を別Taskで走らせておく必要があります。
これをしないとValkeyにコマンドを送っても何も返ってきません。

import Valkey

let client = ValkeyClient(.hostname("localhost", port: 6379), logger: logger)

Task {
  await client.run()
}

let data = try await client.get("Key")

SET: 値の保存

TestKeyというキーで、TestValueという値を保存する

import Valkey

let client: ValkeyClient

try await client.set(
  "TestKey",               // キー
  value: "TestValue",      // 値
  expiration: .seconds(60) // 有効期限(保存期間)
)

GET: 値の取得

TestKeyというキーで値を取得する

import Valkey

let client: ValkeyClient

guard let buffer = try await client.get("TestKey") else {
  print("Not Found")
  return
}

let value = String(buffer: buffer)

print(value)

DEL: キー/値の削

import Valkey

let client: ValkeyClient

try await client.del(keys: ["Key1", "Key2"])

SET: Encodableの保存方法

import Valkey

let client: ValkeyClient

struct User: Codable {
  var id: UUID
  var name: String
  var age: Int
}
let user = User(id: UUID(), name: "Bob", age: 20)
let data = try JSONEncoder().encode(user)
try await client.set(
  ValkeyKey("users:\(user.id)"),
  value: data
)

GET: Decodableの取得方法

let userId: UUID
guard let buffer = try await client.get(
  "users:\(userId.uuidString)"
) else {
  print("Not Found")
  return
}
let user = try JSONDecoder().decode(User.self, from: buffer)
print(user)

MSET: Array<Encodable>の保存方法

MSETは複数のKey/Valueの組み合わせを1回で保存でコマンドです。

import Valkey
import NIOCore

let client: ValkeyClient

struct User: Codable {
  var id: UUID
  var name: String
  var age: Int
}

func save(users: [User]) async throws {
  let encoder = JSONEncoder()
  let datas = try users.map { ($0.id, try encoder.encode($0)) }
      
  try await client.mset(
    data: datas.map { .init(key: ValkeyKey("users:\($0.0.uuidString)"), value: $0.1)}
  )
}

MGET: Array<Decodable>の取得方法

MGETは複数のキーを渡して、1回で複数の値を取得できるコマンドです。

import Valkey
import NIOCore

let client: ValkeyClient

struct User: Codable {
  var id: UUID
  var name: String
  var age: Int
}

func fetchUsers(ids: [UUID]) async throws -> [User] {    
  let usersData: RESPToken.Array = try await client.mget(
    keys: ids.map { ValkeyKey("users:\($0.uuidString)") }
  )
          
  let decoder = JSONDecoder()
  let users: [User] = try usersData.map {
    try decoder.decode(User.self, from: $0.decode(as: ByteBuffer.self))
  }
  return users
}

connection

withConnectionを使って、コマンドをまとめて実行することが可能です。

try await client.withConnection { connection in
  connection.set("Key1", value: "Value1")
  connection.set("Key2", value: "Value2")
}

Vapor対応

Vaporではキャッシュのツールとしてvapor/redisが用意されていますが、Swift 6に対応する予定のVapor v5には対応しなさそうな雰囲気を勝手に感じています。

https://github.com/swift-server/RediStack/issues/129#issuecomment-3144264639
It might be worth checking out the new Valkey library that's written from scratch with Swift concurrency in mind - https://github.com/valkey-io/valkey-swift

なのでValkeyVaporを作成しました。vapor/redisのようにVapor上で使うことができます。

https://github.com/zunda-pixel/ValkeyVapor

Discussion