analytics_538412813
· generated 2026-09-22Interstitial ad loads succeed 3.0% of the time
(28,449 of 943,813 attempts); rewarded video
loads succeed 40.7% of the time
(54,094 of 133,006).
Measured from ad_request_status (status = success/
fail), the whole export, 2026-07-01 to yesterday.
The dominant cause isn't "no ad available" — it's the mediation SDK failing to reach its
own server. 591,164
interstitial attempts (65%
of all fails) error with a DNS/connect failure to ms.applvn.com,
AppLovin MAX's own mediation host — not a shortage of ads, a networking problem.
This affects a broad slice of the base, not a few looping devices: 23% of users who requested an interstitial hit this failure at least once (5,416 of 23,436), averaging ~109 failed attempts each — consistent with a client retry loop hammering an unreachable host.
Engineering has already root-caused part of a fresh decline in rewarded fill,
separate from the DNS/connect problem above: AppLovin's mediation server is
returning ad responses for the REWARDED_INTER (Rewarded Interstitial) format
without the ad_format field the client's MAX SDK expects. The SDK doesn't
recognize the format, treats the response as No Fill (error 204), and the ad
never becomes "ready" — a player who taps gets a "not ready" toast even though AppLovin did
return something. This is a client-SDK/server-format mismatch, not a lack of inventory.
The data shows a matching pattern over the last 10 days: rewarded fill rate slid from 67% on 2026-09-13 to 44% on 2026-09-21 (choppy day to day, but the last full day is well below the first), while interstitial attempt volume surged from a baseline around 3,383/day to a peak of 15,946 attempts on 2026-09-20 — roughly 5× baseline — before easing back down, tracking the same days rewarded fill was at its worst. Interstitial fill rate stayed in the low single digits throughout, so this substitution is not recovering the failed ad opportunity, just changing what's being retried.
| Day | Ad type | Attempts | Fill rate |
|---|---|---|---|
| 2026-09-13 | inter | 3,383 | 8.1% |
| 2026-09-14 | inter | 3,639 | 7.3% |
| 2026-09-15 | inter | 5,586 | 3.6% |
| 2026-09-16 | inter | 5,747 | 3.7% |
| 2026-09-17 | inter | 7,866 | 3.1% |
| 2026-09-18 | inter | 11,462 | 2.1% |
| 2026-09-19 | inter | 6,028 | 7.8% |
| 2026-09-20 | inter | 15,946 | 3.3% |
| 2026-09-21 | inter | 2,933 | 11.1% |
| 2026-09-22 | inter | 768 | 19.0% |
| 2026-09-13 | rewarded | 891 | 66.8% |
| 2026-09-14 | rewarded | 805 | 56.5% |
| 2026-09-15 | rewarded | 948 | 47.7% |
| 2026-09-16 | rewarded | 972 | 63.6% |
| 2026-09-17 | rewarded | 1,227 | 50.9% |
| 2026-09-18 | rewarded | 1,292 | 35.3% |
| 2026-09-19 | rewarded | 1,423 | 48.8% |
| 2026-09-20 | rewarded | 2,156 | 35.9% |
| 2026-09-21 | rewarded | 1,215 | 44.1% |
| 2026-09-22 | rewarded | 463 | 62.4% |
The last-10-days query:
SELECT
PARSE_DATE('%Y%m%d', event_date) AS day,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='ad_type') AS ad_type,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='status') AS status,
COUNT(*) AS n
FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
WHERE event_name = 'ad_request_status'
AND _TABLE_SUFFIX BETWEEN '20260913' AND '20260922'
GROUP BY day, ad_type, status
ORDER BY day, ad_type
ad_request_status is the load outcome event —
status = success or fail, with which network
filled it and the raw SDK error on failure. This is the standard fill/fail-rate signal,
and it's been live since build 5 (2026-07-01), so the whole export has consistent
instrumentation.
| Ad type | Attempts | Success | Fail | Fill rate |
|---|---|---|---|---|
| inter | 943,813 | 28,449 | 915,364 | 3.0% |
| rewarded | 133,006 | 54,094 | 78,912 | 40.7% |
Today's shard is partial and excluded. Both lines are consistently low across the whole window — this is not a one-day blip.
The query behind sections 1, 2, 3 and 4 — copy and re-run it:
SELECT
PARSE_DATE('%Y%m%d', event_date) AS day,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='ad_type') AS ad_type,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='ad_network') AS ad_network,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='status') AS status,
CASE
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') IS NULL THEN NULL
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%applvn.com%' THEN 'AppLovin MAX mediation server unreachable (DNS/connect)'
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%No Fill%' OR (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%no eligible ads%' THEN 'No fill (no ad available)'
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%timeout%' OR (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%timed out%' THEN 'Timeout'
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%onnection%' OR (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE 'unexpected end of stream%' THEN 'Connection dropped mid-request'
WHEN (SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') LIKE '%JSONException%' THEN 'Malformed mediation response (JSON parse error)'
ELSE 'Other'
END AS error_bucket,
COUNT(*) AS n
FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
WHERE event_name = 'ad_request_status'
GROUP BY day, ad_type, ad_network, status, error_bucket
ORDER BY day
| Ad type | Network | Successful loads | Share |
|---|---|---|---|
| inter | AppLovin | 13,419 | 47.2% |
| inter | Mintegral | 7,060 | 24.8% |
| inter | Google AdMob | 5,399 | 19.0% |
| inter | DT Exchange | 862 | 3.0% |
| inter | Liftoff Monetize | 693 | 2.4% |
| inter | APPLOVIN_EXCHANGE | 536 | 1.9% |
| inter | 480 | 1.7% | |
| rewarded | AppLovin | 24,358 | 45.0% |
| rewarded | Google AdMob | 15,289 | 28.3% |
| rewarded | Mintegral | 7,956 | 14.7% |
| rewarded | 3,746 | 6.9% | |
| rewarded | DT Exchange | 1,161 | 2.1% |
| rewarded | Liftoff Monetize | 935 | 1.7% |
| rewarded | APPLOVIN_EXCHANGE | 646 | 1.2% |
| rewarded | IronSource | 3 | 0.0% |
| Ad type | Reason | Events | Share of fails |
|---|---|---|---|
| inter | AppLovin MAX mediation server unreachable (DNS/connect) | 591,164 | 64.6% |
| inter | (no error string recorded) | 296,459 | 32.4% |
| inter | No fill (no ad available) | 14,116 | 1.5% |
| inter | Connection dropped mid-request | 10,079 | 1.1% |
| inter | Other | 3,020 | 0.3% |
| inter | Timeout | 526 | 0.1% |
| rewarded | AppLovin MAX mediation server unreachable (DNS/connect) | 46,555 | 59.0% |
| rewarded | (no error string recorded) | 28,837 | 36.5% |
| rewarded | Connection dropped mid-request | 1,550 | 2.0% |
| rewarded | No fill (no ad available) | 1,391 | 1.8% |
| rewarded | Other | 329 | 0.4% |
| rewarded | Timeout | 250 | 0.3% |
~32-37% of failures carry no error string at all (the SDK set
status=fail but the error param is empty) — a real instrumentation
gap; right now there's no way to tell what's behind that bucket. Genuine "no fill" (the
network legitimately having nothing to serve) is a small minority of failures for both
ad types.
| Event | Count | Peak daily users | First seen | Last seen |
|---|---|---|---|---|
| ad_request | 1,930,708 | 1,137 | 2026-07-01 | 2026-09-22 |
| ad_request_status | 1,076,819 | 765 | 2026-07-01 | 2026-09-22 |
| ad_reward | 41,214 | 172 | 2026-07-15 | 2026-09-22 |
| show_ad_click | 34,474 | 197 | 2026-06-29 | 2026-09-22 |
| ad_show | 29,107 | 153 | 2026-07-04 | 2026-09-22 |
| show_ad_inter_backup_failed | 3,183 | 28 | 2026-07-05 | 2026-09-22 |
| show_ad_inter_backup | 677 | 10 | 2026-07-05 | 2026-09-22 |
| ad_rewarded_show_failed | 89 | 3 | 2026-07-09 | 2026-09-21 |
ad_request events
(56%) have a matching ad_request_status —
the rest never resolve to a logged success or fail.ad_show, 35%) are followed by an actual display.
Plausibly SDK preloading/caching (a successful load doesn't have to be shown right away),
but large enough that it's worth checking the client's ad-caching logic rather than assuming
it's benign.ad_request_status and carries no
network/error breakdown, so it needs its own investigation.The funnel query:
SELECT
event_name,
PARSE_DATE('%Y%m%d', event_date) AS day,
COUNT(*) AS n,
COUNT(DISTINCT user_pseudo_id) AS users
FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
WHERE event_name IN ('show_ad_click','ad_request','ad_request_status','ad_show','ad_reward',
'ad_rewarded_show_failed','show_ad_inter_backup','show_ad_inter_backup_failed')
GROUP BY event_name, day
ORDER BY event_name, day
The concentration check (section verdict, para 3):
WITH s AS (
SELECT
user_pseudo_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='ad_type') AS ad_type,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='status') AS status,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='error') AS error
FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
WHERE event_name = 'ad_request_status'
)
SELECT
ad_type,
COUNT(DISTINCT user_pseudo_id) AS users_total,
COUNT(DISTINCT IF(status='fail' AND error LIKE '%applvn.com%', user_pseudo_id, NULL)) AS users_with_dns_fail,
COUNTIF(status='fail' AND error LIKE '%applvn.com%') AS dns_fail_events
FROM s
GROUP BY ad_type
publisher.ad_revenue_in_usd if the next question is how much this is
costing.ad_network='unknown' on every failed row is expected — no
network filled the request, so none is attributed.notebooks/13_ad_fill_show_rate.ipynb.