MongoDB Academy · 강의

파이프라인에서 $sort, $limit, $skip 사용하기

학습자는 집계 결과를 정렬하고 페이지 매김하며, $group 전에 $match와 $sort를 배치하는 최적화 규칙을 이해합니다.

레슨 4/413개 단계

파이프라인에서 $sort, $limit, $skip 사용하기은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

개요: 결과 정렬 및 페이지 나누기

세 가지 집계 파이프라인 단계가 결과의 순서와 양을 제어합니다. $sort는 하나 이상의 필드를 기준으로 문서를 정렬하고, $limit는 처음 N개의 문서만 가져오며, $skip은 처음 N개의 문서를 버립니다. 이들을 함께 사용하면 정렬과 페이지 나누기를 구현할 수 있습니다. 파이프라인에서 이 단계들을 어디에 배치하는지 이해하는 것은 성능에 큰 영향을 줍니다.

$sort 단계

$sort 단계는 하나 이상의 필드 값을 기준으로 문서를 정렬합니다. 오름차순에는 1을, 내림차순에는 -1을 사용합니다. 여러 필드를 지정하면 먼저 첫 번째 필드로 정렬하고, 값이 같은 문서끼리는 두 번째 필드로 정렬하는 방식으로 계속 진행됩니다. 이는 SQL의 다중 키 정렬이나 MongoDB의 find().sort()와 정확히 같은 방식입니다.

// Sort by revenue descending, then alphabetically by name for ties
db.products.aggregate([
  { $group: { _id: '$category', revenue: { $sum: '$price' } } },
  { $sort: { revenue: -1, _id: 1 } }  // revenue desc, category name asc
]);

$sort와 인덱스 사용

$sort가 파이프라인의 첫 번째 단계이거나 인덱스를 사용하는 $match 뒤에 배치되면 MongoDB는 인덱스를 사용하여 정렬을 처리할 수 있으므로 메모리 내 정렬을 완전히 피할 수 있습니다. $sort가 $group이나 $project처럼 새 필드를 생성하는 단계 뒤에 나타나면 사용할 수 있는 인덱스가 없으므로 MongoDB는 메모리 내 정렬을 수행합니다. 메모리 내 정렬의 크기가 100MB를 초과하면 allowDiskUse: true가 필요합니다.

// Index-backed sort: $sort on indexed field before any transformation
db.orders.aggregate([
  { $match: { userId: 'u1' } },
  { $sort: { createdAt: -1 } }  // if createdAt is indexed, no in-memory sort
]);

// In-memory sort: $sort after $group (computed field, no index)
db.orders.aggregate([
  { $group: { _id: '$userId', total: { $sum: '$amount' } } },
  { $sort: { total: -1 } }  // in-memory: 'total' is a computed field
]);

$limit 단계

$limit는 처음 N개의 문서만 다음 단계로 전달하고 나머지는 버립니다. 사용 방법은 간단합니다. 통과하도록 허용할 문서의 최대 개수를 정수로 지정하면 됩니다. $lookup과 같은 비용이 큰 단계 앞에 $limit를 배치하면 수행되는 조인 또는 조회의 수를 크게 줄일 수 있습니다. 항상 다음과 같이 자문해 보십시오. '더 앞에서 제한할 수 있는가?'

// Top 10 best-selling products
db.orders.aggregate([
  { $group: { _id: '$productId', sold: { $sum: '$qty' } } },
  { $sort: { sold: -1 } },
  { $limit: 10 },  // only top 10 pass through
  // Only 10 lookups instead of thousands:
  { $lookup: { from: 'products', localField: '_id', foreignField: '_id', as: 'product' } }
]);

$skip 단계

$skip은 스트림에서 처음 N개의 문서를 버립니다. 이는 오프셋 페이지 나누기의 집계 버전입니다. 페이지당 10개를 표시한다면 2페이지는 skip 10에서 시작합니다. 일관된 결과를 얻으려면 $skip은 항상 $sort 뒤에 배치해야 합니다. 정렬되지 않은 문서를 건너뛰으면 페이지 결과를 예측할 수 없습니다. SQL의 OFFSET과 마찬가지로 큰 오프셋에서 $skip은 느립니다. MongoDB가 건너뛴 모든 문서를 여전히 처리해야 하기 때문입니다.

const PAGE = 2;
const PAGE_SIZE = 10;

db.articles.aggregate([
  { $match: { status: 'published' } },
  { $sort: { publishedAt: -1 } },
  { $skip: (PAGE - 1) * PAGE_SIZE },  // skip page 1's 10 docs
  { $limit: PAGE_SIZE }               // take page 2's 10 docs
]);

최적화 도구: $sort + $limit 병합

MongoDB의 집계 최적화 도구는 $sort 단계 바로 뒤에 $limit 단계가 오는 경우를 감지하여 두 단계를 상위 N개 정렬로 병합합니다. 모든 문서를 정렬한 다음 대부분을 버리는 대신, MongoDB는 정렬하는 동안 우선순위 큐에 상위 N개의 후보만 유지합니다. 따라서 전체 문서 수에 비례하는 메모리 대신 O(N) 메모리를 사용합니다. 이 최적화는 자동으로 적용되므로 $sort 뒤에 $limit를 작성하기만 하면 됩니다.

// This pattern triggers the top-N sort optimisation
db.reviews.aggregate([
  { $match: { productId: 'p1' } },
  { $sort: { rating: -1, helpful: -1 } },  // sort
  { $limit: 5 }   // $sort + $limit merged into top-5 sort internally
]);
// MongoDB never sorts ALL reviews; it tracks only the best 5

최적화 도구: $match와 $sort 순서 변경

집계 최적화 도구는 안전한 경우 $match 단계를 파이프라인의 더 앞쪽으로 자동 이동합니다. 예를 들어 $sort가 계산하지 않은 필드에 대해 $sort가 $match보다 앞에 있으면, 최적화 도구는 정렬해야 하는 문서 수를 줄이기 위해 $match를 $sort 앞에 배치합니다. 최적화 도구의 변경 사항은 explain() 출력에서 확인할 수 있습니다.

// You write:
db.orders.aggregate([
  { $sort: { amount: -1 } },
  { $match: { status: 'completed' } }  // optimizer moves this BEFORE $sort
]);

// Optimized execution (equivalent to):
db.orders.aggregate([
  { $match: { status: 'completed' } },  // fewer docs to sort
  { $sort: { amount: -1 } }
]);

네 가지 단계 결합: 완전한 목록 엔드포인트

필터, 정렬, 페이지 나누기를 적용하여 제품 목록을 반환하는 현실적인 API 엔드포인트는 $match, $sort, $skip, $limit를 올바른 순서로 결합합니다. 패턴은 항상 필터링 → 정렬 → 건너뛰기 → 제한입니다. 이 순서를 사용하면 MongoDB가 불필요한 문서를 버리기 전에 필터링과 가능한 경우 정렬 모두에 인덱스를 사용할 수 있습니다.

async function getProducts({ category, sort = 'price', page = 1, pageSize = 20 }) {
  return db.products.aggregate([
    { $match: { category, inStock: true } },   // 1. filter
    { $sort: { [sort]: 1 } },                  // 2. sort
    { $skip: (page - 1) * pageSize },          // 3. offset
    { $limit: pageSize }                       // 4. take page
  ]).toArray();
}

깊은 오프셋에서의 $skip 성능

$skip의 근본적인 문제는 MongoDB가 건너뛴 모든 문서를 여전히 처리하고 버려야 한다는 것입니다. 페이지당 항목이 20개인 1000페이지에서는 MongoDB가 20개를 반환하기 전에 19,980개의 문서를 버립니다. 페이지가 깊어질수록 점점 느려집니다. 사용자가 5~10페이지를 거의 넘기지 않는 공개 검색 또는 피드 API에서는 오프셋 페이지 나누기를 사용해도 괜찮습니다. 하지만 빈번하게 깊은 페이지까지 이동해야 한다면 키셋 페이지 나누기가 필요합니다.

// Page 1000 is SLOW: MongoDB processes and discards 19,980 docs
db.posts.aggregate([
  { $sort: { createdAt: -1 } },
  { $skip: 19980 },  // 999 * 20
  { $limit: 20 }
]);

// Keyset pagination: always fast regardless of page depth
db.posts.aggregate([
  { $match: { createdAt: { $lt: lastSeenTimestamp } } },  // cursor
  { $sort: { createdAt: -1 } },
  { $limit: 20 }
]);

$sort 메모리 제한 및 allowDiskUse

집계 파이프라인에서 메모리 내 $sort는 기본적으로 100MB로 제한됩니다. 정렬이 이 제한을 초과하면 파이프라인이 QueryExceededMemoryLimitNoDiskUseAllowed 오류와 함께 실패합니다. aggregate()의 두 번째 인수로 { allowDiskUse: true }를 전달하면 디스크로 데이터를 넘길 수 있습니다. 또는 초기에 $match를 적용하거나 이를 지원하는 인덱스를 사용하여 정렬 입력의 크기를 줄일 수 있습니다.

// Enable disk use for large sorts
db.events.aggregate(
  [
    { $match: { year: 2024 } },
    { $sort: { timestamp: -1 } },
    { $group: { _id: '$userId', events: { $push: '$type' } } }
  ],
  { allowDiskUse: true }  // allows spilling to disk
);

결과와 함께 전체 개수 가져오기

일반적인 API 패턴은 한 번의 데이터베이스 왕복으로 페이지가 나뉜 결과와 전체 개수를 함께 반환하는 것입니다. $facet을 사용하면 파이프라인을 두 개의 병렬 분기로 나눌 수 있습니다. 한 분기는 $skip/$limit 결과를 처리하고, 다른 분기는 $count를 처리합니다. 이렇게 하면 두 번의 별도 집계 호출을 피할 수 있으며 모든 문서를 따로 세는 것보다 효율적입니다.

db.products.aggregate([
  { $match: { category: 'electronics' } },
  { $sort: { price: 1 } },
  { $facet: {
    // Branch 1: paginated results
    data: [
      { $skip: 0 },
      { $limit: 20 }
    ],
    // Branch 2: total count
    total: [
      { $count: 'count' }
    ]
  }}
]);
// Output: { data: [...20 docs...], total: [{ count: 348 }] }

빠른 확인

집계 파이프라인의 $sort, $limit, $skip에 대한 이해도를 확인해 보십시오.

레슨 요약

이 레슨에서는 다음을 배웠습니다. $sort는 문서를 정렬하며 앞쪽에 배치하면 인덱스 사용의 이점을 얻을 수 있습니다. $limit는 처음 N개의 문서를 가져오고 $sort와 병합되어 효율적인 상위 N개 정렬을 수행합니다. 또한 $skip은 오프셋 페이지 나누기를 구현하지만 깊은 오프셋에서는 성능이 저하됩니다. 다음으로는 컬렉션을 조인하는 $lookup을 포함한 고급 집계 단계를 살펴보겠습니다.

무료로 시작

AI 튜터와 함께 JavaScript을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
30
레슨
120

자주 묻는 질문

“파이프라인에서 $sort, $limit, $skip 사용하기” 강의는 무료인가요?

네 — “파이프라인에서 $sort, $limit, $skip 사용하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“파이프라인에서 $sort, $limit, $skip 사용하기”에서 뭘 배우나요?

학습자는 집계 결과를 정렬하고 페이지 매김하며, $group 전에 $match와 $sort를 배치하는 최적화 규칙을 이해합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“파이프라인에서 $sort, $limit, $skip 사용하기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 파이프라인 개념: 단계, 연산자, 표현식
  2. $match와 $project: 필터링 및 형태 변경
  3. $group: 집계 및 합계 계산
  4. 파이프라인에서 $sort, $limit, $skip 사용하기
← MongoDB Academy(으)로 돌아가기