2012年11月1日 星期四

測試 MongoDB 的效能

設定了一個情境,假設我有 100 萬個檔名不同的檔案,隨機放在以下八個資料夾

/user_c/
/user_c/application/
/user_a/log/user_c/
/user_a/log/webserver/
/user_b/
/user_b/application/
/user_b/log/
/user_b/log/webserver/

那麼當我想要知道 /user_c/ 這個資料夾有哪些檔案和資料夾時,MongoDB 可以多快幫我找出來?

首先是隨機產生資料,我用隨機產生的 UUID 產生一百萬個不重複的 UUID 來當做檔名
然後用 Random 隨便決定一個目錄來放這個檔名,產生的資料先暫存在 ArrayList 上。
程式碼如下:
String[] basicPathArray = {
  "/user_c/",
  "/user_c/application/",
  "/user_a/log/user_c/",
  "/user_a/log/webserver/",
  "/user_b/",
  "/user_b/application/",
  "/user_b/log/",
  "/user_b/log/webserver/"
};

int length = 1000000;

// Initial list.
ArrayList<String> parentList = new ArrayList<String>(length);
ArrayList<UUID> uuidList = new ArrayList<UUID>(length);

// Create path list.
Random rand = new Random();
HashMap<UUID, Boolean> maps = new HashMap<UUID, Boolean>();
for(int i=0 ; i<length ; i++) {
  // Create UUID
  UUID uuid = UUID.randomUUID();
  while(maps.containsKey(uuid))
    uuid = UUID.randomUUID();
  // Record the UUID
  maps.put(uuid, true);
  // Add to list
  parentList.add(basicPathArray[rand.nextInt(basicPathArray.length)]);
  uuidList.add(uuid);
}

插入資料時,將檔名與路徑分開存放,這樣在處理時應該可以加快取出資料的速度。

// Connect to MongoDB.
Mongo mongo = new Mongo();
DB db = mongo.getDB("mongo_test");
DBCollection dbColl = db.getCollection("performance");

for(int i=0 ; i<parentList.size() ; i++) {
  // Construct resource
  dbo = new BasicDBObject();
  dbo.append("parent", parentList.get(i));
  dbo.append("name", uuidList.get(i).toString());
  // Insert
  dbColl.insert(dbo);
}

因為要測試索引的效果,因此要設定索引。
// Create index
BasicDBObject indexDbo = new BasicDBObject();
indexDbo.append("parent", 1);
dbColl.ensureIndex(indexDbo);

搜尋資料時,我的設定是搜尋路徑開頭是 /user_c/ 的資料,也就是目標會搜尋出所有 /user_c/ 的檔案和資料夾。
這裡用的搜尋方法是用 Regular Expression 搜尋以 /user_c/ 作為開頭的資料
即使用 {"parent": {"$regex" : "^/user_c/"}} 作為搜尋的參數。

BasicDBObject dbo = new BasicDBObject();
dbo.put("parent", Pattern.compile("^" + basicPathArray[0]));
cursor = dbColl.find(dbo);
while(cursor.hasNext()) {
  DBObject obj = cursor.next();
}

這裡比較特別的地方是,因為我最終是需要所有以 /user_c/ 作為開頭的資料
因此衡量時我是直接衡量 find() 指令的時間加上尋訪所有找到的文件的時間。

最後得到的結果如下:
Find /user_c/ size: 250239

Test insert and search without index
Insert spent: 34476ms
Find /user_c/: 1524ms
---------------------------------------
Test insert and search with index
Insert spent: 37760ms
Create index: 19226ms
Find /user_c/: 1211ms

插入資料的時間其實顯示出來沒什麼意義因為兩者插入資料的方法是一樣的 XD
(我的索引是在資料插入完以後才建索引 XD)
不過可以看出在 100 萬筆資料下面,建索引花了 19 秒的時間~
而搜尋以 /user_c/ 開頭的路徑,未建索引是 1.524 秒、建了索引是 1.211 秒。

接著再做第二次測試,這次把建索引提前到插入之前先建,來看看建了索引以後才插入資料的時間差異。
Find /user_c/ size: 250142

Test insert and search without index
Insert spent: 33597ms
Find /user_c/: 6658ms
---------------------------------------
Test insert and search with index
Create index: 252ms
Insert spent: 63218ms
Find /user_c/: 1222ms

這次可以明顯看出,有了索引之後,插入的速度會變慢~測了幾次,平均來說變慢的幅度大概也在一倍以內
而這次搜尋的結果則是呈現有索引比沒有索引快三到四倍。
其實每次跑數據都不太一樣,但以搜尋來說,有索引通常會快 30%~300% 不等的程度
一般好像落在大概 70% 上下比較多吧(純感覺 XD)。

參考資料:
1、MongoDB与内存

沒有留言: