페이지네이션(페이징) 기능을 구현할 때는 limit과 skip으로 화면에 보여줄 문서만 조회하면서도, 동시에 컬렉션의 전체 문서 수(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 결과 필드는 빈 배열([])로 반환됩니다. 따라서 애플리케이션 코드에서 이 경우를 별도로 처리해 주는 것이 좋습니다. 또한 대용량 컬렉션에서는 카운트 연산 비용이 클 수 있으므로, 필요하다면 인덱스를 활용하거나 카운트 값을 캐싱하는 방식을 고려해 보세요.