Skip to content

Read X post metrics from the timeline that already returned them - #300

Closed
paulocastellano wants to merge 1 commit into
mainfrom
fix/x-analytics-double-read
Closed

Read X post metrics from the timeline that already returned them#300
paulocastellano wants to merge 1 commit into
mainfrom
fix/x-analytics-double-read

Conversation

@paulocastellano

Copy link
Copy Markdown
Contributor

Follow-up to #299, independent of it — branched from main and mergeable on its own.

The problem

XAnalytics::getMetrics fetched the account's timeline for post ids, then looked the same ids up again through GET /2/tweets, purely to read the public_metrics the first request could have returned.

The timeline call asked for start_time, end_time and max_results — never tweet.fields:

$params = [
    'start_time'  => $since->toIso8601ZuluString(),
    'end_time'    => $until->toIso8601ZuluString(),
    'max_results' => 100,
];

...and then, a few lines later:

$response = $this->getHttpClient()->get("{$this->baseUrl}/tweets", [
    'ids'          => implode(',', $chunk),
    'tweet.fields' => 'public_metrics',
]);

Both endpoints bill per Post returned. The second pass claimed the same resources again, took a second round-trip, and spent a second slice of the same rate limit.

Measured

One analytics load for an account with 250 posts in range:

HTTP round-trips Posts returned by the API
main 6 the same 250, read twice
this branch 3 250, once

What this actually saves — and what it doesn't

Not necessarily dollars. X deduplicates a resource within a 24-hour UTC window, so the second read of an id already read that day isn't charged again. In the common case the redundant pass was probably free.

What it does buy:

  • Half the round-trips on a user-facing page, which is latency they feel.
  • Half the rate-limit consumption for the same answer.
  • No reliance on a soft guarantee. The docs are explicit that deduplication "may result in resources not being deduplicated" in edge cases like service outages. Every analytics load was putting 250 resources' worth of billing behind that caveat for no benefit.

Worth stating plainly rather than claiming a saving that dedup was likely already absorbing.

Behaviour

Unchanged. Same totals, same 5-page ceiling, same empty result when the account posted nothing in the range. The page cap is now a named constant (MAX_TIMELINE_PAGES) — it bounds what a single load can cost as much as how long it takes, which the bare 5 didn't say.

The two helpers collapse into one paginated pass that sums metrics as pages arrive. The accumulator also drops the foreach (... as &$total) reference in favour of indexing by key.

Tests

First tests for XAnalytics::getMetrics — there were none:

  • metrics come from the timeline instead of a second lookup of the same posts
  • the timeline request asks for public_metrics
  • metrics accumulate across paginated timeline pages

Full suite: 3750 passed, 1 skipped. Pint clean.

Still open

The larger analytics lever isn't this: it's that a load can pull up to 500 posts (5 pages × 100) at $0.005 each, and since/until come straight off the request without validation (AnalyticsController.php:72), capped at 100 days inside the service. For a heavy poster on a long range that's $2.50 per UTC day, per account. Reading fewer posts would change what the numbers mean, so it's a product decision rather than a cleanup.

Analytics fetched the account's timeline for post ids, then turned around and
looked the same ids up again through GET /2/tweets purely to read the
public_metrics the first request could have returned. The timeline call asked
for start_time, end_time and max_results — never tweet.fields.

Both endpoints bill per Post returned, so the second pass claimed the same
resources a second time, took a second round-trip, and spent a second slice of
the same rate limit. For an account with 250 posts in range that is 6 requests
where 3 will do.

The saving is in round-trips and rate limit rather than dollars: X deduplicates
a resource within a 24-hour UTC window, so the second read of an id already
read that day is not charged again. But the docs call that a soft guarantee
that "may result in resources not being deduplicated" — this stops leaning on
it for 250 resources per analytics load.

Behaviour is unchanged: same totals, same 5-page ceiling, same empty result
when the account posted nothing in range. The page cap is now a named constant,
since it bounds what one load can cost as much as how long it takes.

Adds the first tests for XAnalytics::getMetrics, covering the totals, the
pagination, and that the metrics arrive on the timeline request.
@paulocastellano

Copy link
Copy Markdown
Contributor Author

Folded into #299 — this belonged in the same PR, not a separate one. The commit is now ec3fe17 on fix/x-api-token-verification-cost.

@paulocastellano
paulocastellano deleted the fix/x-analytics-double-read branch August 20, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant