|
| 1 | +# Event Volume Drop and Missing Super Properties After Unity SDK Migration |
| 2 | + |
| 3 | +## Issue Summary |
| 4 | +After migrating from native SDKs (Android 5.3.0 and custom iOS wrapper) to mixpanel-unity v3.5.3, customers are experiencing: |
| 5 | + |
| 6 | +1. **30-44% drop in event volume** for App Start and App Exit events on Android (Google Play) only |
| 7 | +2. **Missing super properties** on the first event fired in each session (App Start event) on both iOS and Android |
| 8 | +3. Events fired between App Start and App Exit work normally with super properties present |
| 9 | + |
| 10 | +## Environment |
| 11 | +- **SDK Version**: mixpanel-unity v3.5.3 |
| 12 | +- **Platform**: Android (Google Play) - event volume drop; Both iOS and Android - missing super properties |
| 13 | +- **Migration**: From Android SDK 5.3.0 (Gradle) and iOS custom wrapper (commit 0b982aab58e832ad85a8d9dca4d9729aa3948b53) |
| 14 | +- **Unity Version**: Not specified |
| 15 | +- **Deployment**: Production environment |
| 16 | + |
| 17 | +## Affected Games |
| 18 | +- Sago Mini School (rollout Oct 1st, 2025) |
| 19 | +- Sago Mini World (also released with SDK) |
| 20 | +- Project: Sago Mini - Production |
| 21 | + |
| 22 | +## Detailed Symptoms |
| 23 | + |
| 24 | +### 1. Event Volume Drop (Android Only) |
| 25 | +- **Metric**: ~30% drop in total events, ~44% drop in unique users for App Start and App Exit |
| 26 | +- **Comparison**: Sep 30th vs 30 days prior (pre-migration baseline) |
| 27 | +- **Pattern**: |
| 28 | + - App Start events: Significantly reduced |
| 29 | + - App Exit events: Significantly reduced |
| 30 | + - Mid-session events: Normal volume |
| 31 | +- **iOS**: No event volume drop observed |
| 32 | +- **Dashboard**: https://mixpanel.com/project/210340/view/23451/app/insights#fTLhAYFBMnRZ |
| 33 | + |
| 34 | +### 2. Missing Super Properties (iOS + Android) |
| 35 | +- **Scope**: First event of every session (App Start event) |
| 36 | +- **Behavior**: Super properties that should be present are completely missing |
| 37 | +- **Registration**: Super property registration code executes at same location as before migration |
| 38 | +- **Later Events**: All events after the first event in a session include super properties correctly |
| 39 | + |
| 40 | +### 3. Not Reproducible Locally |
| 41 | +- Issue only manifests in production with real users |
| 42 | +- Local testing (download from Play Store, test locally) does not reproduce the issue |
| 43 | +- This suggests timing-related issue or device-specific behavior |
| 44 | + |
| 45 | +## Customer's Investigation |
| 46 | + |
| 47 | +### What They Checked |
| 48 | +- ✅ Initialization paths appear to run (Mixpanel.Init + subsequent calls) |
| 49 | +- ✅ Super property registration code still executes (same location as before) |
| 50 | +- ✅ No recent logic changes that would gate or suppress tracking |
| 51 | +- ✅ No obvious token or environment mismatches |
| 52 | +- ✅ DAU metrics from App Store/Play Store show no actual user drop |
| 53 | + |
| 54 | +### What They Observed |
| 55 | +- Migration kept existing C# wrapper/util classes |
| 56 | +- Only swapped underlying calls to Unity Mixpanel API |
| 57 | +- Games not yet migrated continue to report expected volumes with super properties present |
| 58 | + |
| 59 | +### Code Flow |
| 60 | +Customer provided sequence diagram showing: |
| 61 | +1. Bootstrap scene loads |
| 62 | +2. Mixpanel.Init() called early in first scene |
| 63 | +3. App Start event fired immediately after initialization |
| 64 | +4. Super property registration happens in their SagoMixpanelBinding wrapper |
| 65 | + |
| 66 | +## Technical Analysis (Mixpanel Support) |
| 67 | + |
| 68 | +### Lin Yee Koh (Support) |
| 69 | +> "Mixpanel Unity (v3.5.3) initializes asynchronously. It is possible that the first event can fire before the instance is ready or before the super-properties file is loaded from disk." |
| 70 | +
|
| 71 | +### Customer Response (Guilherme) |
| 72 | +> "Hello, but the Mixpanel.Init() call seems sync. It is not a coroutine or Task method. It just applies the configs from the scriptable object and then initialize a singleton instance. How can we wait for the initialization finish? I didn't find an event or something that would tell the call finished." |
| 73 | +
|
| 74 | +### App Exit Event Mystery |
| 75 | +Customer noted that missing App Exit events is harder to explain with the async initialization theory: |
| 76 | +> "That still doesn't explain the missing App Exit events... Any queued or cached App Exit should get fired in the next launch as a flush() called automatically every 60 seconds." |
| 77 | +
|
| 78 | +## Root Cause (Identified) |
| 79 | + |
| 80 | +After investigation, the root cause is a **race condition during initialization**: |
| 81 | + |
| 82 | +### 1. Lazy Property Loading |
| 83 | +- Super properties are stored in PlayerPreferences/disk |
| 84 | +- Properties are lazily loaded on first access via `MixpanelStorage.SuperProperties` getter |
| 85 | +- On Android, PlayerPreferences read operations can be slow, especially on first access after app launch |
| 86 | +- If `Track()` is called immediately after `Init()`, properties may not be loaded yet |
| 87 | + |
| 88 | +### 2. Auto Properties Not Cached Early |
| 89 | +- `GetEventsDefaultProperties()` and `GetEngageDefaultProperties()` cache platform-specific auto properties |
| 90 | +- These were called in `InitializeAfterSceneLoad()` (RuntimeInitializeLoadType.AfterSceneLoad) |
| 91 | +- If `Track()` is called during or before AfterSceneLoad, auto properties aren't cached yet |
| 92 | +- Each uncached call would regenerate them (performance issue + timing issue) |
| 93 | + |
| 94 | +### 3. Session Metadata Not Initialized |
| 95 | +- Session metadata (`_sessionID`, counters, etc.) was only initialized in `OnApplicationPause()` |
| 96 | +- First event could have null/uninitialized session metadata |
| 97 | + |
| 98 | +### 4. App Exit Event Loss |
| 99 | +- App Exit events queued to PlayerPreferences |
| 100 | +- Auto-flush happens every 60 seconds |
| 101 | +- On Android, aggressive process killing means: |
| 102 | + - App Exit event queued but not flushed before process killed |
| 103 | + - Event should send on next launch, but if there are issues with loading queued events from PlayerPreferences, they could be lost |
| 104 | + |
| 105 | +## Solution Implemented |
| 106 | + |
| 107 | +Modified `Controller.Initialize()` to **eagerly load all properties synchronously**: |
| 108 | + |
| 109 | +```csharp |
| 110 | +internal static void Initialize() { |
| 111 | + MixpanelSettings.Instance.ApplyToConfig(); |
| 112 | + GetInstance(); |
| 113 | + |
| 114 | + // Eagerly load all persisted properties to ensure they're available immediately |
| 115 | + var _ = MixpanelStorage.SuperProperties; |
| 116 | + var __ = MixpanelStorage.OnceProperties; |
| 117 | + var ___ = MixpanelStorage.TimedEvents; |
| 118 | + |
| 119 | + // Pre-cache auto properties |
| 120 | + GetEventsDefaultProperties(); |
| 121 | + GetEngageDefaultProperties(); |
| 122 | + |
| 123 | + // Initialize session metadata |
| 124 | + Metadata.InitSession(); |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +### Benefits |
| 129 | +1. **Synchronous Loading**: All properties loaded from disk during `Init()` before it returns |
| 130 | +2. **No Race Condition**: `Track()` can be called immediately after `Init()` with properties guaranteed to be available |
| 131 | +3. **Session Metadata Ready**: Session ID and counters initialized before first event |
| 132 | +4. **Performance**: Auto properties cached once instead of regenerating |
| 133 | + |
| 134 | +## Testing Recommendations |
| 135 | + |
| 136 | +### For Customers |
| 137 | +1. Deploy updated SDK to staging environment |
| 138 | +2. Test immediate tracking after Init(): |
| 139 | + ```csharp |
| 140 | + Mixpanel.Init(); |
| 141 | + Mixpanel.Register("test_property", "test_value"); |
| 142 | + Mixpanel.Track("App Start"); // Should include test_property |
| 143 | + ``` |
| 144 | +3. Verify super properties present on first event in production |
| 145 | +4. Monitor event volumes for 1-2 weeks post-deployment |
| 146 | +5. Compare App Start/Exit event volumes to pre-migration baseline |
| 147 | + |
| 148 | +### For Mixpanel QA |
| 149 | +1. Test on low-end Android devices (slower storage I/O) |
| 150 | +2. Test with large super property sets (slower loading) |
| 151 | +3. Test rapid Init() → Track() sequences |
| 152 | +4. Verify session metadata present on first event |
| 153 | +5. Test App Exit event flush behavior |
| 154 | + |
| 155 | +## References |
| 156 | +- **Slack Thread**: #mixpanel-sagomini, Nov 5-19, 2025 |
| 157 | +- **Zendesk Case**: #674175 |
| 158 | +- **Customer**: Sago Mini (Piknik) |
| 159 | +- **Project**: Sago Mini - Production (ID: 210340) |
| 160 | +- **Dashboard**: https://mixpanel.com/project/210340/view/23451/app/insights#fTLhAYFBMnRZ |
| 161 | + |
| 162 | +## Related Code Files |
| 163 | +- `Mixpanel/Controller.cs` - Initialization logic |
| 164 | +- `Mixpanel/Storage.cs` - Property persistence and lazy loading |
| 165 | +- `Mixpanel/MixpanelAPI.cs` - Public API surface |
| 166 | + |
| 167 | +## Timeline |
| 168 | +- **Oct 1, 2025**: Customer rolled out Unity SDK migration to production |
| 169 | +- **Nov 5, 2025**: Customer reported issue to Mixpanel |
| 170 | +- **Nov 10, 2025**: Escalated to Senior Support Engineer |
| 171 | +- **Nov 18, 2025**: Mobile Engineering team engaged (Jared McFarland) |
| 172 | +- **Nov 19, 2025**: Root cause identified, fix in progress |
| 173 | +- **Nov 20, 2025**: Fix implemented and tested |
0 commit comments