Skip to content

Performance improvements - skipping already finished events - #177

Merged
edalzell merged 5 commits into
mainfrom
perf/skip-ended-events
Aug 5, 2026
Merged

Performance improvements - skipping already finished events#177
edalzell merged 5 commits into
mainfrom
perf/skip-ended-events

Conversation

@edalzell

@edalzell edalzell commented Aug 5, 2026

Copy link
Copy Markdown
Member

Summary

Events::upcoming()/between() fully augment every entry in the collection, then throw almost all of it away. Collections are mostly made up of events that finished long ago, so the vast majority of that work is wasted.

On a real site with 548 events, 527 had already finished. Generating their occurrences accounted for roughly 90% of the ~1.6s an upcoming(30) call took — no SQL involved, it's all Stache augmentation.

Fix

Reject already-ended events up front, in occurrences(), before the generator runs.

The important part is how the check is made: it reads only raw values. Augmentation is the expensive thing here — measured on one multi-day entry, augmented $entry->days costs ~30ms while raw $entry->get('days') costs 0ms — so a check that touched augmented values would just relocate the cost instead of removing it.

An event is skipped only when its last possible date is known to have passed, which is deliberately not the same as "started in the past":

type last possible date
non-recurring start_date — the only occurrence
recurring end_date; no end_date means it recurs forever
multi-day the last of its days

So an event that started two years ago and recurs annually is kept, and querying a past range still returns everything it used to. Dates that can't be parsed fall through rather than being excluded, and there's two days of slack because the event's own timezone isn't knowable without augmenting.

Results

Measured on that 548-event site, upcoming(30):

before ~1646ms
after ~195ms

Output is byte-identical — verified by diffing full dumps against unmodified main: 87 rows for upcoming(30), and 1082 rows for a between() spanning three years of past events.

Test plan

  • Added tests/SkipsEndedEventsTest.php covering the cases this could plausibly get wrong: unbounded recurring events that started years ago, annually recurring events, recurring events with a future vs passed end_date, multi-day events that are past/partway through, and past-range between() queries.
  • Since this is an optimization, those tests pass both before and after — so I checked they actually have teeth by temporarily implementing the naive "start_date is in the past" filter instead. 4 of the 8 fail against it, including every unbounded-recurrence case and the past-range query.
  • Full suite green (116 passed).

References https://github.com/transformstudios/zakat.org/issues/2322

Every call fully augmented every entry in the collection, then threw
almost all of it away. On a real site with 548 events, 527 of them had
finished long ago, and generating their occurrences accounted for
roughly 90% of the ~1.6s an upcoming() call took.

Reject those up front, using only raw values. Augmentation is what's
expensive here (an augmented grid field costs ~30ms, the raw array 0ms),
so the check has to stay off the augmented values to be worth anything.

An event is only skipped when its last possible date is known to have
passed, which is not the same as having started in the past:

  - non-recurring: start_date is the only occurrence
  - recurring:     end_date, and no end_date means it recurs forever
  - multi-day:     the last of its days

So an event that started two years ago and recurs annually is kept, and
querying a past range still returns what it always did.

Measured on that site, upcoming(30): ~1646ms -> ~195ms, with byte
identical output for both upcoming() and a between() spanning three
years of past events.
@edalzell edalzell added the fix label Aug 5, 2026
@edalzell
edalzell marked this pull request as ready for review August 5, 2026 22:36
@edalzell edalzell changed the title Skip events that have already ended before generating occurrences Performance improvements - skipping already finished events Aug 5, 2026
@edalzell
edalzell merged commit 8258a49 into main Aug 5, 2026
24 checks passed
@edalzell
edalzell deleted the perf/skip-ended-events branch August 5, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant