All Articles

インメモリデータベースredisを触ってみた(初回編)

概要

isucon の事前学習として redis について学習してみたものをまとめています。 触り始めなのである程度、基本的なコマンドを網羅したい。

redis について(wiki 参照 https://ja.wikipedia.org/wiki/Redis)

Redis は、ネットワーク接続された永続化可能なインメモリデータベース。連想配列(キー・バリュー)、リスト、セットなどのデータ構造を扱える。いわゆる NoSQL データベースの一つ。オープンソースソフトウェアプロジェクトであり、Redis Labs(英語版)がスポンサーとなって開発されている。

  • メモリにデータを key, value の形で保存することができる。
  • データをキャッシュする際に使用されている。

redis の導入

macOS のみ

brew install redis

その後起動させる

redis-server

redis-cli コマンドを使用すれば対話的に redis のコマンドを打ち込むことができる。

基本的なコマンド

SET

データを key, value の形で保存する。

GET

key を指定してデータを取得する。

DEL

key を指定してデータを削除する。

EXISTS

key に対応するデータが存在するかどうかを調べる。

これらの動作を redigo/redis を使用して動作を確認していく。

※関数等にまとめた方がいいが今回は学習なので省略

package main

import (
	"fmt"
	"log"

	"github.com/garyburd/redigo/redis"
)

func main() {
	// redisの初期portは6379
	port := "127.0.0.1:6379"
	redisClient, err := redis.Dial("tcp", port)
	if err != nil {
		log.Fatalf("Failed to connect: %s", err.Error())
	}
	defer redisClient.Close()

	// SET: HOGEにVALUEを保存する
	// redisClient.Do("SET", KEY, VALUE)
	_, err = redisClient.Do("SET", "HOGE", "VALUE")
	if err != nil {
		log.Fatalf("Failed to set value: %s", err.Error())
	}

	// GET: HOGEからVALUEを取得する
    // redisClient.Do("GET", KEY)
    // 出力: VALUE
	value, err := redis.Bytes(redisClient.Do("GET", "HOGE"))
	if err != nil {
		log.Fatalf("data: %s", err.Error())
	}

	// 返り値は[]byte型なのでstringに変換して出力
	fmt.Println(string(value))

	// DELETE: keyのHOGEを削除する
	// redisClient.Do("DEL", KEY)
	_, err = redisClient.Do("DEL", "HOGE")
	if err != nil {
		log.Fatalf("DELETE: %s", err.Error())
	}

	// EXISTS: 存在を確認する
	// redisClient.Do("EXISTS", KEY)
	ok, err := redis.Bool(redisClient.Do("EXISTS", "HOGE"))
	if err != nil {
		log.Fatalf("EXISTS: %s", err.Error())
	}
	// HOGEは削除したのでfalseが出力される
	fmt.Println(ok)
}

list 型のコマンド

RPUSH

key に対応するリストにデータを挿入

LRANGE

key に対応するリストのデータを返す

LINDEX

key に対応するリストの指定した index のデータを返す

LREM

key に対応するリストの指定した value のデータを削除する

package main

import (
	"fmt"
	"log"

	"github.com/garyburd/redigo/redis"
)

func main() {
	// redisの初期portは6379
	port := "127.0.0.1:6379"
	redisClient, err := redis.Dial("tcp", port)
	if err != nil {
		log.Fatalf("Failed to connect: %s", err.Error())
	}
	defer redisClient.Close()

	// RPUSH:keyに対応するリストの先頭にデータを保存していく
	// redisClient.Do("RPUSH", KEY, VALUE)
	// リストが存在しなければ、データ挿入前にkeyに対応する空のリストを作成する
	_, err = redisClient.Do("RPUSH", "school", "一学期")
	_, err = redisClient.Do("RPUSH", "school", "二学期")
	_, err = redisClient.Do("RPUSH", "school", "三学期")
	if err != nil {
		log.Fatalf("RPUSH: %s", err.Error())
	}

	// LRANGE:keyに対応するリストのデータを返す
	// redisClient.Do("LRANGE", "school", start, stop)
	// 0を基準としたindexで取得するデータのインデックスの開始位置と終了位置を
	// 指定することによりデータを取得する。-1はリストの最後尾のデータを指定している
	value, err := redis.Strings(redisClient.Do("LRANGE", "school", 0, -1))
	if err != nil {
		log.Fatalf("LRANGE: %s", err.Error())
	}
	//[一学期 二学期 三学期]
	fmt.Println(value)

	// LINDEX:keyに対応するリストの指定したindexのデータを返す
	// redisClient.Do("LINDEX", key, index)
	value2, err := redis.String(redisClient.Do("LINDEX", "school", 1))
	if err != nil {
		log.Fatalf("LINDEX: %s", err.Error())
	}
	//二学期
	fmt.Println(value2)

	// LREM: keyに対応するリストの指定したvalueのデータを削除する
	// redisClient.Do("LREM", key, count, value)
	// count > 0ならば先頭から最初に一致したvalueを持つデータを削除
	// count < 0ならば後方から最初に一致したvalueを持つデータを削除
	// count = 0ならば全ての指定したvalueを持つデータを削除
	_, err = redisClient.Do("LREM", "school", 1, "一学期")
	if err != nil {
		log.Fatalf("LREM: %s", err.Error())
	}

	value3, err := redis.Strings(redisClient.Do("LRANGE", "school", 0, -1))
	if err != nil {
		log.Fatalf("LRANGE: %s", err.Error())
	}
	//[二学期 三学期]
	fmt.Println(value3)

	_, err = redisClient.Do("DEL", "school")
	if err != nil {
		log.Fatalf("DELETE: %s", err.Error())
	}
}

構造体を保存したい

データベースから取得できるデータをそのまま保存しておいて置けるのが理想。key を id(primary_key)にして value を json でデコードしたデータを保存することにより実装してみる。

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"strconv"

	"github.com/garyburd/redigo/redis"
)

// User 構造体
type User struct {
	ID     uint64 `json:"id"`
	Name   string `json:"name"`
	Number int    `json:"number"`
}

func main() {
	// redisの初期portは6379
	port := "127.0.0.1:6379"
	redisClient, err := redis.Dial("tcp", port)
	if err != nil {
		log.Fatalf("Failed to connect: %s", err.Error())
	}
	defer redisClient.Close()

	user1 := &User{ID: 1, Name: "Tosa", Number: 1}
	user1JSON, err := json.Marshal(user1)
	if err != nil {
		log.Fatalf("Failed to marshal user: %s", err.Error())
	}
	// 2020/08/22 18:39:49 user1_json :  {"id":1,"name":"Tosa","number":1}
	log.Println("user1_json : ", string(user1JSON))

	_, err = redisClient.Do("SET", "user"+strconv.FormatUint(user1.ID, 10), string(user1JSON))
	if err != nil {
		log.Fatalf("Failed to SET: %s", err.Error())
	}

	// user1
	fmt.Println("user" + strconv.FormatUint(user1.ID, 10))

	user, err := redis.Bytes(redisClient.Do("GET", "user"+strconv.FormatUint(user1.ID, 10)))
	if err != nil {
		log.Fatalf("Failed to get user data: %s", err.Error())
	}
	data := new(User)
	json.Unmarshal(user, data)
	// 2020/08/22 18:39:49 &{1 Tosa 1}
	log.Println(data)
}

これで一応構造体を redis に保存することができたが、id ごとに key を作らなくてはいけないのが不便、かついちいち json にパースしなくてはならないので、あんまり使い勝手が良くなさそう。redis にはハッシュ型があるので次回はそちらで実装してみたい。またデータを sort して保存して置ける sorted リストなどのもあるらしいのでそちらも調査していく予定です。

参考サイト

Mac に Redis をインストールする

Redis を CentOS7 に yum インストールする手順

examples-redigo

LPUSH のドキュメント