Firebaseでindexを貼るとどのくらいソートが速くなるのか

More than 1 year has passed since last update.

データベースに入っている要素を何かの数値でソートして取得したりしたくなることはすごくよくあります。

そういうことをMySQLとかでやる場合、その要素にindexというものを貼って、ソートを高速化したりするものですが、FirebaseでなんちゃってDB操作を楽しんでいるフロントエンドの人(私もそんなのですが)は、そういうのよく知りません。

しかし、Firebaseにもindex機能はちゃんとあって、しかも結構いい感じなのですが、あんまりこのあたりのことを書いている日本語文献が無かったので書いてみます。

とりあえず、indexによる高速化を実感するためにはそれなりの量のデータが必要かと思うので、jsで下記のようなコードを書いて、なんかオブジェクトを20000個くらいつくります。


bigdata.js

var MAX_NUM = 20000;

for(var i=0; i<MAX_NUM; i++){
//各オブジェクトの名前を適当にランダムで生成
var _name = Math.random().toString(36).slice(-16);
//数値をランダムで生成
var _num = Math.floor(Math.random()*1000000);

//追加するオブジェクトのデータを用意する
var postData = {
name: _name,
num: _num
};
//オブジェクトを追加する
var newPostKey = firebase.database().ref().child('Objects').push().key;
var updates = {};
updates['/Objects/' + newPostKey] = postData;
firebase.database().ref().update(updates, function(error) {
if (!error) {
_firebase.raiseEvent('dataIncreaseComplete',[]);
}
});
}


こんな感じのコードを実行すると、下記のような感じで大量でランダムな数値がくっついたデータが生成されます。これで準備完了です。

スクリーンショット 2016-12-22 15.17.43.png

データを取得するクライアント側は何で書いても良いのですが、今回は、Objective-Cで実験します。

下記の手順でセッティングをしつつ、

Firebase を iOS プロジェクトに追加する

podは、

pod 'Firebase/Core'

pod 'Firebase/Database'

の2つを仕込んでおきます。

そして、まず、indexとか何にも設定しないで下記を走らせてみます。

この時点のruleは下記のような感じです。

{

"rules": {
"Objects": {
".read": true,
".write": true
}
}
}

オブジェクト数は20,000個。最初の500個だけを取ってきます。


ViewController.m

#import "ViewController.h"

@import Firebase;
@import FirebaseDatabase;

@interface ViewController ()

@end

@implementation ViewController{
FIRDatabaseReference *rootRef;
NSDate *startDate;
NSDate *endDate;
}

- (void)viewDidLoad {
[super viewDidLoad];

rootRef= [[FIRDatabase database] reference];
// Do any additional setup after loading the view, typically from a nib.
FIRDatabaseReference *objects = [rootRef child: @"Objects"];

startDate = [NSDate date];

[[objects queryOrderedByChild:@"num"] queryLimitedToFirst:500] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot){

endDate = [NSDate date];
NSTimeInterval interval = [endDate timeIntervalSinceDate:startDate];
NSLog([NSString stringWithFormat:@"処理時間 : ミリ秒 : %f",interval]);
//データの中身を確認する場合、下記を実行
/*
for (FIRDataSnapshot* childSnap in snapshot.children) {
NSLog(@"%@", childSnap.value[@"name"]);
NSLog(@"%@", childSnap.value[@"num"]);
}
*/

}withCancelBlock:^(NSError *error){
}];

}

- (NSString*)getDateString:(NSDate*)date
{
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss.SSS"];
NSString *dateString = [dateFormatter stringFromDate:date];
return dateString;
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

@end


時間計測するやつは、こちらからお借りしました。

5.973116秒で処理が終わりました。何回やってもこのあたり。

ログで内容を確認すると、ちゃんとnumが小さいやつから順番に書き出されていますが、それなりに遅いです。

というわけで、今度はindexを貼ったらどれだけ速くなるのかをやってみます。

今回は"num"の値でソートするので、Firebaseのコンソールから、ruleを下記みたいな感じにします。

{

"rules": {
"Objects": {
".read": true,
".write": true,
".indexOn": ["num"]
}
}
}

これだけで、"num"に対するindexが貼られました。

クライアント側のコードは変えなくて良いです。

これで再度Firebaseのレスポンスを計測してみます。

0.659976秒。すばらしい。速いです。

indexという概念は、データベースを普段から触っている人には当たり前の概念ですが、特にFirebaseはバックエンドのことあんまりよくわからなくてもフロントのノリでどうにかするための仕組みなので、結構知らない人が多いのではないかと思います。

とはいえ、改めて試してみたらわかりやすく高速だったので、こういうことをする際は、indexを貼らない手はないかと思いました。

コードは下記です。

データ増殖するやつ

Firebase-dataIncrement

iOS-app

Firebase-indextest