
Grovs는 모바일 앱의 딥링크, 설치 유입 분석, 캠페인 분석과 사용자 참여 기능을 자체 서버에서 운영하도록 제공하는 오픈소스 플랫폼이다. 제작진은 Branch, AppsFlyer, Amplitude, Braze, OneSignal을 대체하는 단일 플랫폼이라고 소개한다.
백엔드 저장소 · 자체 호스팅 저장소 · 문서
Grovs는 모바일 측정 파트너(MMP)가 제공하는 딥링크, 설치 유입 분석, 캠페인 분석 기능을 제공하면서 광고 네트워크와 사용자 데이터를 공유하지 않는다고 설명한다. 광고 네트워크 제휴, 앱 간 추적, 확률적 지문 채취도 하지 않는다고 밝힌다. 이 저장소에는 Rails API, 링크 확인기, SDK 엔드포인트, 분석 파이프라인, 백그라운드 작업자가 포함된다.
주요 기능은 다음과 같다.
- iOS, Android, 웹에서 작동하는 단축 링크와 지연 딥링크를 제공한다. 클립보드 기반 매칭도 지원한다.
- 설치, 앱 열기, 재설치, 추천 유입을 출처와 연결해 추적한다.
- 일별 지표, 방문자 통계, 캠페인 성과, 세션, 유지율을 보여주는 실시간 대시보드를 제공한다. 분석 저장소로 ClickHouse를 선택적으로 사용할 수 있다.
- Cloudflare for SaaS 또는 자체 호스팅 환경의 수동 DNS 설정으로 사용자 지정 도메인에서 링크를 제공한다.
- Branch.io 또는 AppsFlyer 링크의 이전을 지원한다. 기존 링크를 처음 클릭하면 해당 서비스에서 링크를 확인한 뒤 Grovs 네이티브 링크를 만든다.
- 웹사이트용 앱 다운로드 배너를 제공하며, 배너를 앱 딥링크로 연결한다.
- 지표, 필터, 분류 기준을 조합한 분석 대시보드를 저장할 수 있다.
- 모델 컨텍스트 프로토콜(MCP) 서버를 통해 AI 도우미에서 링크와 분석 정보를 읽을 수 있다.
- 하나의 인스턴스에서 여러 플랫폼의 여러 앱을 운영할 수 있다.
- 엔터프라이즈 에디션에는 세그먼트, 푸시 캠페인, 인앱 메시지, 여정, 이벤트 스트리밍, 매출 추적, 감사 로그, SCIM을 통한 엔터프라이즈 SSO가 포함된다. 엔터프라이즈 에디션 안내를 참고하면 된다.
기술 구성과 구조
백엔드는 Ruby on Rails 8.1과 Ruby 3.4를 사용한다. 데이터베이스는 PostgreSQL 16이며, 선택 사항인 분석 저장소는 ClickHouse 25.3 이상이다. 캐시와 큐에는 Redis 6을 사용하고, 백그라운드 작업은 sidekiq-scheduler를 포함한 Sidekiq로 처리한다. 인증에는 Devise, Doorkeeper(OAuth2), OmniAuth(Google, Microsoft)를 사용하며, 엔터프라이즈 에디션은 OIDC와 SCIM도 지원한다. 파일은 ActiveStorage를 통해 S3 또는 S3 호환 저장소에 보관할 수 있다. 결제에는 Stripe와 인앱 구매(IAP)용 App Store Server API 및 Google Play Developer API를 사용하며, 푸시 알림은 RPush로 APNs와 FCM에 전달한다. 배포 방식은 Docker, Kamal, docker-compose다.
React 대시보드는 Doorkeeper OAuth2를 통해 Rails API에 연결된다. 모바일 SDK는 SDK API로, Apple과 Google 웹훅은 웹훅 API로 데이터를 보낸다. Sidekiq 작업자는 Redis를 사용하며, ClickHouse는 선택 가능한 분석 저장소다.
앱은 멀티테넌트 구조다. 최상위 테넌트는 인스턴스이며, 각 인스턴스에는 테스트용 프로젝트와 프로덕션용 프로젝트가 있다. 사용자는 InstanceRole을 통해 인스턴스에 속하고, 도메인은 프로젝트에 연결돼 단축 링크를 처리한다.
SDK와 링크 클릭에서 들어온 이벤트는 Redis 큐에 쌓인 뒤 묶음 단위로 처리돼 일별 통계에 반영된다. ClickHouse를 활성화하면 같은 이벤트를 ClickHouse에도 기록하고, 분석 대시보드는 ClickHouse의 롤업 데이터를 읽는다.
API는 서브도메인에 따라 라우팅된다. api.*는 CRUD, 분석, 설정을 담당하는 대시보드 API이며 Doorkeeper OAuth2로 인증한다. sdk.*는 이벤트, 링크, 구매를 처리하는 모바일 SDK API로 기기 지문을 사용한다. go.*는 인증 없이 단축 링크를 리디렉션하고, preview.*는 인증 없이 링크 미리보기를 제공한다. mcp.*는 OAuth 2.1로 인증하는 AI 도구용 MCP 서버다. <project>.<domain>은 기본 도메인과 사용자 지정 도메인의 프로젝트 딥링크를 제공하며 인증은 필요하지 않다.
SERVER_HOST에는 api. 호스트가 아니라 links.example.com과 같은 기본 도메인을 지정해야 한다. 예약된 서브도메인은 이 값을 바탕으로 만들어진다. 세 개 이상의 레이블로 구성된 중첩 도메인도 지원한다.
설치와 실행
백엔드, 대시보드, 데이터베이스, TLS 프록시를 포함한 전체 구성을 가장 빠르게 실행하려면 자체 호스팅 저장소를 이용한다. 설치 프로그램 하나로 설치하거나 Railway, Render, Coolify, Dokploy 및 주요 클라우드의 원클릭 템플릿을 사용할 수 있다. 아래 절차는 백엔드만 실행하는 방법이다.
Docker Compose로 백엔드만 실행하기
소스 코드를 내려받지 않아도 된다. 공개된 Community Edition 이미지를 사용한다.
# 1. compose 파일과 환경 변수 템플릿을 내려받는다
mkdir grovs && cd grovs
curl -fsSLO https://github.com/grovs-io/backend/releases/latest/download/docker-compose.yml
curl -fsSL https://github.com/grovs-io/backend/releases/latest/download/.env.example -o .env
# 2. 비밀 키를 생성해 .env에 입력한다
docker compose run --rm --no-deps web bin/rails secret # SECRET_KEY_BASE
docker compose run --rm --no-deps web bin/rails db:encryption:init # 3개의 ACTIVE_RECORD_ENCRYPTION_* 키
# 최초 로그인에 사용할 BOOTSTRAP_ADMIN_EMAIL / BOOTSTRAP_ADMIN_PASSWORD도 설정한다.
# 3. 모든 서비스를 시작한다(최초 실행 때 데이터베이스 생성, 마이그레이션, 시드 작업을 수행한다)
docker compose up -d
API는 http://localhost:3000에서 수신 대기한다. .env의 WEB_PORT를 바꾸면 포트를 변경할 수 있다. 이 절차는 PostgreSQL, Redis, ClickHouse, Rails API와 Sidekiq 작업자 프로세스 5개를 시작한다. 업로드한 자산은 storage 볼륨에 저장되고 앱이 제공한다. 대신 S3나 MinIO를 쓰려면 ACTIVE_STORAGE_SERVICE=amazon과 AWS_S3_* 변수를 설정한다.
latest 대신 특정 릴리스를 고정하려면 .env에 GROVS_IMAGE=ghcr.io/grovs-io/backend:3.0.0을 지정한다. 소스 체크아웃에서 실행하려면 docker build -t grovs-backend .를 실행하고 GROVS_IMAGE=grovs-backend로 설정한다.
실제 배포에서는 SERVER_HOST, REACT_HOST, DOMAIN_LIVE, DOMAIN_TEST, S3_ASSET_PREFIX, PREVIEW_BASE_URL을 자체 도메인에 맞게 설정해야 한다. 또한 포트 3000 앞에 TLS를 종료하는 리버스 프록시를 두고 api., sdk., go., preview. 및 링크 서브도메인의 요청을 해당 포트로 전달해야 한다.
ClickHouse는 이벤트 저장소로 사용된다. .env.example에서는 CLICKHOUSE_PRIMARY=true로 설정돼 있으며, web 서비스는 시작할 때 ClickHouse 데이터베이스를 만들고 마이그레이션한다.
Docker 없이 로컬에서 개발하기
필요한 환경은 Ruby 3.4, PostgreSQL 16, Redis 6 이상이다. bin/dev로 분석 기능을 로컬에서 실행할 때만 ClickHouse가 필요하다.
# 의존성을 설치하고 git 훅을 설정한다
bin/setup
# 처음 실행할 때 암호화 키를 생성한다
bin/rails db:encryption:init
# 출력된 값을 .env에 추가한다
# 데이터베이스를 만들고 시드 데이터를 넣는다
bin/rails db:create db:migrate db:seed
# 모든 서비스(Rails, Sidekiq, ClickHouse)를 시작한다
bin/dev
bin/dev는 Foreman을 사용해 Procfile.dev에 정의된 모든 프로세스를 실행한다. 각 서비스의 로그에는 색상과 접두어가 붙는다. Ctrl+C를 누르면 모든 서비스를 종료한다.
일부 서비스만 실행하려면 다음 명령을 사용한다.
bin/dev -m web,sidekiq
설정
.env.example을 .env로 복사한 뒤 설정한다. 주요 변수는 다음과 같다.
- 암호화(필수):
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY,_DETERMINISTIC_KEY,_KEY_DERIVATION_SALT - Redis(필수):
REDIS_URL이며rediss://를 지원한다. 사설 CA를 사용하면REDIS_SSL_CA_FILE을 설정한다. - 서버(필수):
SERVER_HOST_PROTOCOL, 기본 도메인인SERVER_HOST,DOMAIN_LIVE,DOMAIN_TEST - 대시보드(필수):
REACT_HOST_PROTOCOL,REACT_HOST - 데이터베이스(프로덕션 전용):
DATABASE_URL - 객체 저장소(프로덕션 전용):
AWS_S3_KEY_ID,AWS_S3_ACCESS_KEY,AWS_S3_REGION,AWS_S3_BUCKET,S3_ASSET_PREFIX. MinIO나 Ceph에는S3_ENDPOINT를 사용한다. - 이메일(이메일 발송 시):
SENDGRID_API_KEY를 설정하거나MAILER_DELIVERY_METHOD=smtp와SMTP_*,MAILER_FROM을 설정한다. - Stripe(결제 시):
STRIPE_API_KEY,STRIPE_STANDARD_PRICE_ID,STRIPE_WEBHOOK_SECRET - OAuth/SSO(소셜 로그인 시):
GOOGLE_CLIENT_ID와SECRET,MICROSOFT_CLIENT_ID와SECRET. 각 공급자는 ID와 시크릿을 모두 설정해야 활성화된다. - GeoIP(지역 기능 사용 시): 번들에 포함되지 않은 MaxMind GeoLite2 City
.mmdb파일을 가리키는GEOIP_DB_PATH - 클라이언트 IP(프록시 뒤에서 운영할 때):
CLIENT_IP_HEADER. 기본값은CF-Connecting-IP이며, nginx 뒤에서는X-Real-IP, AWS ALB 뒤에서는X-Forwarded-For를 사용한다. - ClickHouse(선택 사항):
CLICKHOUSE_URL,CLICKHOUSE_WRITE_ENABLED,CLICKHOUSE_READ_ENABLED및 읽기 기능 플래그 - 기능 플래그(선택 사항):
GROVS_EE,GROVS_SELF_HOSTED,CUSTOM_DOMAINS_ENABLED,MIGRATIONS_ENABLED - 초기 설정(최초 배포):
BOOTSTRAP_ADMIN_EMAIL/_PASSWORD,OAUTH_CLIENT_UID/_SECRET
모든 변수의 전체 설명은 .env.example에 있다.
선택 사항인 ClickHouse 분석
기본 분석은 PostgreSQL에서 실행된다. ClickHouse를 이벤트 저장과 분석 조회에 사용할 수 있지만 모든 플래그의 기본값은 꺼져 있다. 따라서 ClickHouse 없이 설치해도 기존 동작은 달라지지 않는다.
CLICKHOUSE_URL기본값은http://localhost:8123이다. 인증 정보는http://user:pass@host:8123처럼 URL에 넣는다.CLICKHOUSE_DATABASE의 기본값은grovs_<env>다.CLICKHOUSE_WRITE_ENABLED는 기본값이false이며, 활성화하면 PostgreSQL과 함께 ClickHouse에도 이벤트를 쓴다.CLICKHOUSE_READ_ENABLED는 기본값이false이며, 활성화하면 분석 조회가 ClickHouse를 사용할 수 있다.CLICKHOUSE_ANALYTICS_ROLLUPS_READ_ENABLED는 기본값이false이며, 활성화하면 대시보드 수치를 ClickHouse 롤업에서 가져온다.CLICKHOUSE_ATTRIBUTION_READ_ENABLED는 기본값이false이며, 활성화하면 방문자별 유입 분석을 ClickHouse에서 가져온다.CLICKHOUSE_LINK_DIMENSIONS_READ_ENABLED는 기본값이false이며, 활성화하면 링크 목록과 지표를 ClickHouse에서 가져온다.CLICKHOUSE_PRIMARY는 기본값이false다. 활성화하면 이벤트를 ClickHouse에만 저장하고 PostgreSQL을 대체 저장 경로로 사용한다. 쓰기와 읽기를 모두 활성화해야 한다.CLICKHOUSE_ROLLUP_FAST_LANE은 기본값이false이며, 활성화하면 최신 대시보드 수치를 위해 현재 월의 롤업을 매분 다시 만든다.DASHBOARD_CACHE_TTL_SECONDS는 대시보드 캐시 유지 시간이며 기본값은300이다.
# 데이터베이스를 만들고 ClickHouse 마이그레이션을 실행한다
bin/rails clickhouse:setup
# 대기 중인 ClickHouse 마이그레이션만 적용한다
bin/rails clickhouse:migrate
# 롤업을 다시 만든다(데이터를 다시 채운 뒤 또는 읽기 플래그 변경 시)
bin/rails clickhouse:rebuild_rollups
플래그는 쓰기부터 켠 뒤 읽기를 활성화하고, 이어 롤업 및 유입 분석 읽기 플래그를 켠 다음 CLICKHOUSE_PRIMARY를 마지막에 켠다. bin/dev는 run_clickhouse.sh를 통해 로컬 ClickHouse를 시작한다. ClickHouse 25.3 이상이 필요하다.
엔터프라이즈 에디션
GROVS_EE=true로 설정하면 ee/ 아래 기능을 불러온다. 이 설정이 없으면 코어 에디션이 그대로 실행되며 엔터프라이즈 경로는 존재하지 않는다.
- 이벤트, 속성, 기기, 구매 데이터를 바탕으로 잠재고객을 구성하는 세그먼트 기능. 실시간 규모 추정과 CSV 내보내기를 지원한다.
- 세그먼트를 대상으로 APNs와 FCM 푸시 캠페인을 예약하거나 즉시 발송하고, 전달 및 열람을 보고하는 기능
- SDK에 표시할 템플릿, 설문, 사용자 지정 HTML 형식의 인앱 메시지. 타기팅, 노출 빈도 제한, 보고 기능을 포함한다.
- 푸시와 인앱 메시지를 연결하는 다단계 여정. 대기, 분기, A/B 경로를 지원한다.
- 원시 이벤트를 S3 호환 저장소나 웹훅 등 자체 목적지로 보내거나 피드에서 가져오는 이벤트 스트리밍
- Apple 및 Google 웹훅, 구매 검증, 매출 유입 분석을 지원하는 IAP 매출 추적
- 모든 관리자 작업을 변조 여부를 확인할 수 있는 해시 연결 기록으로 남기는 감사 로그. 인스턴스 관리자는 읽기 전용 내보내기 토큰을 발급하고, SIEM은
GET /api/v1/instances/:id/audit_events를 호출한다.bin/rails audit:verify[<instance_id>]로 체인을 확인할 수 있다. - 검증된 이메일 도메인을 이용하는 인스턴스별 OIDC 연결 방식의 엔터프라이즈 SSO.
PUT /api/v1/instances/:id/sso_connection에서 설정한다. - 인스턴스별 SCIM 토큰으로 인증해
/scim/v2에서 ID 공급자의 사용자 생성 및 비활성화를 처리하는 SCIM 2.0
엔터프라이즈 기능을 프로덕션에서 사용하려면 구독이 필요하다. 자세한 조건은 ee/LICENSE를 확인하면 된다.
자체 호스팅
단일 테넌트 배포에서는 GROVS_SELF_HOSTED=true로 설정한다. 이 설정은 서명된 S3 리디렉션 대신 앱에서 업로드 자산을 제공하고, SaaS 전용 시드 데이터를 건너뛴다. 결제 없이 사용자 지정 도메인 권한을 부여하며 감사 로그를 활성화한다.
- 최초 관리자: 첫
db:seed전에BOOTSTRAP_ADMIN_EMAIL과BOOTSTRAP_ADMIN_PASSWORD를 설정하거나, 나중에bin/rails grovs:ensure_bootstrap_admin을 실행한다. 대시보드 OAuth 클라이언트에 고정 자격 증명을 사용하려면OAUTH_CLIENT_UID와OAUTH_CLIENT_SECRET도 설정한다. - 이메일:
MAILER_DELIVERY_METHOD=smtp와SMTP_ADDRESS,SMTP_PORT,SMTP_USERNAME,SMTP_PASSWORD,SMTP_DOMAIN,MAILER_FROM을 설정한다. - Cloudflare 없는 사용자 지정 도메인:
CUSTOM_DOMAINS_ENABLED=true,CUSTOM_DOMAINS_PROVIDER=manual로 설정한다. 고객 도메인은SELF_HOSTED_INGRESS_HOST에 CNAME을 설정하며, 기본값은SERVER_HOST다. HTTPS 자체 확인, 검증 엔드포인트 또는bin/rails grovs:custom_domain:activate[<hostname>]명령으로 도메인을 활성화한다. - 경쟁 서비스에서 이전:
MIGRATIONS_ENABLED=true로 설정한 뒤 프로젝트별로 Branch 또는 AppsFlyer 자격 증명을 사용해 이전 소스를 만든다.xyz.app.link처럼 공급자가 소유한 도메인을 공급자 호스팅 소스로 연결하면 SDK 측에서 링크를 확인할 수 있다. - 로그:
RAILS_LOG_LEVEL의 기본값은error이며 로그는 표준 출력으로 보낸다. - 상태 확인:
GET /up은 인증이나 데이터베이스 왕복 요청 없이 사용할 수 있다.
백그라운드 작업자
앱은 전용 큐를 사용하는 Sidekiq 프로세스 5개를 실행한다.
worker:sidekiq_worker.yml설정, 동시성 40(SIDEKIQ_EVENTS_CONCURRENCY), SDK 이벤트 수집batch:sidekiq_batch.yml설정, 동시성 3(SIDEKIQ_BATCH_CONCURRENCY), 이벤트 묶음 처리scheduler:sidekiq_scheduler.yml설정, 동시성 1, 크론 기반 예약 작업device_updates:sidekiq_device_updates.yml설정, 동시성 10, 기기 메타데이터 갱신maintenance:sidekiq_maintenance.yml설정, 동시성 5, 과거 데이터 채우기, 롤업 재구축, 유지 관리
bin/dev를 실행하면 모든 작업자가 자동으로 시작된다. 작업자를 개별 실행하려면 다음 명령을 사용한다.
bundle exec sidekiq -C config/sidekiq_worker.yml
테스트와 코드 스타일
프로젝트는 픽스처를 사용하는 Minitest를 쓴다. .env.test에는 테스트를 바로 실행할 수 있도록 결정론적 더미 값이 포함돼 있다.
# 코어와 엔터프라이즈 에디션 테스트 전체 실행(변경 사항을 푸시하기 전에 실행)
bin/rails test:full
# 코어 테스트만 실행(test/)
bin/rails test
# 엔터프라이즈 에디션 테스트만 실행(ee/test, GROVS_EE=true 설정)
bin/rails test:ee
# 특정 파일이나 디렉터리 실행
bin/rails test test/models/device_test.rb
bin/rails test test/services/
CLICKHOUSE_URL에 연결 가능한 ClickHouse가 없으면 ClickHouse 기반 테스트는 건너뛴다. CI처럼 연결이 없을 때 실패하도록 하려면 CLICKHOUSE_REQUIRED=true로 설정한다.
코드 스타일은 RuboCop으로 검사한다. bin/setup을 실행했다면 커밋 전 훅이 자동으로 동작한다.
# RuboCop 실행
bundle exec rubocop
# 안전한 위반 항목 자동 수정
bundle exec rubocop -a
프로젝트 구조는 app/ 아래에 API 및 SDK 컨트롤러, 공개 링크 리디렉션·표시 컨트롤러, 모델, 서비스, 작업, 메일러를 둔다. ee/에는 세그먼트, 푸시, 인앱 메시지, 여정, 이벤트 스트리밍, 매출, 감사 로그, SSO/SCIM 기능과 별도 테스트가 있다. config/에는 Sidekiq 프로세스 설정과 라우트가, db/에는 스키마와 PostgreSQL 및 ClickHouse 마이그레이션이 있다. test/에는 픽스처를 사용하는 Minitest 테스트가 있으며, lib/에는 ClickHouse 마이그레이션 프레임워크와 지표, 과거 데이터 처리, ClickHouse, 디버깅용 작업이 있다.
배포
자체 호스팅 Docker Compose
ghcr.io/grovs-io/backend는 Community Edition 이미지다. GROVS_EE=false로 Dockerfile에서 빌드하며, 빌드 과정에서 ee/ 디렉터리를 삭제한다. 따라서 세그먼트, 푸시, 인앱 메시지, 여정, 이벤트 스트리밍, 매출, 감사 로그, OIDC/SCIM을 포함하는 엔터프라이즈 에디션은 이미지에 없으며 환경 변수로 활성화할 수 없다. 이미지는 루트가 아닌 사용자로 실행되고 자산은 미리 컴파일된다. docker-compose.yml은 하나의 이미지에서 모든 역할을 실행한다. 업그레이드할 때는 .env의 GROVS_IMAGE를 바꾼 뒤 다음을 실행한다.
# .env의 GROVS_IMAGE를 바꾼 다음 실행한다
docker compose pull && docker compose up -d # web이 db:prepare를 실행하므로 시작 시 대기 중인 마이그레이션을 적용한다
릴리스는 Actions -> Release에서 수동으로 만든다(.github/workflows/release.yml). 매번 코어와 엔터프라이즈 테스트, bundler-audit, Brakeman, 빌드 이미지의 Trivy CVE 검사, GROVS_EE=true로 compose 파일을 실행해 해당 플래그가 영향을 주지 않는지 확인하는 절차를 거친다. "publish"를 켜면 SBOM과 출처 정보(provenance)를 포함한 amd64 및 arm64 이미지를 올리고 v* 태그를 만든 뒤, 이 compose 파일과 .env.example을 GitHub 릴리스에 첨부한다.
Kamal
config/deploy.yml.example을 config/deploy.yml로 복사하고 서버 IP와 레지스트리 자격 증명을 입력한다.
# 최초 설정
bundle exec kamal setup
# 배포
bundle exec kamal deploy
SDK
- iOS: grovs-io/grovs-ios
- Android: grovs-io/grovs-android
- React Native: grovs-io/grovs-react-native
- Flutter: grovs-io/grovs-flutter
- Unity: grovs-io/grovs-unity
- 웹: grovs-io/grovs-js
기여와 라이선스
기여하려면 저장소를 포크하고 기능 브랜치(git checkout -b my-feature)를 만든 뒤 변경 사항을 적용한다. 테스트(bin/rails test:full)를 통과하고 RuboCop 검사(bundle exec rubocop)를 마친 다음 변경 사항을 커밋해 포크에 푸시하고 풀 리퀘스트를 연다. 큰 변경은 먼저 이슈를 열어 접근 방식을 논의하도록 안내한다.
Grovs는 이중 라이선스 모델을 사용한다. ee/ 밖의 코어는 MIT 라이선스로 제공되며 자유롭게 사용, 수정, 배포할 수 있다. ee/의 세그먼트, 푸시 캠페인, 인앱 메시지, 여정, 이벤트 스트리밍, 매출 추적, 감사 로그, 엔터프라이즈 SSO와 SCIM은 Grovs Enterprise License에 따라 제공된다. 엔터프라이즈 기능을 프로덕션에서 사용하려면 유효한 구독이 필요하다. 전체 조건은 LICENSE와 ee/LICENSE를 확인하면 된다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요