Go言語でデータをキャッシュしたい時に便利な『go-cache』の使い方

プログラムの動作を高速化し、CPU の負荷を下げたい時に使う手法の1つとして『キャッシュ』があります。

例えば、1から10を足し算して結果を表示するプログラムを作ったとしましょう。暗算でも出来るくらい簡単ですし、コンピューターを使えば一瞬ですよね。でも、1から1億まで、1兆まで…となると大変そうです。

更にその計算結果を何度も何度も要求されて、毎回1から順に足していって結果を表示していたら凄く時間がかかりますよね。そんな時、一度求めた結果をキャッシュしておき、次に要求された時にそのキャッシュデータを渡せば、計算し直さなくて済みますよね。

コンテンツの転載は固くお断りいたします。

当ブログサーバーでも使用している『キャッシュ』

当ブログサーバーのメインシステムは、Go言語で作っており『go-cache』というライブラリを使用してキャッシュ機能を用意しています。

ブログ記事は、要求されたアドレスから、どの記事を表示したら良いかを処理し、その記事のデータをディスクから読み込み、タイトルや公開日などの情報をデータベースから取得し、その記事に関連する記事一覧の項目を自動的に用意し、それらを1つのHTMLにまとめ、圧縮して配信しています。この処理に約0.06秒かかります。

しかし当ブログでは、一度圧縮して配信した記事データをキャッシュし、次に同じ記事にアクセスが来たらそのキャッシュデータを返しています。これで一度のアクセスを処理するのに約0.01秒まで短縮することが出来ています。

キャッシュしなくても速いじゃん!って思うかもしれませんが、このアクセス数が1000や1万を超えて来た時、大きな差が出て来て CPU の処理にも影響が出て来ます。

※2018年1月現在、ブログサーバーは GCP の GAE上で動作しており、キャッシュは『memcache』というものを利用しています。

キャッシュを新規作成し、データのセットと取得を行うコードのご紹介

Go言語では『go-cache』というライブラリを使用るすことで、簡単にキャッシュ機能を用意することが可能で、更に同時にキャッシュの内容を操作してもデータの内容が破壊されることのないスレッドセーフとなっています。


package main

import (
	"fmt"
	"time"

	cache "github.com/patrickmn/go-cache"
)

func main() {
	c := cache.New(1*time.Hour, 2*time.Hour)

	c.Set("gm", "おはようございます。", cache.DefaultExpiration)

	data, found := c.Get("gm")
	if found {
		fmt.Println("gm: ", data.(string))
	}

	data, found = c.Get("abc")
	if found {
		fmt.Println("abc: ", data.(string))
	}
}

必要なパッケージのインポートをする


import (
	"fmt"
	"time"

	cache "github.com/patrickmn/go-cache"
)

基本的なものは『fmt』と『time』パッケージで、go-cache を利用するために7行目にそれを記述しておきます。

キャッシュの新規作成


c := cache.New(1*time.Hour, 2*time.Hour)

『cache.New』関数でキャッシュの新規作成を行い、そのポインタを貰います。

引数は2つあり、1つ目でセットしたキャッシュデータの有効期間を指定します。今回は『1*time.Hour』なので1時間ですね。キャッシュをセットしてから1時間が経過すると、そのキャッシュデータは無効化されます。

2つ目で、有効期限が切れて無効化されているキャッシュデータを破棄(削除)する時間を指定します。キャッシュデータは無効化されてもメモリの中にまだ残っています。キャッシュデータをセットしてから、2つ目で指定した時間が経過すると無効化されたデータがメモリ上から削除されます。

キャッシュデータをセット


c.Set("gm", "おはようございます。", cache.DefaultExpiration)

『Set』関数でキャッシュにデータをセットします。

引数は3つあり、1つ目はそのキャッシュデータを操作したり取得するために必要な『キー(名前)』を指定しています。

2つ目はセットするデータです。今回は文字列を指定していますが、数値でもバイトスライスでも何でも指定が可能です。高速化したいなら、ポインタを指定すると良いようです。

3つ目は無効化されるまでの有効期間です。今回はデフォルト値を指定しておきます。

キャッシュデータを取得する


data, found := c.Get("gm")
if found {
    fmt.Println("gm: ", data.(string))
}

data, found = c.Get("abc")
if found {
    fmt.Println("abc: ", data.(string))
}

キャッシュからデータを取得する際には『Get』関数を使用します。

引数は1つだけであり、取得したいデータのキーをセットします。そのキーに対応したキャッシュデータを取得できるわけです。

戻り値は2つあり、1つ目はキーに対応したキャッシュデータです。2つ目は、指定したキーに対応するデータが見つかったどうかが返って来ます。見つかれば『true』ですね。

見つかったら『data.(string)』で1つ目の戻り値のキャッシュデータを文字列に変換しておきましょう。

あれ?と思った方もいると思いますが、Get 関数で返って来たものはそのままでは利用することができません。なので、そのキャッシュデータが文字列であるなら『data.(string)』int 型であるなら『data.(int)』として、文字列、数値として利用できるようにする必要があります。

キーは何でも指定できるが、無いものは見つからない

上記のコードで2つ目の Get をしていますよね。キーは『abc』となっていますが、そのようなキーのデータはセットしていないので『found』は『false』となり、データが表示されることはありません。

キャッシュデータの削除を行うコードのご紹介


c.Delete("gm")

この操作もとてもシンプルであり『Delete』関数に削除したいキャッシュデータのキーを指定して呼び出すだけです。

キャッシュ内の全てのデータを削除したい時のコード


c.Flush()

キャッシュ内の全てのデータを削除したい場合は『Flush』関数を呼び出しましょう。

キャッシュデータを全て取得するコード


items := c.Items()

キャッシュデータを全て取得し、ファイルの保存したりするのに使えそうです。ファイルに保存する関数として『SaveFile』や『Save』が用意されてはいるのですが、それらは非推奨であり、この『Items』を使うと良いようです。

返却されたものは『map[string]Item』型であり、Item とは以下のような構造体です。


type Item struct {
    Object interface{}
    Expiration int64
}

キャッシュデータと有効期限ですね。

map[string]Item を元にキャッシュを新規作成するコード


data := make(map[string]cache.Item)
data["gm"] = cache.Item{Object: "おはようございます", Expiration: 100}
data["ge"] = cache.Item{Object: "こんにちは", Expiration: 100}
data["gn"] = cache.Item{Object: "おやすみなさい", Expiration: 100}

c := cache.NewFrom(1*time.Hour, 2*time.Hour, data)

c.Flush()

先ほどご紹介した『Items』を使えば、キャッシュをどこかへ保存することが可能になります。それを元に再びキャッシュを用意したい場合は、上記のコードのように『NewFrom』関数を使うと良いでしょう。

まずはテスト用に、make を使って『map[string]cache.Item』を作成しておきます。そしてキャッシュデータを追加していきます。

2行目の最初に『data["gm"]』とありますが、ここでキーである『"gm"』を指定しています。そして、Item 構造体を初期化して追加します。『Object: "おはようございます"』でキャッシュデータの内容を指定し、『Expiration』で有効期限を指定しています。

そして『NewFrom』関数を使って、『data』を元にキャッシュを新規作成しています。

ファイル等からキャッシュを取得して新規作成する『Load』や『LoadFile』という関数もあるのですが、こちらも非推奨のようです。

終わりに

ここでご紹介した内容の他に、既にあるキャッシュデータを別のに置き換えしたり、キャッシュデータの数値に別の数値を加算したり、キャッシュデータがいくつあるのかカウントする関数などもあります。

詳しくは『go-cache の godocページ』で確認できますので、そちらも読んでみてください。