安い！うまい！速い！効果抜群 凄いぞrubyでのキャッシュ化による速度向上

ここ1年で一番感動した、キャッシュの効果凄いな！という話です。
今回はrubyで実現してるのでrubyとつけてますが、色々な言語でも適用可能だと思います。

##**とある問題。 rubyで作ったcgiのレスポンスが遅すぎる**

とあるシステムで、フォルダを検索して各フォルダの設定値一覧をUIを作りました。
サーバー側はcgi(コマンド)として処理を実行し、結果をJSON形式で返すという形。

実現方法としてはrubyを使い、

各フォルダのJSONフォーマット設定値を読み込み
⇒レスポンス用のHashを成形
⇒JSONフォーマットに変換して送信

という流れだったんですがこれが遅い遅い。
フォルダ数が膨大な数になると、応答返すまで数分レベルになってしまいました。
概算ではいけそうだったんだけど甘かった(-_-;)

###**原因と対処の検討**

ruby上での1フォルダ1フォルダの設定値open/close時間を計測すると、1open/closeが極めて遅いわけではないけど、フォルダ数が万単位になったらこうなる。
**純粋に必要な情報を集めるのに時間がかかる**

しかもこのフォルダ数は設定値の組み合わせ的に十分あり得る数。
つまり、今の仕組みでは限界。なので何か手を入れることに

速度は限界なので、何かしらの方法で**レスポンスのキャッシュ**するしかない。
実現方法はどうしよう

###**レスポンスキャッシュ方法**

対処について最初に出した草案は2つ。

1. キャッシュサーバーを作る
2. レスポンス用のキャッシュファイルを作る。

草案考えてた時点では、2はキャッシュファイル管理が煩雑になるな～と思っており、
やるなら1かなと思っていました。

作る規模はちょっとかかるけど、システム設計上も問題なく作れそう。
じゃあ実際に対処を考えますか。許される期間は？
1日。…

はい、無理なので案2で考えます。。果たして綺麗に行くのだろうか

####**キャッシュファイルについて整理**

冷静にシステムの状況を考えてみる。

1. このHTTPサーバーはシングルスレッド
2. レスポンス用のデータはHash構造
3. 時間がかかるのは最初のキャッシュ
4. レスポンス状態が変更されるケースがある

1⇒cgiアクセスによる排他制御は考えなくていい。
　　ってことはキャッシュファイルはコマンド名やquery毎に作ればいいのか。あれ？わかりやすいぞ

2.⇒rubyにはJSONファイル⇔Hash/Array構造を変換する処理が標準である。
　　ってことはレスポンス用データ=そのままキャッシュするファイルでいいじゃん。

3.⇒ってことは、起動時にキャッシュを作ってやればいいな。
　　getでキャッシュが作れるようにしておけば、起動時に1発getしとけばいいぞ！

4.⇒これは更新の仕組みがいるな。でもキャッシュ情報ベースだからそんなに大変ではなさそう。
　　　最悪キャッシュファイルを消してgetすればいいか。

…あれ？簡単だぞキャッシュ化


####**対処 キャッシュファイル化**

**修正前:**元々の実装はこんな感じでした。

```ruby:CgiActer.rb
class CgiActer
	def getResponse(query)
		#queryパース
		responseData = createRespnse(input1, input2)
		return responseData
	end
end
```

**修正後:**`CacheManager`クラスを追加し、こんな感じに変更。

```ruby:CgiActer.rb
class CgiActer
	def initialize ()
		@cache_mng = CacheManager.new()
	end

	def getResponse(query)
		#queryパース
		responseData={}

		#キャッシュがあるかを確認。あるならキャッシュを取得。なければ既存処理 && キャッシュ保存
		if @cache_mng.isCached(input1, input2) then
			responseData = @cache_mng.getCache(input1, input2)
		else
			responseData = createRespnse(input1, input2)
			@cache_mng.writeCache(input1, input2, responseData)
		end

		return responseData
	end
end
```

`CacheManager`クラスはこんなんです。

```ruby:CacheManager.rb
require 'json'
class CacheManager
	CACHEFILE_NAME = "cachefile.json"

	#ファイル名取得
	def getName(input1, input2)
		return ".#{input1}_#{input2}_#{CACHEFILE_NAME}"
	end

	#ファイルチェック
	def isCached(input1, input2)
		#ファイルがあるか見るだけ
		return File.exist?(getName(input1, input2)))
	end

	def getCache(input1, input2)
		#JSON.parseしてHash化するだけ
		responseData ={}
		File.open(getName(input1, input2), "r") {|f|
			responseData = JSON.parse(f.read())
		}
		return responseData
	end

	def writeCache(input1, input2, resdata)
		#JSON.形式にしてwriteするだけ
		File.open(getName(input1, input2), "w") {|f|
			f.write(JSON.pretty_generate(resdata))
		}
	end
end
```

シンプルな`CacheManager`クラスを追加するだけ。サクッと実装出来ました。

構成次第では`createRespnse`ごと`CacheManager`に移動すれば、`CgiActer`は常に`createRespnse`を呼ぶだけで実現可能ですね。
(Flyweightパターンだな)

実装後のレスポンス速度も1cgi requestに**数秒⇒300msecの高速化に成功**。
簡単なのに凄い！

##**もう一つ問題。ファイルを増やすと起動が遅すぎて動かない！**

さっきの対処でどんなデータでもcgiの応答は早くなりました！
ということでやっとシステム試験に入ることに。

今回の件もあるので大きなデータも交えた構成にして試験。
…したところ、サーバーの起動が遅すぎて動かない。なぜだ！

###**原因と対処の検討**

似たような理由がCで作ったサーバー側にもありました。

ファイルサイズをfopen/サイズ読み込み、サイズとファイルの種類を取得/fcloseの繰り返しで測ってるんですが、対象ファイル量が多すぎて起動が終わらない…
10分弱かかってやっと起動。ほかのプロセスが待ちきれずにabort。

またか。でもこちらにはキャッシュ化があるぜ！

###**キャッシュ化 Cを交えた場合**

時間がないので、ファイルパースが苦手なCさんではなく、rubyさんを利用してキャッシュ化。

まずfopen/サイズ読み込み/fclose部分をまるっと新しいmain.cにコピーしてコマンド化。

こんな感じの元ソースから

```C:original_file.c
//既存関数
void get_filesize(char *file, int *size, int * type) {
	//ファイル読み込み処理
}

void read_allfile () {

	while(file 全てのファイルを) {
		int size, type;
		get_filesize(ファイル名, &size, &type);
		//size, typeをstore
	}
}
```

こんなコマンドを作りました。

```C:new_command.c
//既存関数
void get_filesize(char *file, int *size, int * type) {
	//ファイル読み込み処理
}

int main(int argc, char *argv) {

	char *fname = argv[1];
	int size, type;
	get_filesize(fname, &size, &type);
	printf("[%d,%d]", size, type);
}

```


ファイル名を指定されたらrubyのArray形式で結果を表示するようにします。

後はrubyさんが全ファイルに対してファイル情報を取得。得意のJSON形式でファイル情報をキャッシュします。
ファイル追加時もrubyさんがキャッシュ更新。
rubyは`CacheManager`みたいな感じなので省略。

起動時はC側からrubyを実行。
rubyさんに得意のJSONファイルパースをしてもらい、変換したArrayを出力してもらいます。
自然と
[1,1]
[2,2]
[3,3]
みたいな[]のリストに成形してくれるので、C側は何も考えずにfgets/sscanfで取得します。

こんな感じ

```C:original_file.c
void read_allfile () {

	int size, type;
	char result[64]={0};
	FILE *fp = popen("rubyのコマンド", "r");

	while(fgets(result, 64, fp)) {
		int size, type;
		sscanf(result, "[%d,%d]", &size, &type);
		//size, typeをstore
	}
	pclose(fp);
}
```

簡単修正でサクッと対処後、実行。
**10分弱⇒1秒かからずの超高速化に成功**しました！

凄いぞキャッシュ！

##**まとめ 安い！うまい！速い！凄いぞキャッシュ化**

キャッシュ化、こんなに簡単で効果のあるものだとは思っていませんでした！
感想として素晴らしさを3点で

**安い！**
⇒工数的な意味で。今はファイルパーサーが用意されてる言語が沢山あるので、
　うまくはまれば使いたいデータのファイル化は簡単！

**うまい！**
⇒基本getの処理へのテコ入れと、新規のキャッシュ処理追加。
　影響範囲が少なく、導入の為にああだこうだシステム設計を変えず、すっきり導入可能！

**速い**
⇒ケースに寄るでしょうが、今回は適用後の速度改善が本当に凄い！

今回の例はruby×JSONでしたが、その言語の得意なファイルパース処理を用いて、
簡単にキャッシュ化、高速化が実現できるはず！