えいのうにっき

主に Web 系技術ネタ。背景画像 is powered by grass-graph.shitemil.works

巨大な情報のアップロードに対応しました&Datastoreにオブジェクトを圧縮して登録する

こんにちは、a-knowです。
今日のはCountDown iTunesのfor Webの方のことなんですが、従来まではできていなかった巨大な情報のアップロード&登録ができるようになりましたので、お知らせさせて頂きます。


CountDown iTunes!! for Web


既にプログラムは差し替えています。CDiTのクライアントソフト側はそのままのものでOKです。今までに「アップロードしようとしている情報が大きすぎます」エラーが発生した方で、もしまだここを見ていただけている方がおられましたら、是非もう一度お試し頂ければウレシイです。

どのような対策を打ったか?

ここからは少し技術的なハナシになってしまいますが・・・。
何度もご紹介している通り、このforWebはGoogle App Engineを利用して運用してみています。
Google App Engine上でデータを保管しようと思ったら、全てDatastore(BigTable)に格納する形で保管しなければならないんですが(データのファイル化は不可)、このときの情報の大きさの制限が1MBなんですね。

CDiTは、利用者のPC上で動作するCDiTもforWebも、各期間での情報を「曲別ランキング情報」「アルバム別ランキング情報」「アーティスト別ランキング情報」の3つの情報を作って、それを1セットとして保存する形式を取っています。このうち、「曲別ランキング情報」のサイズが特にデカい!まぁ、iTunesのライブラリに登録されている全曲の情報を集約したものになりますから、ムリはないです。例えば、「まぁまぁ音楽好きで、そこそこ色んな音楽を聴く」a-knowで、だいたい500〜600KBになります。この時点で、GAEの上限に対して既に半分以上の割合を占めてるわけですから、そもそもGAEにそのまま適用するにはムリがあったデータ構造だったわけなんですが・・・。。(リリースを焦るあまり、見切り発車してしまいました。。)
で、案の定、月に2,3回の頻度で「アップロードしようとしている情報が大きすぎます」エラーが出てるような状況で。やっぱり自分よりもたくさんの音楽を聴く方はいるんだな(当たり前)ということで、今回の対策と相成りました。


1MBを超える、大きすぎる情報を格納するための方法の結論として考えた対策はもう簡単なもので、情報を登録する際に圧縮をかけてやる、というものです。
調べたところ、Javaで情報を圧縮した結果それをファイルに出力する、という情報は多く見つけられたのですが、今回やりたかったことのように、情報がセットされたオブジェクトを直列化し、それを圧縮する、といったことの情報はあまり見受けられなかったので、ここでもまとめておこうかと思います。
もっと良い書き方はあるかと思いますが、どなたかのお役に立てれば。

//格納したい情報(ArrayList<Element> sortedListBySongPlayCount)を圧縮してBlob型にするサンプル

ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = null;

ZipOutputStream zip_out = new ZipOutputStream(baos1);
zip_out.putNextEntry(new ZipEntry("zipped_entry"));//"zipped_entry"は別になんでもいい

oos1 = new ObjectOutputStream(zip_out);
oos1.reset();
oos1.writeObject(sortedListBySongPlayCount);//シリアライズされたオブジェクトを書き出す
oos1.flush();
zip_out.closeEntry();
zip_out.close();
baos1.close();

this.sortedListBySongPlayCount = new Blob(baos1.toByteArray());//Blobにする
//Blob型として格納された圧縮情報から元の情報(ArrayList<Element> sortedListBySongPlayCount)を取り出すサンプル。
//エンティティのアクセサ(getter)の形式で書いています。

public ArrayList getSortedListBySongPlayCount() {

	//ここでsortedListBySongPlayCountは、このエンティティのBlob型フィールド。
	ByteArrayInputStream bais = new ByteArrayInputStream(sortedListBySongPlayCount.getBytes());
        ObjectInputStream ois = null;
	try {
		//今回のバージョンから圧縮をしだしたので。以前のバージョンで保存された場合は展開せずに読めるように。
		if(zipped != null && zipped.booleanValue()){
			ZipInputStream zip_in = new ZipInputStream(bais);
			zip_in.getNextEntry();
			ois = new ObjectInputStream(zip_in);
		}else{
			ois = new ObjectInputStream(bais);
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
        Object o = null;
	try {
		o = ois.readObject();//書き出されたシリアライズオブジェクトを復元
	} catch (IOException e) {
		e.printStackTrace();
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}

	return (ArrayList)o;
}


ほんとだったら、アクセサにこんなロジックは入れるべきではないかと。


今回の対策で、例えば僕のだったら500〜600KBだった曲別ランキング情報がおよそ十分の一、50〜60KBにまでなりました。やったね!
とりあえずはこれで、しばらくは様子を見たいと思います。

16:12 追記

今回は単に登録するデータを圧縮しただけですが、当然、対象データを分割する方法も考えられると思います。
twitterでもフォローさせて頂いているshin1ogawaさんから、そのことについてコメントでアドバイス頂きました〜。
今回の僕のケースよりも巨大なデータを格納しなければならないときには、分割した方が安心ですね!

22:13 追記

なおも「アップロードしようとしている情報が大きすぎます」エラーが発生しているようです。ご迷惑をお掛けしております。現在原因を調査中です。今しばらくお待ち下さい。

22:46 追記

残りの2つの情報、「アルバム別ランキング情報」「アーティスト別ランキング情報」の圧縮も行うように対応致しました。この状態で再度お試し頂ければと思います。