Skip to content

앞서 Cursors를 다룬 포스팅에서 도큐먼트 쿼리 시 skip을 통해 도큐먼트를 건너뛰는 것에 대해 알아본 바 있습니다.

skip 연산자는 작은 규모의 도큐먼트에 대해서는 적당할 지 모르지만, 도큐먼트 규모가 커지면 건너뛰는 속도가 현저히 떨어집니다.

이것은 MongoDB만의 문제가 아닌 거의 모든 데이터베이스에서 공통적으로 나타나는 현상입니다.

따라서, 큰 규모의 도큐먼트에서 도큐먼트를 건너뛰어 검색 결과를 받아올 때 skip은 반드시 피해야 합니다.


skip 없이 결과 표시하기

다음과 같이 1 ~ 30000까지의 cnt 값을 가지는 도큐먼트를 생성해 보도록 하겠습니다:

for(i=1; i<30001; i++) {
    db.count.insert({cnt : i});
}

만약 20000개의 도큐먼트를 건너뛰려면,

var myCursor = db.count.find().skip(20000);

대신 다음과 같이 $gt 연산자를 사용하는 것이 훨씬 빠른 속도를 보일 것입니다 (상황에 따라 속도차이를 거의 못 느낄 수도 있습니다).

> var myCursor = db.count.find({cnt: {$gt: 20000}});
> myCursor
{ "_id" : ObjectId("52f38fff2d911bccacf26913"), "cnt" : 20001 }
{ "_id" : ObjectId("52f38fff2d911bccacf26914"), "cnt" : 20002 }
{ "_id" : ObjectId("52f38fff2d911bccacf26915"), "cnt" : 20003 }
{ "_id" : ObjectId("52f38fff2d911bccacf26916"), "cnt" : 20004 }
{ "_id" : ObjectId("52f38fff2d911bccacf26917"), "cnt" : 20005 }
{ "_id" : ObjectId("52f38fff2d911bccacf26918"), "cnt" : 20006 }
{ "_id" : ObjectId("52f38fff2d911bccacf26919"), "cnt" : 20007 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691a"), "cnt" : 20008 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691b"), "cnt" : 20009 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691c"), "cnt" : 20010 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691d"), "cnt" : 20011 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691e"), "cnt" : 20012 }
{ "_id" : ObjectId("52f38fff2d911bccacf2691f"), "cnt" : 20013 }
{ "_id" : ObjectId("52f38fff2d911bccacf26920"), "cnt" : 20014 }
{ "_id" : ObjectId("52f38fff2d911bccacf26921"), "cnt" : 20015 }
{ "_id" : ObjectId("52f38fff2d911bccacf26922"), "cnt" : 20016 }
{ "_id" : ObjectId("52f38fff2d911bccacf26923"), "cnt" : 20017 }
{ "_id" : ObjectId("52f38fff2d911bccacf26924"), "cnt" : 20018 }
{ "_id" : ObjectId("52f38fff2d911bccacf26925"), "cnt" : 20019 }
{ "_id" : ObjectId("52f38fff2d911bccacf26926"), "cnt" : 20020 }
Type "it" for more

랜덤 도큐먼트 검색하기

컬렉션으로부터 랜덤 도큐먼트를 얻는 방법 중 가장 단순하면서도 무식한(?) 방법은 전체 도큐먼트 수를 파악하고 이 범위 안에서 랜덤 수를 발생시켜 그 수만큼 도큐먼트를 skip하는 것입니다.

다음 예를 살펴보도록 합니다:

for(i=1; i<1001; i++) {
    db.myCollection.insert({cnt : i});
}
var total = db.myCollection.count();
var random = Math.floor(Math.random()*total);
db.myCollection.find().skip(random).limit(1);

위의 코드는 1000개의 도큐먼트를 생성하고, 도큐먼트의 전체 개수를 파악하여 전체 개수의 범위에 대한 랜덤 수를 발생시킨 후, 그 수만큼 skip한 후 1개의 도큐먼트를 얻어왔습니다.

그러나, 이 방법은 도큐먼트 수가 매우 많을 때에는 속도가 현저히 느려지므로 좋은 방법이 아닙니다. 일반적으로 사용하는 랜덤 도큐먼트 검색은 약간의 트릭이 숨어있습니다.

위의 myCollection에는 키(key) 값이 cnt 뿐인데, 각 도큐먼트에 랜덤 번호를 추가하여, 랜덤 수를 발생하여 그 수보다 작거나 큰 하나의 도큐먼트를 찾는 방법이 훨씬 효율적입니다.

다음 예를 통해 보다 효율적인 skip 방법을 알아보도록 하겠습니다:

// myCollection에 "random" 키를 설정하고 랜덤수를 저장한다.
for(i=1; i<1001; i++) {
    db.myCollection.update({cnt: i}, {$addToSet: {random: Math.random()}});
}

// 랜덤수를 하나 발생시키고 그 값보다 큰 도큐먼트 1개를 찾는다.
var myRandom = Math.random();
result = db.myCollection.findOne({random : {$gt : myRandom}});

// 만약 "result"에 아무값도 저장되지 않으면, 발생된 랜덤수보다 작은 도큐먼트 1개를 찾는다.
if (result == null) {
    result = db.myCollection.findOne({random : {$lt : random}});
}

코드 설명은 다음과 같습니다: 랜덤수를 하나 발생시키고 그 값보다 큰 도큐먼트 1개를 찾는 경우, 발생된 랜덤수보다 큰 random 값을 가지고 있는 도큐먼트가 없을 확률도 있습니다.

이를 방지하기 위해 result에 아무 값도 저장되어 있지 않다면(result == null) 좀전에 발생한 랜덤수보다 작은 도큐먼트 1개를 찾습니다.