Computer >> 컴퓨터 >  >> 프로그래밍 >> MongoDB

MongoDB에서 $facet 연산자로 limit 페이징과 전체 문서 수 동시에 조회하는 방법

페이지네이션(페이징) 기능을 구현할 때는 limitskip으로 화면에 보여줄 문서만 조회하면서도, 동시에 컬렉션의 전체 문서 수(total count)를 함께 알아야 하는 경우가 많습니다. 이때 MongoDB의 $facet 연산자를 활용하면 하나의 집계 쿼리만으로 두 가지 결과를 한 번에 얻을 수 있습니다.

$facet은 하나의 집계 파이프라인 안에서 여러 하위 파이프라인을 실행하고, 그 결과들을 각각 다른 필드 이름으로 묶어 하나의 문서로 반환합니다. 덕분에 "실제 데이터 목록"과 "전체 문서 개수"를 별도의 쿼리 없이 동시에 조회할 수 있어 네트워크 왕복 횟수를 줄이고 성능 면에서도 유리합니다.

1. 예제 컬렉션 만들기

먼저 실습용 컬렉션을 생성하고 문서를 삽입해 보겠습니다. 아래 쿼리는 강사 정보를 담은 문서 두 개를 삽입합니다.

> db.totalDocumentDemo.insertOne({"InstructorId":100,"InstructorName":"Larry","InstructorFavouriteSubject":["Java","MongoDB","Python"]});
{
   "acknowledged" : true,
   "insertedId" : ObjectId("5c76e6701e9c5dd6f1f78274")
}
> db.totalDocumentDemo.insertOne({"InstructorId":200,"InstructorName":"Sam","InstructorFavouriteSubject":["SQL Server","C#","Javascript"]});
{
   "acknowledged" : true,
   "insertedId" : ObjectId("5c76e69c1e9c5dd6f1f78275")
}

2. 저장된 문서 확인하기

find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 확인할 수 있습니다.

> db.totalDocumentDemo.find().pretty();

실행 결과

{
   "_id" : ObjectId("5c76e6701e9c5dd6f1f78274"),
   "InstructorId" : 100,
   "InstructorName" : "Larry",
   "InstructorFavouriteSubject" : [
      "Java",
      "MongoDB",
      "Python"
   ]
}
{
   "_id" : ObjectId("5c76e69c1e9c5dd6f1f78275"),
   "InstructorId" : 200,
   "InstructorName" : "Sam",
   "InstructorFavouriteSubject" : [
      "SQL Server",
      "C#",
      "Javascript"
   ]
}

3. $facet으로 전체 데이터와 문서 수 동시에 조회하기

이제 핵심인 집계 쿼리입니다. $facet 내부에 두 개의 하위 파이프라인을 정의합니다.

  • Alldata: $match: {}로 조건 없이 모든 문서를 그대로 반환합니다. 여기에 $skip$limit을 추가하면 바로 페이지네이션에 활용할 수 있습니다.
  • totalDocument: $count 스테이지로 전체 문서 개수를 계산합니다.
> db.totalDocumentDemo.aggregate([
...    {
...       $facet:{
...          Alldata:[{$match:{}}],
...          totalDocument: [{ $count: 'totalDocument' }]
...       }
...    }
... ]).pretty();

실행 결과

{
   "Alldata" : [
      {
         "_id" : ObjectId("5c76e6701e9c5dd6f1f78274"),
         "InstructorId" : 100,
         "InstructorName" : "Larry",
         "InstructorFavouriteSubject" : [
            "Java",
            "MongoDB",
            "Python"
         ]
      },
      {
         "_id" : ObjectId("5c76e69c1e9c5dd6f1f78275"),
         "InstructorId" : 200,
         "InstructorName" : "Sam",
         "InstructorFavouriteSubject" : [
            "SQL Server",
            "C#",
            "Javascript"
         ]
      }
   ],
   "totalDocument" : [
      {
         "totalDocument" : 2
      }
   ]
}

결과를 보면 하나의 응답 안에 Alldata(문서 목록)와 totalDocument(문서 개수)가 함께 담겨 있는 것을 확인할 수 있습니다.

4. 실전 페이지네이션 적용 예시

실제 서비스에서는 다음과 같이 $skip$limit을 조합해 특정 페이지의 데이터만 가져오면서 전체 개수를 함께 구할 수 있습니다.

> db.totalDocumentDemo.aggregate([
...    {
...       $facet:{
...          pageData:[
...             { $skip: 0 },
...             { $limit: 10 }
...          ],
...          totalCount: [{ $count: 'total' }]
...       }
...    }
... ]);

이렇게 하면 클라이언트는 한 번의 요청으로 현재 페이지 데이터와 전체 개수(총 페이지 수 계산에 필요한 값)를 모두 받을 수 있습니다.

주의할 점

컬렉션이 비어 있거나 조건에 맞는 문서가 없으면 $count 결과 필드는 빈 배열([])로 반환됩니다. 따라서 애플리케이션 코드에서 이 경우를 별도로 처리해 주는 것이 좋습니다. 또한 대용량 컬렉션에서는 카운트 연산 비용이 클 수 있으므로, 필요하다면 인덱스를 활용하거나 카운트 값을 캐싱하는 방식을 고려해 보세요.