37 miesięcy i koniec: dane Google Ads mają teraz termin ważności
Spis treści
Od 1.06.2026 dane w Google Ads mają termin ważności. Dane o skuteczności są przechowywane przez 37 miesięcy, historia zmian na koncie przez 11 lat, dane Reach and Frequency przez 3 lata. Dziś jest 10.08.2026, więc najstarszy dzień z danymi o skuteczności, jaki da się jeszcze pobrać, to 10.07.2023 — a ta granica przesuwa się do przodu o jeden dzień każdego dnia. Jeżeli historia raportowania istnieje wyłącznie wewnątrz Google Ads, to po cichu ubywa jej po jednym dniu dziennie. Ten artykuł wykonuje potrzebne wyliczenia, prostuje liczby błędnie przytaczane przy okazji tej zmiany i buduje wznawialne zadanie backfill, które przenosi wszystko powyżej granicy do własnej hurtowni danych.
1. Co naprawdę mówi strona o retencji danych Google Ads
Udokumentowane są trzy okna — i tylko trzy:
- 37 miesięcy dla danych o skuteczności.
- 11 lat dla historii zmian na koncie.
- 3 lata dla Reach and Frequency.
Większość omówień tej zmiany niesie ze sobą trzy dodatkowe liczby: 36 miesięcy dla GA Data API, 24 miesiące dla BigQuery Data Transfer Service, 24 miesiące dla DV360 i CM360. Tych wartości nie ma na stronie pomocy Google Ads, która ogłosiła okna retencji. W zakresie tego ogłoszenia pozostają nieudokumentowane, a powtarzanie ich tak, jakby stanowiły jego część, to dokładnie ten mechanizm, w którym plotka zamienia się w założenie planistyczne. Proszę planować w oparciu o trzy powyższe wartości. Jeżeli potrzebna jest wartość retencji dla GA4, dla transferów BigQuery albo dla DV360 i CM360, należy wziąć ją z dokumentacji danego produktu.
Jeden szczegół łatwo przeoczyć: 37 miesięcy to więcej niż trzy lata. Reach and Frequency wygasa zatem mniej więcej miesiąc przed danymi o skuteczności dotyczącymi tych samych kampanii. Przy zabezpieczaniu obu zbiorów to R&F ma krótszy termin, a nie dłuższy.
2. Wyliczenie własnego terminu i ustalenie, co już przepadło
Granica jest ruchoma, nie jest pojedynczym cięciem, które nastąpiło w czerwcu. Lepiej ją wyliczać, niż zapamiętywać:
from datetime import date
from dateutil.relativedelta import relativedelta
today = date(2026, 8, 10)
print(today - relativedelta(months=37)) # dane performance 2023-07-10
print(today - relativedelta(years=3)) # reach, frequency 2023-08-10
print(today - relativedelta(years=11)) # historia zmian 2015-08-10
Wszystko, co ma datę wcześniejszą niż 10.07.2023, jest już nie do odzyskania przez API. Jutro tą datą będzie 11.07.2023. Zanim powstanie jakikolwiek kod eksportu, warto ustalić, jaka część wciąż dostępnego zakresu faktycznie znajduje się w Państwa posiadaniu. Dla hurtowni na Postgresie z tabelą dzienną:
WITH expected AS (
SELECT generate_series(DATE '2023-07-10', CURRENT_DATE - 1, INTERVAL '1 day')::date AS d
)
SELECT count(*) AS missing_days, min(e.d) AS oldest_gap, max(e.d) AS newest_gap
FROM expected e
LEFT JOIN ads_daily a ON a.day = e.d
WHERE a.day IS NULL;
To samo zapytanie należy uruchomić z początkiem okna ustawionym na własny najwcześniejszy oczekiwany dzień. Wszystko, co zostanie zgłoszone poniżej 2023-07-10, to trwała dziura; wszystko powyżej to praca, którą wciąż da się wykonać.
3. Zadanie backfill, które się wznawia
Nie będę tu drukował listy pól GAQL, ponieważ właściwa lista to ta, z której już Państwo korzystają. Zasób, segment daty i wybrane pola należy wziąć wprost z raportu uruchamianego dzisiaj, umieścić je w pliku konfiguracyjnym i pozostawić zadaniu wyłącznie okno dat:
{
"customer_id": "1234567890",
"dsn": "postgresql://ads@localhost/warehouse",
"resource": "<the resource your existing report reads>",
"date_field": "<the date segment that report groups by>",
"fields": ["<field 1>", "<field 2>"],
"query_template": "SELECT {fields} FROM {resource} WHERE {date_field} BETWEEN '{start}' AND '{end}'",
"oldest_wanted": "2019-01-01"
}
Runner pobiera dokładnie jedno okno na wywołanie i je odnotowuje. Stan to cztery kolumny: customer_id, window_start, window_end, rows, zapisywane w tej samej transakcji co dane, więc awaria oznacza po prostu powtórzenie okna.
import json
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
import psycopg
CFG, CHUNK = json.load(open("/etc/ads-backfill/config.json")), timedelta(days=7)
CID = CFG["customer_id"]
def next_window(cur):
cur.execute("SELECT max(window_end) FROM backfill_state WHERE customer_id = %s", (CID,))
(last,) = cur.fetchone()
floor = max(date.today() - relativedelta(months=37),
date.fromisoformat(CFG["oldest_wanted"]))
start = last + timedelta(days=1) if last else floor
if start < floor: # zadanie stanęło, te dni wygasły
cur.execute("INSERT INTO backfill_lost VALUES (%s, %s, %s)",
(CID, start, floor - timedelta(days=1)))
start = floor
end = min(start + CHUNK - timedelta(days=1), date.today() - timedelta(days=1))
return (start, end) if start <= end else None
with psycopg.connect(CFG["dsn"]) as conn, conn.cursor() as cur:
win = next_window(cur)
if win:
start, end = win
rows = run_report(CID, CFG["query_template"].format(
fields=", ".join(CFG["fields"]), resource=CFG["resource"],
date_field=CFG["date_field"], start=start, end=end))
cur.executemany(CFG["insert_sql"], rows)
cur.execute("INSERT INTO backfill_state VALUES (%s, %s, %s, %s, now())",
(CID, start, end, len(rows)))
conn.commit()
run_report to Państwa istniejący wrapper API, niezależnie od tego, jaką bibliotekę kliencką opakowuje. Cała reszta pliku działa po Państwa stronie granicy: własna konfiguracja, własna tabela, własne granice transakcji.
4. Cron, kolejność i rejestr strat
# /etc/cron.d/ads-backfill — jedno okno na godzinę, najstarsze najpierw
7 * * * * ads /opt/ads-backfill/.venv/bin/python /opt/ads-backfill/backfill.py >> /var/log/ads-backfill.log 2>&1
Dwie rzeczy w tym zadaniu są zamierzone. Idzie ono do przodu od granicy, a nie wstecz od dzisiaj, ponieważ tylko najstarsze dane mają termin; ostatnie dni mogą poczekać. A kiedy okazuje się, że granica wyprzedziła jego własny postęp, zadanie nie pomija po cichu luki, lecz zapisuje ten przedział do tabeli backfill_lost. Ta tabela jest uczciwą odpowiedzią na pytanie „których okresów już nie mamy” i warto umieścić ją na dashboardzie obok zapytania o luki z sekcji 2.
Arytmetyka jest wygodna. Od 10.07.2023 do wczoraj mija 1126 dni, czyli 161 okien po siedem dni. Przy jednym oknie na godzinę daje to niecałe siedem dni czasu rzeczywistego, w trakcie których granica przesuwa się o siedem dni: na każdy utracony dzień historii przypada 168 dni zyskanych. Po zamknięciu zaległości to samo zadanie utrzymuje bieżący koniec zakresu w aktualności i kosztuje jedno wywołanie API na godzinę.
5. Podsumowanie
Mają Państwo teraz granicę, którą się wylicza zamiast zapamiętywać, zapytanie wskazujące, których dni z wciąż dostępnego okna brakuje w hurtowni, runner obsługujący jedno okno, który czysto wznawia się po awarii i sam dostosowuje się do bieżącego odcięcia, godzinowy wpis w cronie oraz rejestr okresów będących już poza zasięgiem. Całe zaległości od 10.07.2023 zamykają się w mniej więcej tydzień pracy bez nadzoru, po czym zadanie przechodzi w tryb utrzymaniowy.
Zyskują Państwo dzięki temu tyle, że historia raportowania przestaje być cudzą polityką retencji. Porównania rok do roku, modele sezonowości i przebudowy atrybucji prędzej czy później potrzebują danych starszych niż 37 miesięcy, a po 1.06.2026 jedyną wciąż istniejącą kopią będzie ta przechowywana u Państwa. Trzy udokumentowane okna warto potwierdzić w źródle pierwotnym i zignorować krążące obok nich dodatkowe liczby: polityka retencji danych Google Ads.