--- id: wiki-2026-0508-user-acquisition-ua title: User Acquisition (UA) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [UA, Paid Acquisition, Mobile UA] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [marketing, mobile, growth, performance-marketing] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: Python framework: AppsFlyer/Adjust SDK --- # User Acquisition (UA) ## 매 한 줄 > **"매 paid install 의 LTV-positive flow"**. 매 mobile 게임 의 lifeblood — 매 CPI < LTV(D180) 의 maintain. 매 2026 SKAdNetwork 4.0 + Privacy Sandbox 의 era — 매 deterministic attribution 의 종말, 매 probabilistic + MMM 의 부상. ## 매 핵심 ### 매 funnel 1. **Impression** — ad shown (CPM). 2. **Click** — user tap (CTR 1-3%). 3. **Install** — store install (IPM 0.5-2%). 4. **Activation** — first session, tutorial complete. 5. **Monetization** — IAP/ad revenue. 6. **Retention** — D1/D7/D30. ### 매 KPI - **CPI**: Cost Per Install ($0.30-$5). - **CPA**: Cost Per Action (purchase, level X). - **ROAS**: Return on Ad Spend — D7/D30/D90. - **LTV**: Lifetime Value (predicted D180/D360). - **Payback period**: 매 spend recovery 시점. ### 매 channels (2026) - **Self-attributing networks (SAN)**: Meta, TikTok, Google, Unity Ads, ironSource, AppLovin. - **DSPs**: Liftoff, Moloco, Vungle, Mintegral. - **Owned/cross-promo**: 매 portfolio publisher 만 의 leverage. - **Influencer**: TikTok creators, YouTube playthrough. ## 💻 패턴 ### LTV prediction (gradient boost on D7 features) ```python import lightgbm as lgb import pandas as pd def train_ltv_model(cohorts: pd.DataFrame): features = [ "sessions_d7", "iap_count_d7", "iap_value_d7", "ad_views_d7", "level_reached_d7", "session_len_avg_d7", "country", "platform", "channel" ] target = "ltv_d180" X, y = cohorts[features], cohorts[target] model = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.03, num_leaves=63, min_data_in_leaf=200) model.fit(X, y, categorical_feature=["country","platform","channel"]) return model def predict_ltv(model, user_d7_data): return model.predict(user_d7_data)[0] ``` ### Bid optimization (channel-level pacing) ```python def optimize_daily_bids(channels: list[str], budget: float) -> dict: perf = {c: get_recent_perf(c, days=3) for c in channels} target_roas = 1.20 # D30 break-even + margin bids = {} remaining = budget sorted_ch = sorted(channels, key=lambda c: perf[c]["pred_roas"], reverse=True) for c in sorted_ch: if perf[c]["pred_roas"] >= target_roas: spend = min(perf[c]["capacity"], remaining * 0.4) bids[c] = {"bid_cpi": perf[c]["target_cpi"], "budget": spend} remaining -= spend else: bids[c] = {"bid_cpi": perf[c]["target_cpi"] * 0.7, "budget": 0} return bids ``` ### SKAN 4.0 conversion value encoding ```swift // iOS 14.5+ SKAdNetwork 4.0 import StoreKit func updateSKANConversion(user: User) { let coarseValue: SKAdNetwork.CoarseConversionValue let fineValue: Int switch user.revenueD3 { case 0..<5: coarseValue = .low; fineValue = encodeFine(user) case 5..<25: coarseValue = .medium; fineValue = encodeFine(user) default: coarseValue = .high; fineValue = encodeFine(user) } SKAdNetwork.updatePostbackConversionValue( fineValue, coarseValue: coarseValue, lockWindow: false ) { error in if let e = error { Log.warn("SKAN: \(e)") } } } func encodeFine(_ u: User) -> Int { var v = 0 if u.tutorialDone { v |= 0x01 } if u.purchasedD3 { v |= 0x02 } if u.adImpressions > 5 { v |= 0x04 } return v & 0x3F // 6 bits } ``` ### Creative testing (Thompson sampling) ```python import numpy as np class CreativeBandit: def __init__(self, creatives: list[str]): self.alpha = {c: 1 for c in creatives} # installs self.beta = {c: 1 for c in creatives} # non-installs def select(self) -> str: samples = {c: np.random.beta(self.alpha[c], self.beta[c]) for c in self.alpha} return max(samples, key=samples.get) def update(self, creative: str, installed: bool): if installed: self.alpha[creative] += 1 else: self.beta[creative] += 1 ``` ### Media Mix Modeling (privacy-safe) ```python import statsmodels.api as sm def fit_mmm(weekly_data: pd.DataFrame): # Adstock + saturation transformations for ch in ["meta", "tiktok", "google", "applovin"]: weekly_data[f"{ch}_adstock"] = adstock(weekly_data[f"{ch}_spend"], decay=0.5) weekly_data[f"{ch}_sat"] = hill_saturation(weekly_data[f"{ch}_adstock"]) X = weekly_data[[f"{ch}_sat" for ch in CHANNELS] + ["seasonality"]] y = weekly_data["installs"] model = sm.OLS(y, sm.add_constant(X)).fit() return model ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | New game soft launch | $50-100K, 5-7 geos, 14-day window | | Scale phase | Channel diversify, 3+ networks | | iOS 14.5+ | SKAN 4.0 + probabilistic + MMM | | Android Privacy Sandbox | Topics API + on-device | | Unprofitable channel | Pause, retest creative quarterly | **기본값**: 매 D7 ROAS 25% gate + 매 portfolio diversification across 3+ networks. ## 🔗 Graph - 부모: [[Performance Marketing]] - 변형: [[ASO]] - 응용: [[CPI (Cost Per Install)]] · [[Live Operations (LiveOps)]] ## 🤖 LLM 활용 **언제**: Creative copy variants, ad concept brainstorming, channel performance summary. **언제 X**: 매 actual bid 의 결정 — 매 model + human 의 영역. ## ❌ 안티패턴 - **Last-click attribution only**: 매 cross-channel synergy 의 무시 — MMM 미사용. - **Vanity CPI focus**: 매 cheap install 추구 → 매 low-LTV cohort 의 floods. - **No creative refresh**: 매 ad fatigue 무시 — 매 2-week cycle 필요. - **Geo over-concentration**: 매 US/UK only 의 risk — 매 emerging market 의 ignore. ## 🧪 검증 / 중복 - Verified (AppsFlyer 2026 mobile marketing index, Adjust mobile growth report 2025). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — UA full lifecycle w/ SKAN 4.0 + MMM |