Google Looker Studioμ νκ³λ₯Ό 극볡νκ³ , μ»€λ¨Έμ€ λλ©μΈμ μ΅μ νλ μ¬λ΄ μ μ© λ°μ΄ν° μκ°ν νλ«νΌ
- νλ‘μ νΈ λ°°κ²½ λ° λͺ©ν
- ν΄κ²°νλ €λ λ¬Έμ
- μμ€ν κ°μ
- κΈ°μ μ€ν
- μν€ν μ² μ€κ³
- λλ ν°λ¦¬ ꡬ쑰
- μ£Όμ κΈ°λ₯ λͺ©λ‘
- λ°μ΄ν° νλ¦
- λ°°ν¬ μ λ΅
- κ°λ° λ‘λλ§΅
LookFlexλ Google Looker Studioλ₯Ό λ체νκΈ° μν΄ μμλ μ¬λ΄ μ μ© λμ보λ νλ«νΌμ λλ€. BigQueryμ μ μ¬λ λ§€μΆ λ° κ΄κ³ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘, ꡬμ±μ κ°μκ° μνλ λ°©μμΌλ‘ λ°μ΄ν°λ₯Ό μ‘°ννκ³ μκ°νν μ μλ νκ²½μ μ 곡ν©λλ€.
ν΅μ¬ λͺ©ν
- ν μ΄λΈ μ€μ¬μ λ°μ΄ν° μ‘°ν λ° λΆμ νκ²½ μ 곡
- μ¬μ©μλ³ λ·°/λ©νΈλ¦ 컀μ€ν°λ§μ΄μ§ μ€μ μ μ₯ λ° λ³΅μ
- μ‘°κ±΄λΆ μμ, μ λ ¬, νν°λ§μ UIλΏλ§ μλλΌ μ€μ νμΌλ‘λ κ΄λ¦¬
- 100λͺ λ―Έλ§ κ·λͺ¨μ μ¬λ΄ ꡬμ±μ λμ, μν κΈ°λ° μ κ·Ό μ μ΄
- λ‘컬 μλ² μ°μ μ΄μ, μ΄ν μΈν°λ· κ³΅κ° λ°°ν¬ κ°λ₯ν ꡬ쑰
| λ¬Έμ | Looker Studio νν© | LookFlex λͺ©ν |
|---|---|---|
| λ μ΄μμ μμ λ λΆμ‘± | 그리λ κ°κ²©μ΄ λμ΄ ν λ°°μΉκ° λΆμμ°μ€λ¬μ | px λ¨μ μμ λ°°μΉ + 그리λ μ€μ μ΅μ μ 곡 |
| λ©νΈλ¦ λ³κ²½ UX λΆνΈ | μ‘°ν κΆν μ¬μ©μκ° ν€λ μμ΄μ½μ μ°Ύμ ν΄λ¦ν΄μΌ ν¨ | μ¬μ΄λλ° λλ μλ¨ μ»¨νΈλ‘€ ν¨λλ‘ μ§κ΄μ μ ν |
| μ‘°κ±΄λΆ μμ μ€μ λΉν¨μ¨ | μ΄ λ¨μλ‘ λ§μ°μ€ UI μ‘°μ λ°λ³΅ | JSON/YAML μ€μ μΌλ‘ μΌκ΄ μ μ©, UIμμλ νΈμ§ κ°λ₯ |
| λ·° μ μ₯/곡μ λΆκ° | κ°μΈ μ€μ μ΄ μ μ₯λμ§ μμ | μ¬μ©μλ³ μ»€μ€ν λ·°λ₯Ό DBμ μ μ₯νκ³ λ³΅μ |
| μν κΈ°λ° μ κ·Ό μ μ΄ λ―Έν‘ | 보기/νΈμ§ κΆνμ΄ λ¨μν¨ | Admin / Editor / Viewer μν λΆλ¦¬ |
[BigQuery]
β λ°°μΉ ETL (μ: λ§€μΌ μλ²½ μ§κ³)
βΌ
[FastAPI λ°±μλ] ββββββββββββββββββββββββββββββββ
β REST API (JWT μΈμ¦) β
βΌ β
[Next.js νλ‘ νΈμλ] ββ [μ¬μ©μ λΈλΌμ°μ ] β
β
[PostgreSQL] βββ μ¬μ©μ κ³μ , λμ보λ μ€μ μ μ₯ β
[Redis] βββ 쿼리 κ²°κ³Ό μΊμ (μ ν) ββββββββββ
| νλͺ© | μ ν | μ΄μ |
|---|---|---|
| νλ μμν¬ | Next.js 14+ (App Router) | SSR/SSG μ§μ, API Route νμ© κ°λ₯, λ°°ν¬ μ μ°μ± |
| μΈμ΄ | TypeScript | νμ μμ μ±, μ μ§λ³΄μμ± |
| μ€νμΌλ§ | Tailwind CSS + shadcn/ui | λΉ λ₯Έ UI ꡬμ±, μ»΄ν¬λνΈ μ»€μ€ν°λ§μ΄μ§ μ©μ΄ |
| ν μ΄λΈ | TanStack Table v8 | μ λ ¬Β·νν°Β·κ°μ μ€ν¬λ‘€Β·μ‘°κ±΄λΆ μμΒ·μ»¬λΌ μ»€μ€ν°λ§μ΄μ§μ μ½λ λ 벨μμ μμ μ μ΄ |
| μ°¨νΈ | Apache ECharts (echarts-for-react) | 볡μ‘ν 컀μ€ν°λ§μ΄μ§, λμ©λ λ°μ΄ν° λ λλ§ μ±λ₯ μ°μ |
| μλ² μν κ΄λ¦¬ | TanStack Query (React Query) | μΊμ±, λ°±κ·ΈλΌμ΄λ κ°±μ , λ‘λ©/μλ¬ μν κ΄λ¦¬ |
| ν΄λΌμ΄μΈνΈ μν | Zustand | κ²½λ, λ¨μν μ μ μν κ΄λ¦¬ |
| νΌ | React Hook Form + Zod | μ ν¨μ± κ²μ¬, νμ μΆλ‘ |
| λ μ§ | date-fns | κ²½λ λ μ§ μ νΈλ¦¬ν° |
| νλͺ© | μ ν | μ΄μ |
|---|---|---|
| νλ μμν¬ | FastAPI (Python 3.11+) | λΉλκΈ° μ§μ, μλ OpenAPI λ¬Έμ, BigQuery λ± λ°μ΄ν° μνκ³μ λμΌ μΈμ΄ |
| ORM | SQLAlchemy 2.0 (async) | PostgreSQLκ³Όμ λΉλκΈ° ν΅ν©, λ§μ΄κ·Έλ μ΄μ κ΄λ¦¬ μ©μ΄ |
| λ§μ΄κ·Έλ μ΄μ | Alembic | SQLAlchemyμ ν΅ν©λ DB μ€ν€λ§ λ²μ κ΄λ¦¬ |
| μΈμ¦ | JWT (python-jose) + bcrypt (passlib) | Stateless μΈμ¦, λ‘컬/μΈν°λ· νκ²½ λͺ¨λ μ ν© |
| λ°μ΄ν° κ²μ¦ | Pydantic v2 | FastAPIμ λ€μ΄ν°λΈ ν΅ν©, λΉ λ₯Έ μ§λ ¬ν |
| BigQuery μ°λ | google-cloud-bigquery | 곡μ Python ν΄λΌμ΄μΈνΈ, pandas μ°λ μ§μ |
| λ°°μΉ μ€μΌμ€λ¬ | APScheduler (λλ μΈλΆ cron) | μ ν΄μ§ μκ°μ BigQuery β PostgreSQL ETL μ€ν |
| νλͺ© | μ ν | μ΄μ |
|---|---|---|
| μ£Ό DB | PostgreSQL 16 | μ¬μ©μ κ³μ , λμ보λ ꡬμ±, 컀μ€ν μ€μ μ μ₯ |
| μΊμ (μ ν) | Redis 7 | BigQuery 쿼리 κ²°κ³Ό μΊμ±, μΈμ ν ν° κ΄λ¦¬ |
| 컨ν μ΄λ | Docker + Docker Compose | λ‘컬 μλ² μΌκ΄μ± μλ μ€ν νκ²½, μ΄ν ν΄λΌμ°λ μ΄μ μ©μ΄ |
| 리λ²μ€ νλ‘μ | Nginx | νλ‘ νΈΒ·λ°±μλ λ¨μΌ λλ©μΈ λΌμ°ν , ν₯ν HTTPS μ μ© |
| νκ²½ λ³μ κ΄λ¦¬ | .env + python-dotenv / Next.js env | λ‘컬/νλ‘λμ νκ²½ λΆλ¦¬ |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β
β Next.js (TypeScript) β
β TanStack Table β ECharts β TanStack Query β Zustand β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β HTTPS / REST API
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ
β Nginx (Reverse Proxy) β
ββββββββ¬ββββββββββββββββββββββββββββββββββ¬βββββββββββββ
β β
ββββββββΌβββββββ ββββββββββΌββββββββ
β FastAPI β β Next.js β
β (Python) β β (Static/SSR) β
β Port 8000 β β Port 3000 β
ββββββββ¬βββββββ ββββββββββββββββββ
β
ββββββββΌβββββββββββββββββββ
β PostgreSQL β Redis β
ββββββββ¬βββββββββββββββββββ
β
ββββββββΌβββββββ
β BigQuery β (Google Cloud β μ½κΈ° μ μ©)
βββββββββββββββ
1. μ¬μ©μ β POST /api/auth/login (email + password)
2. FastAPI β bcrypt λΉλ°λ²νΈ κ²μ¦
3. FastAPI β JWT Access Token (15λΆ) + Refresh Token (7μΌ) λ°κΈ
4. νλ‘ νΈ β Access Tokenμ λ©λͺ¨λ¦¬μ μ μ₯, Refresh Tokenμ HttpOnly Cookie
5. λ§λ£ μ β /api/auth/refresh λ‘ μλ κ°±μ
| μν | κΆν |
|---|---|
Admin |
μ¬μ©μ κ΄λ¦¬, λ°μ΄ν°μμ€ μ°κ²°, λμ보λ μμ±Β·νΈμ§Β·μμ |
Editor |
λμ보λ νΈμ§, λ·° μ μ₯, μ‘°κ±΄λΆ μμ μ€μ |
Viewer |
λμ보λ μ‘°ν, κ°μΈ λ·° μ μ₯ (λ€λ₯Έ μ¬λμκ²λ λΉκ³΅κ°) |
μ¬μ©μλ³ μ»€μ€ν°λ§μ΄μ§ μ€μ μ JSONB 컬λΌμΌλ‘ μ μ°νκ² μ μ₯ν©λλ€.
-- λμ보λ
CREATE TABLE dashboards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
owner_id UUID REFERENCES users(id),
config JSONB, -- λ μ΄μμ, μμ ― λ°°μΉ μ 보
is_public BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- μ¬μ©μλ³ λ·° μ€λ²λΌμ΄λ (κ°μΈ 컀μ€ν°λ§μ΄μ§)
CREATE TABLE user_view_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
dashboard_id UUID REFERENCES dashboards(id),
widget_id VARCHAR(100),
config JSONB, -- λ©νΈλ¦ μ ν, μ‘°κ±΄λΆ μμ, μ λ ¬ λ±
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, dashboard_id, widget_id)
);config JSONB μμ (ν
μ΄λΈ μμ ―)
{
"metrics": ["settlement_amount", "margin", "roi"],
"sort": { "column": "settlement_amount", "direction": "desc" },
"conditionalFormatting": [
{
"column": "roi",
"rules": [
{ "condition": "lt", "value": 0, "style": { "backgroundColor": "#fee2e2", "color": "#dc2626" } },
{ "condition": "gte", "value": 50, "style": { "backgroundColor": "#dcfce7", "color": "#16a34a" } }
]
}
],
"columnWidths": { "brand": 120, "roi": 80 },
"frozenColumns": ["brand"]
}lookflex/
βββ apps/
β βββ frontend/ # Next.js μ±
β β βββ app/ # App Router
β β β βββ (auth)/ # λ‘κ·ΈμΈ/νμκ°μ
νμ΄μ§
β β β βββ dashboard/ # λμ보λ λ·°
β β β βββ admin/ # κ΄λ¦¬μ νμ΄μ§
β β βββ components/
β β β βββ table/ # TanStack Table κΈ°λ° μ»΄ν¬λνΈ
β β β βββ chart/ # ECharts λνΌ μ»΄ν¬λνΈ
β β β βββ ui/ # shadcn/ui κ³΅ν΅ μ»΄ν¬λνΈ
β β βββ lib/
β β β βββ api/ # API ν΄λΌμ΄μΈνΈ (axios/fetch)
β β β βββ store/ # Zustand μ€ν μ΄
β β βββ ...
β β
β βββ backend/ # FastAPI μ±
β βββ app/
β β βββ api/
β β β βββ auth.py # μΈμ¦ λΌμ°ν°
β β β βββ dashboards.py # λμ보λ CRUD
β β β βββ data.py # BigQuery λ°μ΄ν° μ‘°ν
β β β βββ users.py # μ¬μ©μ κ΄λ¦¬
β β βββ core/
β β β βββ config.py # νκ²½λ³μ, μ€μ
β β β βββ security.py # JWT, bcrypt
β β βββ db/
β β β βββ models.py # SQLAlchemy λͺ¨λΈ
β β β βββ session.py # DB μΈμ
β β βββ services/
β β β βββ bigquery.py # BigQuery 쿼리 μλΉμ€
β β β βββ etl.py # λ°°μΉ ETL λ‘μ§
β β βββ main.py
β βββ alembic/ # DB λ§μ΄κ·Έλ μ΄μ
β
βββ docker-compose.yml
βββ docker-compose.prod.yml
βββ nginx/
β βββ nginx.conf
βββ README.md
- μ¬μ©μ μΈμ¦ (λ‘κ·ΈμΈ / λ‘κ·Έμμ / JWT κ°±μ )
- μν κΈ°λ° μ κ·Ό μ μ΄ (Admin / Editor / Viewer)
- BigQuery μ°κ²° λ° λ°μ΄ν° μ‘°ν API
- ν μ΄λΈ μμ ―: μ λ ¬, νν°, νμ΄μ§λ€μ΄μ
- λμ보λ λ μ΄μμ μ μ₯/λΆλ¬μ€κΈ°
- μ¬μ©μλ³ λ©νΈλ¦ μ ν μ μ₯ (κ°μΈ λ·°)
- μ‘°κ±΄λΆ μμ: JSON μ€μ λ° UI νΈμ§κΈ° μ§μ
- μ»¬λΌ κ³ μ (freeze), μ»¬λΌ λλΉ μ‘°μ μ μ₯
- ν κ·Έλ£Ήν λ° μκ³/ν©κ³ ν
- λ릴λ€μ΄ (λΈλλ β μν β SKU)
- CSV / Excel λ΄λ³΄λ΄κΈ°
- κΊΎμμ κ·Έλν, λ§λ κ·Έλν, λμ λ§λ (ECharts)
- λ μ§ λ²μ νν° (μ μ 컨νΈλ‘€ ν¨λ)
- νν° κ° URL νλΌλ―Έν° λκΈ°ν (λ§ν¬ 곡μ )
- λμ보λ λΆλ§ν¬ / μ¦κ²¨μ°ΎκΈ°
- Admin νμ΄μ§: μ¬μ©μ μ΄λ, μν λ³κ²½
- κ°μ¬ λ‘κ·Έ (λκ° μΈμ μ΄λ€ λ°μ΄ν° μ‘°ν)
- λ°°μΉ ETL λͺ¨λν°λ§ (λ§μ§λ§ μ€ν μκ°, μ€ν¨ μλ¦Ό)
- HTTPS / μΈλΆ μΈν°λ· λ°°ν¬ μ§μ
μκ·λͺ¨ μ¬λ΄ νκ²½μμλ BigQueryλ₯Ό μ§μ μ‘°νν΄λ 무방νμ§λ§, μλ΅ μλ λ° λΉμ© μ΅μ νκ° νμνλ©΄ μ§κ³ κ²°κ³Όλ₯Ό PostgreSQLμ μΊμ±ν©λλ€.
λ§€μΌ μλ²½ Nμ (APScheduler / cron)
β FastAPI ETL μλΉμ€ μ€ν
β BigQueryμμ μΌλ³ μ§κ³ 쿼리 μ€ν
β κ²°κ³Όλ₯Ό PostgreSQL `daily_aggregates` ν
μ΄λΈμ upsert
β Redis μΊμ 무ν¨ν
λΈλΌμ°μ β GET /api/data/sales?date=2026-02-02&brand=λ₯ν°νΈλ£¨
β FastAPI β google-cloud-bigquery ν΄λΌμ΄μΈνΈλ‘ 쿼리 μ€ν
β κ²°κ³Ό JSON λ°ν β TanStack Queryκ° μΊμ± (staleTime μ€μ )
β TanStack Tableλ‘ λ λλ§
# μ 체 μ€ν μ€ν
docker compose up -d
# μλΉμ€ ꡬμ±
# - frontend: http://localhost:3000
# - backend: http://localhost:8000
# - nginx: http://localhost:80 (λ¨μΌ μ§μ
μ )
# - postgres: localhost:5432
# - redis: localhost:6379| νλͺ© | λ°©μ |
|---|---|
| μλ² | μ¬λ΄ μλ²μ κ³΅μΈ IP ν λΉ λλ AWS EC2 / GCP Compute Engine |
| HTTPS | Let's Encrypt + Nginx certbot |
| λλ©μΈ | μ¬λ΄ λλ©μΈ λλ μλΈλλ©μΈ μ€μ |
| CI/CD | GitHub Actions β Docker μ΄λ―Έμ§ λΉλ β μλ² SSH λ°°ν¬ |
| λ°±μ | PostgreSQL pg_dump μΌ 1ν cron + GCS/S3 μ λ‘λ |
Docker Compose κΈ°λ°μΌλ‘ κ°λ°νλ©΄ λ‘컬 β ν΄λΌμ°λ λ§μ΄κ·Έλ μ΄μ
μ docker-compose.prod.yml μ€λ²λΌμ΄λλ§μΌλ‘ νκ²½ μ νμ΄ κ°λ₯ν©λλ€.
2026 Q1 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ
β
ββ [Week 1-2] νλ‘μ νΈ κ³¨κ²© μΈν
β Docker Compose, PostgreSQL, FastAPI μ΄κΈ°ν
β Next.js νλ‘μ νΈ μΈν
, μΈμ¦ UI
β
ββ [Week 3-4] μΈμ¦ μμ± + BigQuery μ°κ²°
β JWT λ°κΈ/κ²μ¦, μν λ―Έλ€μ¨μ΄
β BigQuery μλΉμ€ κ³μ μ°λ, λ°μ΄ν° API
β
ββ [Week 5-6] MVP ν
μ΄λΈ μμ ―
β TanStack Table κΈ°λ° λ§€μΆ μμ½ ν
μ΄λΈ
β λ©νΈλ¦ μ ν, μ λ ¬, κΈ°λ³Έ νν°
β
ββ [Week 7-8] μ€μ μ μ₯ + μ‘°κ±΄λΆ μμ
β μ¬μ©μλ³ λ·° config DB μ μ₯/λΆλ¬μ€κΈ°
β μ‘°κ±΄λΆ μμ UI νΈμ§κΈ° + JSON νμ±
β
2026 Q2
ββ [Week 9-12] μ°¨νΈ μμ ― + λμ보λ λ μ΄μμ
β
ββ [Week 13+] μ΄μ κΈ°λ₯, μΈλΆ λ°°ν¬ μ€λΉ
μμΈ λ΄μ©μ κ° μ±μ READMEλ₯Ό μ°Έκ³ νμΈμ.
# μ μ₯μ ν΄λ‘
git clone https://github.com/cuz/lookflex.git
cd lookflex
# νκ²½ λ³μ 볡μ¬
cp .env.example .env
# .env νμΌμμ BigQuery μλΉμ€ κ³μ ν€ κ²½λ‘, DB λΉλ°λ²νΈ λ± μ€μ
# μ 체 μ€ν μ€ν
docker compose up -dLookFlex β Built to replace Looker Studio, one table at a time.