2013年3月22日 星期五

實作 MongoDB 的 tail 指令

當資料放在 MongoDB 上時,有時會想要用類似 Linux 的 tail 指令來觀察新增的資料的狀態~
這時可以利用以下的 script 來達成!

這裡假設 Collection 的名稱是 Coll,裡面的每筆資料(每個文件)都有 time 跟 message 兩個欄位。

tail.js
// Get the started position.
var start_index = db.Coll.find().count()-10;

while(true) {
  // 
  if(db.Coll.find().count() >= start_index) {
    var index = start_index - 1;
    // Get the next document with ignoring previous old messages.
    var rec = db.Coll.find().skip(index);
    print(rec[0].time + " | " + rec[0].message);
    start_index++;
  }
  // Sleep for 100 ms.
  sleep(0.1);
}

function sleep( milliseconds ) {
  var timer = new Date();
  var time = timer.getTime();
  do
    timer = new Date();
  while( (timer.getTime() - time) < milliseconds );
}

sleep 部分的 JavaScript 取自 [3]。

tail.sh
{MONGO_PATH}/bin/mongo --quiet localhost:27017/{DB_NAME} ./tail.js
其中 {MONGO_PATH} 是 MongoDB 的路徑、{DB_NAME} 是使用的資料庫名稱。
(假設 MongoDB 沒有更改連接埠,因此連線對象是 localhost: 27017)

這個寫法雖然可以動,不過已知會有幾個問題:
1、目前寫法是 sleep 0.1 秒後只會拿出一筆資料,而沒有辦法一次一整沱一起印出來
2、因為 MongoDB 的 script 好像不支援 window,所以沒有 setTimeout() 可以使用,只能用應該很浪費系統資源的迴圈來達到 sleep 的效果。

第一點可能有一些方法可以解決,例如把 while 迴圈改成以下的寫法:
while(true) {
  if(db.Coll.find().count() >= start_index) {
    var index = start_index - 1;
    // Get the next document with ignoring previous old messages.
    db.Coll.find().skip(index).forEach(function (rec) {
      print(rec.time + " | " + rec.message);
      start_index++;
    });
  }
  sleep(100);
}
每次 find() 時,都會把現在剩下的所有資料都印出,然後才會 sleep,可以讓訊息更即時一點出現。
如果想要減少 I/O 次數,也許還可以考慮 print 改成暫存在某個變數裡,等到迴圈結束要 sleep 之前才一次印出。

第二點目前還不知道有沒有其他更好的 sleep 寫法,所以暫時不解決。

參考資料:
1、mongodb: how to get the last N records?
2、MongoDB: Create Tailable Cursor
3、[小筆記] Javascript 中實現 sleep 的方法

沒有留言: