Skip to content

refactor(gtfs): optimize situation IDs fetch by eliminating duplicate DB queries#395

Merged
aaronbrethorst merged 2 commits intoOneBusAway:mainfrom
3rabiii:refactor/optimize-situation-ids
Feb 19, 2026
Merged

refactor(gtfs): optimize situation IDs fetch by eliminating duplicate DB queries#395
aaronbrethorst merged 2 commits intoOneBusAway:mainfrom
3rabiii:refactor/optimize-situation-ids

Conversation

@3rabiii
Copy link
Copy Markdown
Contributor

@3rabiii 3rabiii commented Feb 11, 2026

Context

This Pr was identified that GetSituationIDsForTrip was performing redundant database work:

  • It queried the database for Trip and Route details to get the AgencyID.
  • It then called GetAlertsForTrip, which performed the exact same database queries internally.

Changes

  • Refactor: Extracted the alert filtering logic into a new method GetAlertsByIDs in internal/gtfs/realtime.go. This method accepts pre-fetched IDs, avoiding the need for internal DB lookups.
  • Optimization: Updated GetSituationIDsForTrip in internal/restapi/trips_helper.go to fetch the Trip/Route/Agency data once and pass it to GetAlertsByIDs.
  • Fix: Updated BuildTripStatus to explicitly initialize SituationIDs as an empty slice [] (instead of nil) when no alerts are found. This ensures the JSON response remains consistent ("situationIds": [] instead of null), preventing test failures in TestTripsForLocationHandler.

Impact

Reduces database load by eliminating duplicate queries per trip status request while maintaining API contract stability.

Screenshot From 2026-02-11 23-45-51

@aaronbrethorst
#fixes : #394

@3rabiii 3rabiii force-pushed the refactor/optimize-situation-ids branch from 7f4e34b to 88cba66 Compare February 13, 2026 13:56
@3rabiii 3rabiii force-pushed the refactor/optimize-situation-ids branch from 88cba66 to f9fb5d3 Compare February 13, 2026 14:03
Copy link
Copy Markdown
Member

@aaronbrethorst aaronbrethorst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Adel, thanks for identifying the duplicate DB queries in GetSituationIDsForTrip — the optimization of fetching trip/route/agency data once and passing it through is the right idea. The extraction of GetAlertsByIDs as a reusable method is clean, and the GtfsDB != nil safety check is a nice defensive addition. Before we can merge this, I will need you to make a couple changes:

Critical

  1. Situation IDs lost their agency prefix (trips_helper.go:787-789)

    The old code produced situation IDs in the standard OneBusAway {agencyID}_{alertID} format:

    situationIDs = append(situationIDs, utils.FormCombinedID(agencyID, alert.ID))

    The new code appends raw alert IDs without the agency prefix:

    situationIDs = append(situationIDs, alert.ID)

    This is an API contract regression — all OneBusAway IDs use the {agencyID}_{codeID} format. Clients parsing situation IDs will break. The fix is to restore the FormCombinedID logic:

    situationIDs := []string{}
    for _, alert := range alerts {
        if alert.ID != "" {
            if agencyID != "" {
                situationIDs = append(situationIDs, utils.FormCombinedID(agencyID, alert.ID))
            } else {
                situationIDs = append(situationIDs, alert.ID)
            }
        }
    }
  2. Missing empty ID check (trips_helper.go:788)

    The old code guarded with if alert.ID != "" before appending. The new code appends unconditionally, which could add empty strings to the situation IDs array. Please restore the alert.ID != "" check.

Important

  1. IMPORTANT: Caller must hold manager.RLock() comment is misplaced (realtime.go:108)

    The RLock comment was moved from GetAlertsForTrip to GetAlertsByIDs, but GetAlertsByIDs doesn't need the outer manager lock — it only accesses manager.realTimeAlerts, which is protected by the internal realTimeMutex.RLock() it acquires itself.

    The comment should remain on GetAlertsForTrip (which does DB queries via GetTrip/GetRoute) and on GetSituationIDsForTrip (which does the same DB queries). Fix:

    // GetAlertsByIDs returns alerts matching the given trip, route, or agency IDs.
    func (manager *Manager) GetAlertsByIDs(tripID, routeID, agencyID string) []gtfs.Alert {
    
    // GetAlertsForTrip returns alerts matching the trip, its route, or agency.
    // IMPORTANT: Caller must hold manager.RLock() before calling this method.
    func (manager *Manager) GetAlertsForTrip(ctx context.Context, tripID string) []gtfs.Alert {

Thanks again, and I look forward to merging this change.

- Restore utils.FormCombinedID in GetSituationIDsForTrip to maintain API contract ({agency}_{id}).
- Add check for empty alert IDs.
- Move RLock documentation comment to GetAlertsForTrip where external locking is required.
@3rabiii
Copy link
Copy Markdown
Contributor Author

3rabiii commented Feb 18, 2026

@aaronbrethorst
Ready for review!

Copy link
Copy Markdown
Member

@aaronbrethorst aaronbrethorst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adel, nice work addressing the feedback from the first round. The core optimization is solid — fetching trip/route/agency data once instead of twice per call is the right call, and GetAlertsByIDs as a reusable building block is well-structured. All three previously requested changes have been properly resolved:

  1. Agency prefix restoredFormCombinedID logic is back in GetSituationIDsForTrip (trips_helper.go:792-795)
  2. Empty ID check restored — the alert.ID == "" guard with early continue is clean (trips_helper.go:789-791)
  3. RLock comment correctly placed — present on GetAlertsForTrip, absent from GetAlertsByIDs

I verified that tests pass and lint is clean. This is ready to merge with a couple of optional suggestions for a follow-up.

Suggestions

These are non-blocking — take them or leave them.

  1. Add a doc comment on GetAlertsByIDs (realtime.go:108)

    Go convention is to document exported methods. A one-liner would be sufficient:

    // GetAlertsByIDs returns alerts matching the given trip, route, or agency IDs.
    func (manager *Manager) GetAlertsByIDs(tripID, routeID, agencyID string) []gtfs.Alert {
  2. Consider logging DB errors in GetSituationIDsForTrip (trips_helper.go:774-783)

    The old code returned nil when GetTrip failed — no alerts at all. The new code gracefully degrades to trip-only alert matching, which is better behavior. But database errors (connection failures, context timeouts) are now silently swallowed. A slog.Warn for non-sql.ErrNoRows errors would help operators detect database degradation without changing the graceful-degradation behavior. This is a nice-to-have, not a requirement.

@aaronbrethorst aaronbrethorst merged commit 95c0d34 into OneBusAway:main Feb 19, 2026
4 checks passed
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.

refactor: Optimize GetSituationIDsForTrip to eliminate duplicate DB queries

2 participants