Use Cases (업데이트: 2026. 6. 1.)

Claude Code로 NoSQL/MongoDB 설계하기: 스키마, 인덱스, 집계

접근 패턴부터 검증 스키마, 인덱스, aggregation, 테스트, 배포 체크리스트까지 MongoDB 실전 흐름을 정리합니다.

Claude Code로 NoSQL/MongoDB 설계하기: 스키마, 인덱스, 집계

컬렉션보다 접근 패턴을 먼저 정한다

NoSQL/MongoDB는 데이터를 고정된 행과 열의 표에 맞추는 방식이 아니라, JSON에 가까운 문서 단위로 저장하는 데이터베이스입니다. 그래서 MongoDB 설계의 출발점은 “어떤 컬렉션을 만들까”가 아니라 “애플리케이션이 어떤 화면에서 어떤 데이터를 함께 읽고, 어떤 순서로 정렬하며, 어떤 단위로 갱신하는가”입니다.

Claude Code를 사용할 때도 같은 원칙이 필요합니다. 단순히 “주문 스키마를 만들어줘”라고 하면 얇은 예제가 나오기 쉽습니다. 대신 화면, API 응답, 데이터 증가량, 갱신 빈도, 리포트 요구사항, 권한 경계를 함께 전달하면 embedding과 reference, 인덱스, aggregation pipeline, validation schema, seed/test까지 한 번에 검토할 수 있습니다.

실무에서 자주 만나는 사용 사례

사용 사례자주 읽는 형태MongoDB 모델 방향주요 위험
이커머스 주문사용자별 최근 주문, 주문 상세, 월별 매출구매 시점의 상품명과 가격은 주문에 embedding, productId는 reference상품 변경이 과거 주문을 바꾸는 문제
SaaS 감사 로그조직, 사용자, 기간, 이벤트 유형append-only 문서와 복합 인덱스, 필요하면 TTL큰 컬렉션 전체 스캔
CMS 기사slug 단건 조회, 상태별 목록, 카테고리별 목록slug, status, 날짜 인덱스를 명확히 둠초안이나 내부 메모 노출
고객 지원 티켓고객별 큐, 담당자별 큐, 최근 댓글제한된 댓글은 embedding, 첨부와 긴 본문은 분리배열이 끝없이 커짐

관련 배경은 Claude Code 데이터베이스 설계, API 개발, Prisma ORM, SQL 최적화와 함께 보면 좋습니다.

Claude Code에 줄 프롬프트

당신은 MongoDB 설계 리뷰어입니다.
컬렉션 이름보다 접근 패턴을 먼저 기준으로 모델을 설계하세요.

요구사항:
- 사용자는 자신의 주문 목록을 최신순으로 봅니다.
- 주문 상세에는 구매 당시 상품명, 가격, 카테고리가 필요합니다.
- 상품 가격 변경은 과거 주문에 영향을 주면 안 됩니다.
- 관리자는 월별, 상태별, 카테고리별 매출을 집계합니다.
- transaction은 일부 성공이 업무 정합성을 깨는 경우에만 사용합니다.
- validation schema, indexes, seed data, aggregation, explain 확인, rollout checklist를 포함하세요.

좋은 답변은 어떤 필드를 embedding하고 어떤 필드를 reference하는지 분명히 말합니다. 주문의 상품명, 구매 가격, 구매 당시 카테고리는 주문 시점의 사실이므로 주문 문서에 남기는 편이 자연스럽습니다. 현재 상품 설명, 재고, 노출 정책은 상품 문서에 두고 productId로 연결합니다.

바로 실행할 수 있는 예제

MongoDB를 로컬에서 실행합니다.

docker run --name mongo-claude-demo -p 27017:27017 -d mongo:8

공식 Node.js 드라이버를 설치합니다.

npm init -y
npm install mongodb
npm install -D tsx typescript
mkdir -p src

src/mongodb-workflow.ts를 만듭니다.

import { MongoClient, ObjectId } from "mongodb";

const client = new MongoClient(process.env.MONGODB_URI ?? "mongodb://localhost:27017");

async function main() {
  await client.connect();
  const db = client.db("claude_code_shop_ko");
  await db.dropDatabase();

  await db.createCollection("orders", {
    validator: {
      $jsonSchema: {
        bsonType: "object",
        required: ["userId", "status", "items", "totalAmount", "createdAt", "updatedAt"],
        properties: {
          userId: { bsonType: "objectId" },
          status: { enum: ["pending", "paid", "shipped", "cancelled"] },
          totalAmount: { bsonType: ["int", "long", "double", "decimal"], minimum: 0 },
          createdAt: { bsonType: "date" },
          updatedAt: { bsonType: "date" },
          items: {
            bsonType: "array",
            minItems: 1,
            items: {
              bsonType: "object",
              required: ["productId", "name", "category", "price", "quantity"],
              properties: {
                productId: { bsonType: "objectId" },
                name: { bsonType: "string" },
                category: { bsonType: "string" },
                price: { bsonType: ["int", "long", "double", "decimal"], minimum: 0 },
                quantity: { bsonType: "int", minimum: 1 }
              }
            }
          }
        }
      }
    }
  });

  const products = db.collection("products");
  const orders = db.collection("orders");

  await Promise.all([
    orders.createIndex({ userId: 1, createdAt: -1 }, { name: "orders_by_user_recent" }),
    orders.createIndex({ status: 1, createdAt: -1 }, { name: "orders_by_status_recent" }),
    orders.createIndex({ "items.category": 1, createdAt: -1 }, { name: "orders_by_category_month" })
  ]);

  const inserted = await products.insertMany([
    { name: "Claude Code Workshop", category: "training", currentPrice: 48000 },
    { name: "MongoDB Review Template", category: "template", currentPrice: 9800 }
  ]);

  const userId = new ObjectId();
  const now = new Date("2026-06-01T09:00:00.000Z");

  await orders.insertOne({
    userId,
    status: "paid",
    items: [
      { productId: inserted.insertedIds[0], name: "Claude Code Workshop", category: "training", price: 48000, quantity: 1 },
      { productId: inserted.insertedIds[1], name: "MongoDB Review Template", category: "template", price: 9800, quantity: 2 }
    ],
    totalAmount: 67600,
    createdAt: now,
    updatedAt: now
  });

  const report = await orders.aggregate([
    { $match: { status: { $in: ["paid", "shipped"] } } },
    { $unwind: "$items" },
    {
      $group: {
        _id: {
          month: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
          category: "$items.category"
        },
        revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
        quantity: { $sum: "$items.quantity" }
      }
    },
    { $sort: { "_id.month": 1, revenue: -1 } }
  ]).toArray();

  const explain = await orders.find({ userId }).sort({ createdAt: -1 }).limit(10).explain("executionStats");
  if (report.length !== 2) throw new Error("aggregation failed");
  if ((explain.executionStats?.totalDocsExamined ?? 0) > 1) throw new Error("index check failed");

  console.log(JSON.stringify({ report, examined: explain.executionStats.totalDocsExamined }, null, 2));
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await client.close();
  });

실행합니다.

npx tsx src/mongodb-workflow.ts

이 코드는 validation schema, index, seed, aggregation pipeline, explain 확인을 한 번에 보여줍니다. 실행 결과를 Claude Code에 붙여 넣고 인덱스 순서와 쿼리 형태가 맞는지 리뷰시키면, 단순 코드 생성보다 훨씬 실전적인 피드백을 얻을 수 있습니다.

embedding과 reference 판단

항상 함께 읽는 데이터, 그리고 특정 시점의 사실을 보존해야 하는 데이터는 embedding이 잘 맞습니다. 독립적으로 자주 바뀌거나, 무한히 커질 수 있거나, 하나의 진실만 유지해야 하는 데이터는 reference가 낫습니다. 주문 상품 스냅샷은 embedding하고, 상품 마스터는 reference로 둡니다. 감사 로그나 알림처럼 계속 쌓이는 데이터는 부모 문서 안에 끝없이 넣지 않습니다.

인덱스, aggregation, transaction

인덱스는 실제 쿼리에서 역산합니다. find({ userId }).sort({ createdAt: -1 })에는 { userId: 1, createdAt: -1 }가 맞습니다. Aggregation은 먼저 $match로 범위를 줄이고, 필요한 배열만 $unwind한 뒤 $group합니다. 자주 보는 대시보드라면 매번 실시간 집계를 돌리기보다 캐시나 사전 집계 컬렉션도 검토합니다.

transaction은 주문 결제 처리처럼 일부만 성공하면 업무가 깨지는 경계에만 사용합니다. 알림 발송, 검색 인덱스 동기화, 조회수 증가처럼 재시도 가능한 작업까지 모두 transaction에 넣을 필요는 없습니다.

공식 문서는 Data Modeling, Indexes, Aggregation, Transactions, MongoDB Node.js Driver를 확인하세요.

자주 하는 실수

첫째, 관계형 정규화를 그대로 가져와서 매번 애플리케이션에서 여러 번 조회하는 것입니다. 둘째, 반대로 댓글, 로그, 알림처럼 계속 늘어나는 배열을 모두 embedding하는 것입니다. 셋째, TypeScript 타입만 믿고 DB validation을 생략하는 것입니다. 넷째, 인덱스를 만들고 explain("executionStats")를 보지 않는 것입니다. 다섯째, 무거운 aggregation을 모든 API 요청에서 실행하는 것입니다.

배포 전에는 주요 쿼리와 인덱스의 대응, validation schema 적용, seed/test, explain 결과, transaction 경계, 백업과 롤백 절차를 확인해야 합니다.

ClaudeCodeLab은 Claude Code 교육, CLAUDE.md 템플릿 정비, MongoDB/API 설계 상담을 함께 지원합니다. 실제로 이 흐름을 적용해 보면, 인덱스를 많이 추가하는 것보다 explain 결과를 바탕으로 쿼리 모양을 고치는 일이 더 큰 효과를 내는 경우가 많습니다.

#Claude Code #MongoDB #NoSQL #데이터베이스 #백엔드
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.