MongoDB κΈ°λ° B2B λ§€μ₯ λΆμ λμ보λ - μ΄μ DB μ‘°ν λΉμ©μ μ΅μνν κ³ μ±λ₯ λΆμ νλ«νΌ
- MongoDB μ‘°ν λΉμ© κ±°μ 0: μ¬μ μ§κ³(pre-aggregation) μ λ΅μΌλ‘ μ΄μ DB μ§μ μ‘°ν μ°¨λ¨
- λ°μν SaaSκΈ UI: λͺ¨λ°μΌ/PC μ§μ, Stripe/NotionκΈμ λ―Έλλ©ν λμ보λ
- μ€μκ° KPI: λ§€μ₯λ³ GMV, κ²°μ μ‘, μ£Όλ¬Έ μ, λ©λ΄ νλ§€λ λ± ν΅μ¬ μ§ν μ 곡
- β μ΄ GMV (Gross Merchandise Value): μ 체 κ±°λμ‘
- β κ²°μ μ±κ³΅μ‘: μ€μ κ²°μ μλ£ κΈμ‘
- β μ£Όλ¬Έ μ: μ 체 μ£Όλ¬Έ 건μ
- β κ°λ¨κ° (AOV): νκ· μ£Όλ¬Έ κΈμ‘
- β νμ± λ§€μ₯ μ: κΈ°κ° λ΄ μ£Όλ¬Έμ΄ μλ λ§€μ₯ μ
- β κ²°μ μ±κ³΅λ₯ : κ²°μ μ±κ³΅/μ€ν¨ λΉμ¨
- π μκ³μ΄ μ°¨νΈ: μΌλ³ GMV/κ²°μ μ‘ νΈλ λ
- πͺ λ§€μ₯ λνΉ: GMV κΈ°μ€ Top λ§€μ₯
- π λ©λ΄ λνΉ: νλ§€λ/λ§€μΆ κΈ°μ€ μΈκΈ° λ©λ΄
- π λ μ§ νν°: μ€λ/7μΌ/30μΌ/μ΄λ²λ¬/μ§λλ¬/컀μ€ν
βββββββββββββββββββ
β μ΄μ MongoDB β β μ λ μ§μ μ‘°ν κΈμ§!
β (orders, ...) β
ββββββββββ¬βββββββββ
β
β λ°°μΉ μ§κ³ (μ£Ό 1ν)
β
βββββββββββββββββββ
β μ¬μ μ§κ³ 컬λ μ
β β λμ보λλ μ¬κΈ°λ§ μ‘°ν
β (metrics_*) β
ββββββββββ¬βββββββββ
β
β μΊμ (5λΆ TTL)
β
βββββββββββββββββββ
β Dashboard API β
βββββββββββββββββββ
-
metrics_daily_store: μΌλ³ λ§€μ₯ λ©νΈλ¦
- storeId, date, gmv, paidAmount, orderCount, avgOrderValue, paymentSuccessRate
-
metrics_daily_store_menu: μΌλ³ λ§€μ₯-λ©λ΄ λ©νΈλ¦
- storeId, menuId, date, quantity, revenue, orderCount
-
metrics_hourly_store (μ ν): μκ°λ³ λ§€μ₯ λ©νΈλ¦ (ννΈλ§΅μ©)
- storeId, datetime, hour, dayOfWeek, gmv, orderCount
# μμ‘΄μ± μ€μΉ
npm install
# νκ²½ λ³μ μ€μ
cp .env.example .env.local.env.local νμΌμ MongoDB μ°κ²° μ 보 μ
λ ₯:
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/<database>
MONGODB_DB_NAME=your_database_name
# μ ν: μ½κΈ° μ μ© μ°κ²°
MONGODB_READONLY_URI=mongodb+srv://readonly_user:...
# μΊμ μ€μ
CACHE_TTL_SECONDS=300
# λ μ§ λ²μ μ ν
MAX_DATE_RANGE_DAYS=180
# λ°°μΉ μ§κ³ μ£ΌκΈ° (μΌ λ¨μ)
BATCH_INCREMENTAL_DAYS=7μ€μ 컬λ μ ꡬ쑰λ₯Ό νμ νκΈ° μν΄ μμ ν μ€ν€λ§ νμ λꡬ μ€ν:
npm run devλΈλΌμ°μ μμ http://localhost:3000/explore μ μ
- "Load Collections" ν΄λ¦
- μ£Όλ¬Έ/κ²°μ κ΄λ ¨ 컬λ μ μ ν
- νλ ꡬ쑰 νμΈ (storeId, createdAt, totalAmount, items λ±)
lib/aggregation/pipeline.tsμ SourceCollections λλ νκ²½ λ³μλ‘ μ€μ 컬λ μ
λͺ
μ§μ :
COLLECTION_ORDERS=orders
COLLECTION_PAYMENTS=payments
COLLECTION_MENUS=menusλ©νΈλ¦ 컬λ μ μ μΈλ±μ€λ₯Ό μμ± (μ΅μ΄ 1ν):
npm run setup-indexesλ©νΈλ¦ λ°μ΄ν° μμ±:
npm run aggregatenpm run devhttp://localhost:3000 μ μνμ¬ λμ보λ νμΈ
# λ§€μ£Ό μμμΌ μ€μ 0μμ μ€ν
0 0 * * 1 cd /path/to/project && npm run aggregate >> /var/log/aggregation.log 2>&1.github/workflows/aggregate.yml:
name: Weekly Aggregation
on:
schedule:
- cron: '0 0 * * 1' # λ§€μ£Ό μμμΌ 00:00 UTC
workflow_dispatch: # μλ μ€ν κ°λ₯
jobs:
aggregate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm install
- run: npm run aggregate
env:
MONGODB_URI: ${{ secrets.MONGODB_URI }}
MONGODB_DB_NAME: ${{ secrets.MONGODB_DB_NAME }}-
β μ΄μ 컬λ μ μ§μ μ‘°ν κΈμ§
- λμ보λ APIλ μ€μ§
metrics_*컬λ μ λ§ μ‘°ν - λ°°μΉ μ§κ³λ§ μ΄μ 컬λ μ μ κ·Ό νμ©
- λμ보λ APIλ μ€μ§
-
β μΈλ±μ€ κΈ°λ° μΏΌλ¦¬
- λͺ¨λ 쿼리λ μΈλ±μ€ μ¬μ© νμ
explain()μΌλ‘ μ€ν κ³ν κ²μ¦
-
β λ μ§ λ²μ μ ν
- μ΅λ 180μΌλ‘ μ ν (νκ²½ λ³μλ‘ μ‘°μ κ°λ₯)
-
β μΊμ± νμ
- λͺ¨λ λμ보λ API μλ΅ μΊμ (κΈ°λ³Έ 5λΆ)
-
β Projection μ¬μ©
- νμν νλλ§ μ‘°ν
taghere-analytics/
βββ app/
β βββ page.tsx # λ©μΈ λμ보λ
β βββ explore/page.tsx # μ€ν€λ§ νμ UI
β βββ api/
β β βββ dashboard/route.ts # λμ보λ API
β β βββ explore/route.ts # μ€ν€λ§ νμ API
β βββ layout.tsx
βββ lib/
β βββ mongodb.ts # MongoDB μ°κ²°
β βββ cache.ts # μΈλ©λͺ¨λ¦¬ μΊμ
β βββ schema-explorer.ts # μμ ν μ€ν€λ§ νμ
β βββ types/
β β βββ metrics.ts # λ©νΈλ¦ νμ
μ μ
β βββ queries/
β β βββ dashboard.ts # λμ보λ 쿼리 (metrics μ μ©)
β βββ aggregation/
β βββ pipeline.ts # λ°°μΉ μ§κ³ νμ΄νλΌμΈ
βββ scripts/
β βββ run-aggregation.ts # λ°°μΉ μ€ν μ€ν¬λ¦½νΈ
βββ components/ui/ # shadcn/ui μ»΄ν¬λνΈ
βββ README.md
npm run dev # κ°λ° μλ² μμ
npm run build # νλ‘λμ
λΉλ
npm run start # νλ‘λμ
μλ² μμ
npm run aggregate # λ°°μΉ μ§κ³ μ€ν (μ¦λΆ)
npm run setup-indexes # μΈλ±μ€ μμ± (μ΅μ΄ 1ν){
_id: ObjectId,
storeId: string, // νμ
storeName: string, // μ ν
createdAt: Date, // νμ
totalAmount: number, // νμ
status: string, // νμ (cancelled μ μΈ)
items: [ // νμ (λ©λ΄ λΌμΈμμ΄ν
)
{
menuId: string,
menuName: string,
quantity: number,
price: number
}
]
}{
_id: ObjectId,
orderId: string, // μ ν
storeId: string, // νμ
amount: number, // νμ
status: string, // νμ ('success' | 'failed')
paidAt: Date // νμ
}vercelνκ²½ λ³μ μ€μ μ Vercel Dashboardμμ μΆκ°
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"].env.localμMONGODB_URIνμΈ- MongoDB Atlasμ IP νμ© λͺ©λ‘ νμΈ
- λ°°μΉ μ§κ³ μ€ν νμΈ:
npm run aggregate - λ©νΈλ¦ 컬λ μ μ‘΄μ¬ νμΈ
- λ μ§ λ²μ νμΈ
- μΈλ±μ€ μμ± νμΈ:
npm run setup-indexes - μΊμ TTL νμΈ
- λ μ§ λ²μ μΆμ