Google Analytics를 대체하는 오픈소스 웹 분석. 쿠키·개인정보 0개, GDPR 자동 준수, 셀프호스팅 가능.
왜 GA의 대안이 필요해졌나.
2010년대 인터넷은 "공짜 분석 도구 = Google Analytics"가 공식이었다. 그런데 GA는 사용자 데이터를 광고에 활용한다 — 즉 방문자가 진짜 상품이다. EU의 GDPR(2018)과 미국 캘리포니아 CCPA가 등장하면서, 사이트 소유자가 GA를 쓰는 것만으로 법적 리스크를 떠안는 시대가 됐다.
여기에 2023년 Google이 Universal Analytics를 강제 종료시키고 복잡한 GA4로 옮기게 만들면서 결정타. 사이트 소유자들은 "더 단순하고, 동의 배너 필요 없고, 데이터 주권이 있는" 분석 도구를 찾기 시작했다. Plausible은 이 흐름의 한가운데에서 가장 깔끔한 대안으로 떠올랐다.
docker compose up 한 줄로 셀프호스팅 가능 (Community Edition).한 문장으로 줄이면.
Plausible의 가치는 네 가지로 정리된다 — (1) 가벼움(GA보다 54배 작은 트래커), (2) 프라이버시(쿠키 0개, 개인 식별자 0개), (3) 단순함(모든 핵심 지표가 한 페이지), (4) 오픈소스(AGPL-3.0, 셀프호스팅 가능).
단순한 "GA 카피"가 아니라, 비즈니스 모델 자체와 결별한 도구다. 데이터가 곧 상품인 모델 대신 "분석 도구"라는 상품을 직접 판다.
시대적 배경 + 경쟁 우위.
| 비교 항목 | Plausible | Google Analytics | Matomo |
|---|---|---|---|
| 쿠키 사용 | 없음 | 있음 (배너 필수) | 선택 가능 |
| 스크립트 크기 | 약 1KB | 약 54KB | 약 22KB |
| GDPR 준수 | 기본 준수 | 설정 필요 | 설정 필요 |
| 대시보드 UX | 단일 페이지 | 복잡한 메뉴 | GA 스타일 |
| 셀프호스팅 | AGPL CE | 불가 | GPL |
| 가격(클라우드) | $9~19/월 | 무료(데이터로 결제) | $23~/월 |
"우리는 당신의 사용자 데이터로 돈을 벌지 않는다." Google처럼 광고로 무료 제공하는 대신 구독료를 받는다. 데이터가 곧 상품인 GA의 비즈니스 모델 자체와 결별한 제품. 사용자 입장에선 "내가 상품이 아니라 진짜 고객"이 된다.
백엔드 · 프론트엔드 · 트래커 · 인프라.
tailwind ~> 0.4.0 Elixir 패키지로 빌드 통합plausible.js는 메인 앱과 분리tracker/ 디렉토리에서 독립 관리| DB | 용도 | 왜 이걸 썼나 |
|---|---|---|
| PostgreSQL 16 | 사용자 계정, 사이트 설정, 팀, 구독 정보 | OLTP(트랜잭션) 부하, 관계형 데이터 처리. 정합성 중요한 메타데이터용 |
| ClickHouse 24.12 | 페이지뷰 이벤트, 세션 데이터 (수십억 행) | 컬럼 지향 OLAP. 집계 쿼리가 PostgreSQL보다 100~1000배 빠름 |
전체 구조도 → 핵심 패턴 하나하나.
┌──────────────────────────────────────────────────────────────────┐
│ Plausible Analytics 전체 흐름 │
└──────────────────────────────────────────────────────────────────┘
[당신의 웹사이트] [Plausible 인스턴스]
┌─────────────────┐ ┌──────────────────────────┐
│ <script │ ① 페이지 로드 │ │
│ plausible.js │ ──────────────────► │ Phoenix 웹 서버 │
│ /> │ │ (Elixir/Erlang VM/OTP) │
│ │ ② POST /api/event│ │
│ 방문자 │ ──── (JSON) ──────►│ ┌────────────────────┐ │
└─────────────────┘ │ │ /api/event 핸들러 │ │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ 봇/스팸 필터링 │ │
│ │ User-Agent 파싱 │ │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ IP → 국가/도시 변환│ │
│ │ (locus + MaxMind) │ │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ siphash로 일일 │ │
│ │ 익명 user_id 생성 │ │
│ │ (IP/UA 즉시 폐기) │ │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ClickHouse │ │
│ │ (이벤트 테이블) │ │
│ │ timestamp/domain│ │
│ │ country/browser │ │
│ └──────────────────┘ │
│ │
[대시보드 사용자] │ ┌──────────────────┐ │
┌─────────────────┐ │ │ PostgreSQL │ │
│ │ ③ 대시보드 접속 │ │ (메타 데이터) │ │
│ 사이트 소유자 │ ─────────────────► │ │ 사용자/사이트/ │ │
│ Phoenix │ │ │ 팀/구독 │ │
│ LiveView │ ④ 통계 집계 쿼리 │ └──────────────────┘ │
│ + React │ ◄───────────────── │ │
│ │ (실시간 푸시) │ ┌──────────────────┐ │
└─────────────────┘ │ │ Oban Workers │ │
│ │ 이메일 보고서/ │ │
│ │ export/정리 │ │
│ └──────────────────┘ │
└──────────────────────────┘
"한 DB로 다 처리하지 않는다"는 결정이 Plausible 성능의 핵심이다. 메타데이터는 PostgreSQL, 이벤트는 ClickHouse로 분리한다.
하루 1000만 페이지뷰 사이트에서 "지난 30일간 국가별 방문자 수"를 집계하려면 3억 행을 GROUP BY해야 한다. PostgreSQL은 행 기반(row-oriented)이라 이런 쿼리에 수십 초가 걸린다.
컬럼 기반(column-oriented) ClickHouse는 같은 쿼리를 0.1초 이내에 처리한다. 컬럼이 옆에 모여 있어 디스크 I/O와 캐시 효율이 압도적이다.
"개인정보를 저장한 적이 없으면 GDPR을 어길 일도 없다"가 Plausible의 철학. 핵심 트릭은 일일 솔트 + SipHash로 매일 다른 익명 ID를 생성하는 것.
# 일일 익명 user_id 생성 의사 코드 user_id = SipHash(daily_salt + website_domain + user_ip + user_agent) # - daily_salt: 매일 자정에 폐기되고 새로 생성됨 # → 다음날엔 같은 사람도 다른 user_id # - IP/User-Agent는 ID 생성 후 즉시 폐기, DB에 저장 안 됨 # - 따라서 사용자 추적이 24시간 이내로 제한됨
나이트클럽에서 입장 시 손등에 도장을 찍는 것과 같다. 그날 안에서는 "이 사람 들어왔다 나갔다"를 알 수 있지만, 다음 날엔 도장이 사라져서 어제 그 사람인지 알 수 없다. 24시간 단위 익명 추적이라는 GDPR-친화적 설계.
전통적인 SPA는 "프론트엔드가 API 폴링" 방식이다. Plausible은 서버가 LiveView 프로세스를 유지하면서 변경분만 WebSocket으로 푸시한다. 30초 폴링이 아니라 진짜 실시간이다.
Erlang/OTP의 "let it crash" 철학. 이벤트 ingestion 프로세스가 죽어도 대시보드는 멀쩡하고, Oban worker가 죽어도 웹 응답엔 영향 없다. 슈퍼바이저가 자동 재시작한다.
한 층에서 불이 나도 다른 층으로 번지지 않는 방화벽처럼, OTP는 프로세스를 격리한다. 한 이벤트 처리 프로세스가 예외로 죽어도 다른 수만 개의 프로세스는 영향 없이 계속 동작. 슈퍼바이저는 "관리실 알람"처럼 죽은 프로세스를 감지해 즉시 재시작.
어디에 뭐가 있는지.
plausible/analytics/ ├── lib/ # Elixir 메인 코드 (전체의 80%) │ ├── plausible/ # 도메인 로직 (사용자, 사이트, 통계 등) │ ├── plausible_web/ # Phoenix 컨트롤러, LiveView, 라우터 │ └── plausible_release/ # 릴리즈 빌드 로직 │ ├── assets/ # 프론트엔드 자산 │ ├── js/ # React + TypeScript 컴포넌트 │ ├── css/ # Tailwind 설정 및 커스텀 CSS │ └── package.json # npm 의존성 │ ├── tracker/ # 별도 npm 패키지 (MIT 라이선스) │ └── src/ # plausible.js 트래커 소스 │ ├── priv/ # Phoenix 정적 자산 │ ├── repo/migrations/ # PostgreSQL 스키마 마이그레이션 │ ├── ingest_repo/migrations/ # ClickHouse 스키마 마이그레이션 │ └── static/ # 컴파일된 JS/CSS, 이미지 │ ├── config/ # 환경별 설정 │ ├── config.exs # 공통 설정 │ ├── runtime.exs # 런타임 환경 변수 처리 │ └── ce.exs # Community Edition 전용 │ ├── extra/ # 클라우드 전용 (자체 호스팅엔 없음) │ └── lib/ # SSO, 사이트 API, 펀넬 등 프리미엄 기능 │ ├── e2e/ # Playwright E2E 테스트 ├── test/ # ExUnit 단위/통합 테스트 ├── rel/ # Elixir 릴리즈 설정 (docker-entrypoint.sh) │ ├── Dockerfile # 멀티 스테이지 Alpine 빌드 ├── mix.exs # Elixir 프로젝트 정의 (package.json 격) └── mix.lock # 의존성 잠금 파일
extra/는 "오픈코어 모델"의 실제 구현 예시다. 클라우드에만 있는 기능(SSO, 펀넬, 사이트 API)을 별도 디렉토리에 격리하고, mix.exs의 elixirc_paths(:ce)가 "extra/lib"를 제외하도록 설계되어 있다. 같은 코드베이스에서 두 가지 제품을 빌드하는 영리한 방법.
기술 영역마다 무엇을 가져갈지.
실습: 로컬에서 페이지뷰 1000개를 ingestion 해보고, BEAM 프로세스 모니터(observer_cli)로 부하 분포 관찰
실습: priv/ingest_repo/migrations/의 스키마를 읽고 왜 특정 컬럼을 ORDER BY 키로 잡았는지 분석
mix release)실습: Dockerfile을 분석하고 빌더 스테이지에서만 필요한 의존성과 런타임에 필요한 것을 구분
실습: Jaeger 컨테이너 띄우고 Plausible 트레이스 시각화
실습: 새 Oban worker 추가해서 매일 자정 통계 요약 이메일 보내기
실습: 자신의 사이드 프로젝트에 익명 ID 방식 적용해보기
셀프호스팅 최소/권장 사양.
| 리소스 | 최소 | 권장 |
|---|---|---|
| CPU | SSE 4.2 또는 NEON 지원 (ClickHouse 요구사항) | 2 vCPU 이상 |
| RAM | 2 GB | 4 GB+ (트래픽 많으면 8 GB) |
| 디스크 | 10 GB | 50 GB SSD (이벤트 누적용) |
| 네트워크 | 도메인 + DNS A 레코드 | 고정 IP + 80/443 포트 개방 |
| OS | Linux (Docker 지원) | Ubuntu 22.04 LTS |
2GB 머신에서 돌리려면 community-edition/clickhouse/low-resources.xml과 default-profile-low-resources-overrides.xml의 설정을 그대로 사용해야 한다. low-resources.xml은 mark_cache_size(524MB)를 줄이고, default-profile-low-resources-overrides.xml은 max_threads=1 등 병렬 처리 수를 제한해 메모리 사용량을 간접적으로 낮춘다. 안 그러면 OOM Killer가 ClickHouse를 죽인다.
analytics.yourdomain.com)난이도별 실습 — Easy → Hard.
1) 레포 clone 2) .env 작성 (BASE_URL, SECRET_KEY_BASE) 3) compose override로 포트 8000 노출 4) docker compose up -d 5) http://localhost:8000 접속 후 첫 사용자 등록
git clone -b v3.2.0 --single-branch \
https://github.com/plausible/community-edition plausible-ce
cd plausible-ce
echo "BASE_URL=http://localhost:8000" > .env
echo "SECRET_KEY_BASE=$(openssl rand -base64 48)" >> .env
cat > compose.override.yml << EOF
services:
plausible:
ports:
- 8000:80
EOF
docker compose up -d
배우는 것: Docker Compose의 서비스 의존성(depends_on), 헬스체크, 볼륨 영속성
1) 대시보드에서 API 키 발급 2) /api/v1/stats 엔드포인트로 JSON 호출 3) Python/Node로 일일 방문자 그래프를 별도 페이지에 그려보기 4) 시계열 차트 라이브러리 (Chart.js, Recharts) 연동
배우는 것: REST API 인증, 시계열 데이터 처리, 차트 라이브러리 사용
1) React/Next.js 사이드 프로젝트 준비 2) plausible.js 스크립트 삽입 3) history.pushState 후킹으로 라우트 변경 → 페이지뷰 전송 4) 커스텀 이벤트 (plausible('Signup')) 발송 실험 5) 대시보드에서 이벤트 확인
배우는 것: SPA 라우팅과 분석의 충돌, 커스텀 이벤트 API 설계
1) Docker 컨테이너가 아니라 직접 Elixir/Phoenix 빌드 2) asdf install로 .tool-versions 기준 Elixir/Erlang 설치 3) mix setup으로 의존성 설치/DB 생성/마이그레이션 4) mix phx.server로 개발 서버 실행
asdf install # .tool-versions 기준 Elixir/Erlang 설치 mix setup # 의존성 설치, DB 생성, 마이그레이션 mix phx.server # 개발 서버 실행
배우는 것: Mix 빌드 시스템, Phoenix 개발 워크플로우, ClickHouse 로컬 설정
1) 예시 목표: "OS 종류별 트래픽" 그래프 2) ClickHouse 스키마에 컬럼 추가 (priv/ingest_repo/migrations/) 3) 트래커가 OS 정보를 보내도록 수정 4) 백엔드 집계 쿼리 추가 5) LiveView UI에 그래프 추가 → 풀스택 PR
배우는 것: Plausible 코드베이스 탐색, ClickHouse 마이그레이션, LiveView 컴포넌트 작성
1) 여러 노드를 libcluster로 묶기 2) 로드 밸런서 뒤에 두 개의 Plausible 인스턴스 3) ClickHouse를 replicated 모드로 설정 4) 무중단 배포 시도 (Erlang 핫 리로드)
배우는 것: Erlang 분산 시스템, ClickHouse replication, 무중단 배포
주차별로 따라가면 Plausible 스택 전체 마스터.
"Elixir School" 1-3장 / 패턴 매칭, 파이프 연산자, 모듈/함수 정의 → 간단한 CLI 계산기 만들기
GenServer, Supervisor, Agent / 액터 모델 vs 스레드 모델 비교 → 동시 다운로드 매니저 (10개 URL 병렬)
라우터, 컨트롤러, 뷰 / Ecto: 스키마, 체인지셋, 쿼리 → 간단한 블로그 시스템
플러그(Plug) 미들웨어, 인증 / "Real-Time Phoenix" 책 참고 → 사용자 인증 추가
LiveView 라이프사이클 / WebSocket 통신과 변경분 패치 → 실시간 채팅 또는 라이브 투표
MergeTree 엔진 종류 (Replicated, Aggregating 등) / 파티셔닝과 인덱싱 전략 → 100만 행 로그 데이터 적재 + 다양한 집계 쿼리
트레이스/메트릭/로그의 차이 / Jaeger, Prometheus, Grafana 스택 → 자신의 앱에 OTel 계측 추가하고 분산 추적
"good first issue" 라벨 이슈 찾기 / 로컬 개발 환경에서 작은 PR 작성 → 커뮤니티 코드 리뷰 받기
자주 마주칠 단어들.
FROM을 두고, 빌더 이미지에서 컴파일한 결과물만 최종 가벼운 이미지에 복사. Plausible은 빌더에 Elixir+Node, 런타임엔 Alpine만 남김.공식 · 학습 · 배경 글.
community-edition 레포 clone → .env 작성 → docker compose up -d. 30분 안에 http://localhost:8000에서 첫 사용자 등록까지.<script defer data-domain="..." src="..."> 한 줄로 끝. GA 스크립트(54KB)와 Plausible 트래커(1KB)의 네트워크 탭 차이를 직접 비교.:siphash.hash24로 (도메인+IP+UA+날짜) 해시를 직접 만들어보고, 다음날 같은 입력이 다른 ID로 나오는지 확인. 24시간 추적 한계가 어떻게 작동하는지 체감./api/v1/stats를 호출해 일일 방문자 그래프를 본인의 다른 페이지에 삽입. API 인증·시계열 데이터 처리 패턴 학습.docker exec로 들어가 SELECT country_code, count() FROM events GROUP BY country_code. 같은 쿼리를 PostgreSQL에서 가상으로 돌렸을 때와 속도 차이 상상해보기.