MongoDB의 복합 인덱스로 구성된 shard key 범위를 나타내는 min/max로 그 범위 내에서 query하는게 의외로 쉽지 않음.

그냥 일반적인 나열로는 안되고, shard key 순서대로 조건을 중첩해야 가능함.

 

예를 들어 아래와 같은 shard key가 있다고 가정.

shard_key = {
    x: 1,
    y: 1,
    z: 1
};

 

 

그리고 chunk의 min/max가 아래와 같다고 가정.

min = {
    x: 1,
    y: 'zbc',
    z: 99
};
max = {
    x: 9,
    y: 'aaa',
    z: 1
};

 

 

단순하게 생각해서 저 범위내로 query 한다고 아래처럼 하면 y부터 말이 안됨.

db.col.find({
    x: {$gte:1, $lte: 9},
    y: {$gte:'zbc', $lte:'aaa'},
    z: {$gte:99, $lte:1}
})

 

 

그래서 아래처럼 각 단계별로 조건을 중첩하는 로직이 필요함.

db.col.aggregate([
    // 일단 첫번째 key로 필터링
    {
        $match: {
            x: {
                $gte: 1,
                $lte: 9
            }
        }
    },
    // min 필터링 (중첩 조건)
    {
        $match: {
            $or: [
                {
                    x: {$gt: 1}
                },
                {
                    $and: [
                        {x: 1},
                        {
                            y: {$gte: 'zbc'}
                        },
                        {
                            $or: [
                                {y: {$gt: 'zbc'}},
                                {
                                    y: 'zbc',
                                    z: {$gte: 99}
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    },
    // max 필터링 (중첩 조건)
    {
        $match: {
            $or: [
                {
                    x: {$lt: 9}
                },
                {
                    $and: [
                        {x: 9},
                        {
                            y: {$lte: 'aaa'}
                        },
                        {
                            $or: [
                                {y: {$lt: 'aaa'}},
                                {
                                    y: 'aaa',
                                    // chunk의 min은 '포함'이고, max는 '비포함'이므로 $lt
                                    z: {$lt: 1}
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    }
])

 

 

 

shard key의 depth가 늘어날수록 훨씬 복잡해지기 때문에 min, max를 넣으면 범위를 지정할 수 있는 pipeline을 반환해주는 함수 제작.

function getPipeline(min, max)
{
    const keys = Object.keys(min);
 
        const pipeline = [{
        $match: {
            [keys[0]]: {
                $gte: min[keys[0]],
                $lte: max[keys[0]]
            }
        }
    }];
 
    function createMatchConditions(q, isMin)
    {
        const keys = Object.keys(q);
        const operator = isMin ? '$gt' : '$lt';
        let eqOperator = isMin ? '$gte' : '$lte';
 
        const conditions = {
            $and: [
                {[keys[0]]: q[keys[0]]},
                {[keys[1]]: {[eqOperator]: q[keys[1]]}}
            ]
        };
 
        function getNextCondition(idx)
        {
            if (idx >= keys.length) {
                return [];
            }
 
            const key = keys[idx];
 
            const nextCondition = {
                $or: [
                    {[key]: {[operator]: q[key]}}
                ]
            };
 
            const nextIdx = idx + 1;
 
            if (nextIdx >= keys.length - 1) {
                if (!isMin) eqOperator = '$lt';
 
                nextCondition.$or.push({
                    [key]: q[keys[idx]],
                    [keys[nextIdx]]: {[eqOperator]: q[keys[nextIdx]]}
                });
            }
            else {
                nextCondition.$or.push({
                    [key]: q[keys[idx]],
                    ...getNextCondition(nextIdx)
                });
            }
 
            return nextCondition;
        }
 
        const nextCondition = getNextCondition(1);
 
        conditions.$and.push(nextCondition);
 
        return conditions;
 
    }
 
    pipeline.push({
        $match: {
            $or: [{
                [keys[0]]: {
                    $gt: min[keys[0]]
                }
            },
            createMatchConditions(min, true)]
        }
    });
 
    pipeline.push({
        $match: {
            $or: [{
                [keys[0]]: {
                    $lt: max[keys[0]]
                }
            },
            createMatchConditions(max, false)]
        }
    });
 
    return pipeline;
}
 
 
// 상단에 x,y,z 로 구성된 min, max를 넣고 호출하면 필요한 pipeline이 나옴
const pipeline = getPipeline(min, max);
 
// document count
pipeline.push({ $count: 'document_count' });
db.col.aggregate(pipeline);
 
// 결과값
[ { document_count: 36037 } ]
 
// chunk 내에서 N번째 document를 확인하려면 이런식으로
const pipeline = getPipeline(min, max);
pipeline.push({$sort:{x:1, y:1, z:1}});
pipeline.push({$skip: N});
pipeline.push({$limit: 1});

 

 

 

 

Posted by bloodguy
,