본문 바로가기
Database2025년 1월 20일6분 읽기

MongoDB Aggregation 심화 — Facet과 Bucket 분석

YS
김영삼
조회 293

Aggregation Pipeline 복습

MongoDB의 Aggregation Pipeline은 문서를 단계별로 변환하는 데이터 처리 파이프라인입니다. $facet은 하나의 파이프라인에서 여러 집계를 동시에 수행하고, $bucket은 데이터를 범위별로 그룹화하는 강력한 스테이지입니다.

$facet — 다차원 집계

db.products.aggregate([
  { $match: { status: "active" } },

  { $facet: {
    results: [
      { $sort: { createdAt: -1 } },
      { $skip: 0 },
      { $limit: 20 },
      { $project: { name: 1, price: 1, category: 1, rating: 1 } }
    ],

    categoryFacet: [
      { $group: { _id: "$category", count: { $sum: 1 } } },
      { $sort: { count: -1 } }
    ],

    brandFacet: [
      { $group: { _id: "$brand", count: { $sum: 1 } } },
      { $sort: { count: -1 } },
      { $limit: 20 }
    ],

    priceStats: [
      { $group: {
        _id: null,
        avgPrice: { $avg: "$price" },
        minPrice: { $min: "$price" },
        maxPrice: { $max: "$price" },
        totalProducts: { $sum: 1 }
      }}
    ],

    ratingDistribution: [
      { $group: {
        _id: { $floor: "$rating" },
        count: { $sum: 1 }
      }},
      { $sort: { _id: -1 } }
    ]
  }}
]);

$bucket — 범위 기반 그룹화

db.products.aggregate([
  { $bucket: {
    groupBy: "$price",
    boundaries: [0, 10000, 30000, 50000, 100000, 500000, Infinity],
    default: "기타",
    output: {
      count: { $sum: 1 },
      avgPrice: { $avg: "$price" },
      brands: { $addToSet: "$brand" },
      products: { $push: { name: "$name", price: "$price" } }
    }
  }}
]);

$bucketAuto — 자동 범위 설정

db.users.aggregate([
  { $bucketAuto: {
    groupBy: "$totalPurchases",
    buckets: 5,
    granularity: "R5",
    output: {
      count: { $sum: 1 },
      avgSpent: { $avg: "$totalSpent" },
      users: { $push: "$name" }
    }
  }}
]);

db.orders.aggregate([
  { $addFields: {
    hour: { $hour: "$createdAt" }
  }},
  { $bucket: {
    groupBy: "$hour",
    boundaries: [0, 6, 12, 18, 24],
    default: "unknown",
    output: {
      orderCount: { $sum: 1 },
      totalRevenue: { $sum: "$total" },
      avgOrderValue: { $avg: "$total" }
    }
  }}
]);

실전: 대시보드 데이터 한 번에 조회

db.orders.aggregate([
  { $match: {
    createdAt: {
      $gte: ISODate("2024-01-01"),
      $lt: ISODate("2025-01-01")
    }
  }},
  { $facet: {
    monthlySales: [
      { $group: {
        _id: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
        revenue: { $sum: "$total" },
        orders: { $sum: 1 }
      }},
      { $sort: { _id: 1 } }
    ],

    topCategories: [
      { $unwind: "$items" },
      { $group: {
        _id: "$items.category",
        revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } }
      }},
      { $sort: { revenue: -1 } },
      { $limit: 5 }
    ],

    statusBreakdown: [
      { $group: { _id: "$status", count: { $sum: 1 } } }
    ]
  }}
]);

성능 최적화 팁

  • $facet 앞에 $match를 배치하여 처리 대상 문서를 먼저 줄입니다
  • 각 facet 파이프라인은 독립적으로 실행되므로, RAM 사용량에 주의합니다 (100MB 제한)
  • allowDiskUse: true 옵션으로 메모리 제한을 우회할 수 있습니다
  • $bucketboundaries는 반드시 오름차순이어야 합니다
  • $bucketAutogranularity 옵션(R5, R10, E6 등)으로 경계값을 깔끔하게 만듭니다

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록