LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Digital Marketing / Google Ads Retention: Understanding Limits and Planning...
Read this article in other languages:

Google Ads Retention: Understanding Limits and Planning Backfills

Google Ads Retention: Understanding Limits and Planning Backfills
Contents
  1. Retention depends on the query
  2. Separate the date boundary from export coverage
  3. A bounded, runnable window planner
  4. Retrieval and storage need one consistent contract
  5. Catch-up and continuing corrections
  6. Questions and answers
  7. Sources

The time resolution of a Google Ads report determines its available history. Since June 1, 2026, the documented 37-month limit applies to hourly, daily and weekly reports. Monthly, quarterly and yearly reporting can reach back up to eleven years. Reach and frequency have a separate three-year limit. A backfill, meaning the retrieval of older data, needs to distinguish these cases.

Retention depends on the query

The eleven years refer to coarser reporting data, not an eleven-year API export of account changes. The change_event resource requires dates within the last 30 days and a limit of at most 10,000 results. That data needs its own export.

Older monthly totals cannot reconstruct a missing daily breakdown. For the longer history, the Google Ads API also permits reports without a date segment when the date filter covers complete months. Boundary months and field compatibility still matter. Data beyond the daily boundary has therefore not necessarily disappeared in every form.

GA4, BigQuery transfers, DV360 and CM360 have separate rules. A number from the Google Ads policy does not establish another product’s retention period. Reach also cannot simply be summed across days: the same person can appear on several days.

Retention depends on time resolution; export coverage needs its own evidence.
Retention depends on time resolution; export coverage needs its own evidence.

Separate the date boundary from export coverage

A fixed example uses August 10, 2026: subtracting 37 calendar months gives July 10, 2023. A running job needs the date in the advertising account’s customer.time_zone, not an incidental server time zone. Month ends and leap years require calendar arithmetic.

A missing metric row does not prove an export gap. Date-segmented Google Ads reports omit a row when all selected metrics are zero. Each successfully and completely queried day therefore needs a coverage record, even when the result contains zero rows. That record applies only to the particular account and report definition.

A useful report key identifies the API resource, fields, filters, segmentation and schema version. A changed query requires a new version or a controlled rebuild. A single MAX(window_end) overlooks older holes and, without a report key, mixes different exports.

A bounded, runnable window planner

The following Python example requires python-dateutil. It plans at most seven days starting at the oldest unconfirmed day still inside the daily window. completed_days contains confirmed days for the same account and report only. Older unconfirmed days are returned separately, including on the first run. This function makes no API requests and writes no database.

from datetime import date, timedelta
from dateutil.relativedelta import relativedelta

def plan_window(today, oldest_wanted, completed_days, chunk_days=7):
    if chunk_days < 1:
        raise ValueError("chunk_days must be positive")
    floor = today - relativedelta(months=37)
    yesterday = today - timedelta(days=1)
    completed = set(completed_days)
    expired_unconfirmed = []
    current = oldest_wanted
    while current < floor and current <= yesterday:
        if current not in completed:
            expired_unconfirmed.append(current)
        current += timedelta(days=1)
    current = max(oldest_wanted, floor)
    while current <= yesterday and current in completed:
        current += timedelta(days=1)
    window = None if current > yesterday else (
        current, min(current + timedelta(days=chunk_days - 1), yesterday)
    )
    return window, expired_unconfirmed

if __name__ == "__main__":
    example_day = date(2026, 8, 10)
    window, expired = plan_window(
        example_day, date(2023, 7, 8), {date(2023, 7, 8)}
    )
    print(window)   # (2023-07-10, 2023-07-16)
    print(expired)  # [2023-07-09], not proof that every other copy is lost

expired_unconfirmed means that this register has no successful export record for a day outside the daily retrieval window. Other backups or older monthly totals may still exist. It is not proof that every copy is permanently lost. Reloading a partly confirmed window is acceptable when storage safely replaces the whole range.

Retrieval and storage need one consistent contract

  1. Acquire a lock for the account and report before selecting a window. In PostgreSQL, a pre-created state row can be locked using SELECT … FOR UPDATE inside a transaction. Every participating worker must respect the same lock.
  2. Consume the complete API result: all Search pages or the entire SearchStream. Errors and interrupted streams must not create successful coverage records. Validate expected fields, dates and account assignment.
  3. Replace the affected report range atomically: delete old rows for precisely this account, report and window, insert the replacement rows, and confirm every successfully queried day in the coverage register. This includes days returning zero rows.
  4. Commit data and coverage together, or roll both back. Staging and appropriate unique keys prevent duplicates and support controlled retries.
  5. Monitor errors, stalled progress and expired unconfirmed days. An alert on the last successful run detects a stalled exporter before its next successful request.

This is the integration contract. API authentication, compatible GAQL fields, table schema and locking must be implemented for the actual exporter. An existing wrapper does not automatically guarantee complete responses or idempotent storage. Long retrievals inside a database transaction require suitable timeouts; larger systems can use leases with a protected completion step.

Catch-up and continuing corrections

July 10, 2023 through August 9, 2026 inclusive covers 1,127 days: 161 windows of at most seven days. One successful window per hour would take approximately 6.7 days under ideal conditions. This is a capacity estimate, not a runtime guarantee. Volume, quotas, failures and retries affect actual operation.

The oldest endangered days deserve priority. Recent completed days also need repeated retrieval because metrics can change afterwards. A confirmed export is not a promise of immutable values. The planner above only closes historical gaps; recent corrections require a separate run.

Questions and answers

Is an hourly cron entry enough?

Only with configured authentication, monitoring, protection against concurrent starts and verified storage. A window does not necessarily correspond to one HTTP request; pagination and retries may add requests.

Which data needs priority?

Each dataset has its own boundary. Reach and frequency must not use the 37-month window for daily performance reports. The coverage register needs separate report identities.

Can a warehouse rebuild the original attribution?

An aggregated performance report preserves the metrics it received. Independent attribution recalculation requires more than these totals, including suitable additional event data and assumptions.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA to protect against spam.

Write a comment

Practical experience and questions about the implementation are welcome here.

The email address is not published. Required fields are marked with an asterisk.