Skip to content

Optimize GetStationsByLineGroupId/GetStationsByLineIdList performance#1439

Merged
TinyKitten merged 5 commits into
devfrom
feature/optimize-line-group-and-line-id-list-rpc-performance
Mar 20, 2026
Merged

Optimize GetStationsByLineGroupId/GetStationsByLineIdList performance#1439
TinyKitten merged 5 commits into
devfrom
feature/optimize-line-group-and-line-id-list-rpc-performance

Conversation

@TinyKitten

@TinyKitten TinyKitten commented Mar 20, 2026

Copy link
Copy Markdown
Member

Summary

  • train typeクエリの早期リターン: line_group_idNoneの場合、SQLでcolumn = NULLは常にfalseで空結果になるため、クエリをスキップ
  • バス路線取得の候補キャッシュ: 座標を~100mグリッドに丸めてget_by_coordinatesの候補セットをキャッシュし、300mフィルタは駅ごとに正確に再適用。バス路線クエリ(get_by_station_group_id_vec)もキャッシュして重複排除
  • 独立DBクエリの並列化: tokio::try_join!で4つの逐次クエリを2フェーズに並列化
  • クエリIDの重複除去: station_group_ids/company_idsをdedup してIN句サイズを削減
  • テストモック強化: ConfigurableMockTrainTypeRepositoryline_group_idに応じて返す値を分岐するように修正し、line_group_id=Noneでtrain typeがスキップされるテストを追加

Test plan

  • SQLX_OFFLINE=true cargo test 全テスト通過(新規テスト含む)
  • サーバー起動後、Prometheusメトリクスのgrpc_request_duration_secondsで改善を確認
  • GetStationsByLineGroupIdGetStationsByLineIdListの応答時間を実測比較

🤖 Generated with Claude Code

Summary by CodeRabbit

  • パフォーマンス改善

    • 複数フェーズの取得処理をさらに並列化し応答効率を向上しました。
    • 会社情報や列車種別の取得を並列化して待ち時間を短縮しました。
  • 最適化

    • 駅グループ単位・停留所候補のキャッシュを導入し重複処理を削減しました。
    • 緯度経度による300mフィルタなどで候補絞り込みを効率化しました。
    • バス路線・停留所の重複除去を改善しました。
  • 変更

    • 条件により列車種別の空結果を即時返す挙動を明確化しました。
    • 不足する会社情報は処理中に随時取得するようになりました。
  • テスト

    • 挙動検証を強化するためのテストを追加・更新しました。

…ance

Address three performance bottlenecks in update_station_vec_with_attributes:

1. Early return for train type queries when line_group_id is None
   - SQL `column = NULL` always returns empty, so skip the query entirely

2. Cache nearby bus line lookups by ~100m coordinate grid
   - Eliminates N+1 get_nearby_bus_lines queries (2 DB hits per station)
   - Stations on the same line share similar coordinates, so cache hit rate is high

3. Parallelize independent DB queries with tokio::try_join!
   - Phase 1: stations_by_group_id + lines_by_station_group_id
   - Phase 2: companies + train_types

Expected improvement: ~70-80% latency reduction for affected RPCs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the feature 要望対応や課題解決 label Mar 20, 2026
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1974af60-168c-4d48-83c1-5f103cc02ffe

📥 Commits

Reviewing files that changed from the base of the PR and between d1cbcdd and b6f7cd5.

📒 Files selected for processing (1)
  • stationapi/src/use_case/interactor/query.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • stationapi/src/use_case/interactor/query.rs

📝 Walkthrough

Walkthrough

update_station_vec_with_attributesを2フェーズ並列化し、駅/路線取得と会社/列車種別取得をそれぞれ並行実行するよう変更。line_group_id == None時は列車種別取得を早期リターン。RailAndBusの周辺バス取得は座標グリッド→駅グループIDの2段階キャッシュと300mフィルタで再実装。company_mapはCompanyを所有し、欠損会社はオンデマンド取得するようになった。

Changes

Cohort / File(s) Summary
Interactor 実装変更
stationapi/src/use_case/interactor/query.rs
update_station_vec_with_attributesを2フェーズ化:station_group_idsをソート/重複除去し、(stations, lines)を並列取得。続けてcompany_ids派生後に(companies, train_types)を並列取得。company_mapCompanyを所有するよう変更し、欠損会社は処理中にオンデマンド取得するロジックを追加。
周辺バス取得の再設計
stationapi/src/use_case/interactor/query.rs
RailAndBus向けの周辺バス取得を全面書き換え。緯度/経度を丸めたグリッドで候補バス停集合をキャッシュし、各駅は300mハバースァインで候補を絞る。バス路線は駅グループID集合でキャッシュしてline_cdで重複除去し、該当停留所(駅番号含む)を埋め込み。旧ヘルパget_nearby_bus_linesを削除。
列車種別取得と早期リターン
stationapi/src/use_case/interactor/query.rs, .../tests/*
get_train_types_by_station_id_vecline_group_id == Noneの場合にOk(vec![])を即返す実装を追加。これに伴い呼び出し側とモックを更新。
テスト・モック更新
stationapi/.../tests/*, stationapi/src/.../tests/*
テストを修正して期待されるline_group_idをモックへ伝搬/検証可能に。既存の呼び出しをSome(1000)等に更新し、line_group_id == Noneケースの追加テストを含む。

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Interactor
    participant StationRepo
    participant LineRepo
    participant CompanyRepo
    participant TrainTypeRepo
    participant BusRepo

    Client->>Interactor: update_station_vec_with_attributes(request)
    par phase 1
      Interactor->>StationRepo: get_stations_by_group_id_vec(station_group_ids)
      Interactor->>LineRepo: get_lines_by_station_group_id_vec(station_group_ids)
      StationRepo-->>Interactor: stations
      LineRepo-->>Interactor: lines
    end
    Interactor->>CompanyRepo: find_company_by_id_vec(company_ids)
    alt line_group_id present
      Interactor->>TrainTypeRepo: get_train_types_by_station_id_vec(Some(line_group_id), station_ids)
    else line_group_id absent
      TrainTypeRepo-->>Interactor: Ok([])
    end
    par phase 2
      CompanyRepo-->>Interactor: companies
      TrainTypeRepo-->>Interactor: train_types
    end
    Interactor->>BusRepo: get_nearby_candidates_by_grid(grid_key) (cache)
    alt grid miss
      BusRepo-->>Interactor: nearby stop candidates
      Interactor->>BusRepo: get_bus_lines_by_group_ids(group_id_set) (cache)
      BusRepo-->>Interactor: bus lines
    else cache hit
      BusRepo-->>Interactor: cached bus lines
    end
    Interactor-->>Client: merged station info (stations + lines + companies + train_types + nearby bus)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

ぽんと跳ねるウサギが言うよ、並列で早くなるよ🐇
グリッドに刻む足跡はキャッシュのしるし、
Noneはすぐお昼寝、余分な呼び出し無し、
バスは候補集めて重複をぴょい、
小さな跳躍でデータは整うよ ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、主な変更点である GetStationsByLineGroupId/GetStationsByLineIdList のパフォーマンス最適化を正確に反映しており、簡潔で明確です。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/optimize-line-group-and-line-id-list-rpc-performance
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@TinyKitten TinyKitten self-assigned this Mar 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@stationapi/src/use_case/interactor/query.rs`:
- Around line 2189-2191: テストが line_group_id 分岐を検証していないため、モックの
ConfigurableMockTrainTypeRepository::get_types_by_station_id_vec
を受け取った第2引数(line_group_id)に応じて返す値を変えるか、受け取った引数を assert
するように修正して、update_station_vec_with_attributes と build_route_tree_map
の挙動を厳密に検証してください;具体的には get_types_by_station_id_vec が現在常に self.train_types.clone()
を返している箇所を変更し、受け取った line_group_id に一致する TrainType のみを返すか(期待する line_group_id
を渡したときのみテストが通る)、引数が Some(1000) 等の期待値と一致することを assert するロジックを追加してテストの分岐を固定してください。
- Around line 296-297: The bus_line_cache currently keyed by rounded grid
coordinates causes order-dependent incorrect station.lines because
get_nearby_bus_lines returns strict "within 300m" results and the cached value
from one station is reused for another in the same grid bucket; change caching
to either use a stable per-station key (e.g., station_g_cd) when caching final
filtered results, or only cache the candidate set (unfiltered results from
get_nearby_bus_lines) keyed by the grid and then re-run the distance/300m filter
and apply the station-specific limit per station (reference:
get_nearby_bus_lines, bus_line_cache, station.lines, station_g_cd, and the
limit=50 candidate retrieval), and apply the same fix to the other occurrence
around the 332-342 region.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c359f4c6-bab1-4275-8a10-25af083b3baa

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5c23e and 80ea028.

📒 Files selected for processing (1)
  • stationapi/src/use_case/interactor/query.rs

Comment thread stationapi/src/use_case/interactor/query.rs Outdated
Comment thread stationapi/src/use_case/interactor/query.rs
TinyKitten and others added 3 commits March 20, 2026 11:09
1. Bus cache: cache candidate set from get_by_coordinates by grid key,
   re-apply 300m haversine filter per station's exact coordinates.
   Previously cached final results, which could include/exclude bus stops
   incorrectly for stations at different positions within the same grid cell.

2. Mock: ConfigurableMockTrainTypeRepository.get_types_by_station_id_vec
   now returns empty when line_group_id is None (matching real SQL behavior)
   and asserts expected_line_group_id when configured.

3. Tests: use create_configurable_interactor_with_line_group_id for train
   type tests; add dedicated test for line_group_id=None skipping train types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Deduplicate station_group_ids and company_ids before batch queries
  to reduce IN clause size for GetStationsByLineIdList (multiple lines
  can share station groups)
- Cache bus line query results by sorted station_group_ids to avoid
  repeated get_by_station_group_id_vec calls in the per-station loop
- Fix rustfmt formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The logic was inlined into update_station_vec_with_attributes with
per-station distance filtering and caching, making this method dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
stationapi/src/use_case/interactor/query.rs (1)

297-305: ⚠️ Potential issue | 🟠 Major

前回の懸念どおり、グリッド共有した候補 50 件では近傍バス路線をまだ取りこぼします。

各駅で 300m フィルタを掛け直しても、再利用している candidates 自体は最初の駅座標での get_by_coordinates(..., Some(50), ...) 結果です。同じグリッド内の別駅では、その 50 件の外にある停留所が 300m 圏内に入るので、密なエリアでは station.lines から正しいバス路線が落ちます。正確性を維持するなら、キャッシュ単位を station_g_cd ベースの最終結果まで下げるか、候補取得側の limit 依存を外す必要があります。

Based on learnings: Verify that helper methods such as update_station_vec_with_attributes and build_route_tree_map behave correctly when modifying QueryInteractor

Also applies to: 340-358

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@stationapi/src/use_case/interactor/query.rs` around lines 297 - 305,
現在のグリッド単位キャッシュ(bus_candidate_cache keyed by (i64,i64)) と get_by_coordinates(...,
Some(50), ...) の組み合わせは、別駅で 300m に入る停留所を取りこぼすため、候補キャッシュを
station_g_cd(駅ごと)まで細かくするか、候補取得の limit に依存しない設計に変更してください: 具体的には QueryInteractor
内で get_by_coordinates を呼ぶ箇所を見つけ(参照: get_by_coordinates 呼び出しと bus_candidate_cache
の使用箇所)、キャッシュキーを station.station_g_cd に変更して駅ごとの最終候補(既に 300m フィルタ済み)を保存するか、あるいは
grid キャッシュにするなら get_by_coordinates の limit を除去して完全な候補セットを返すようにして
bus_candidate_cache の再利用で欠落が起きないようにしてください。また、update_station_vec_with_attributes
と build_route_tree_map が QueryInteractor の状態を変更する場合に副作用がないか確認し、必要なら station
ごとの処理でクローン/ローカル変数を使って QueryInteractor の共有状態を汚染しないよう修正してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@stationapi/src/use_case/interactor/query.rs`:
- Around line 271-279: The code builds company_ids/company_map only from the
initial lines collection so later-added nearby bus lines lack company entries;
update QueryInteractor so that before calling find_company_by_id_vec (and before
building company_map used in the common enrichment loop) you first collect all
lines including any nearby bus lines (the same logic used in the common
enrichment that produces RailAndBus entries), then recompute company_ids (from
lines.iter().map(|l| l.company_cd as u32), sort_unstable, dedup) and then call
self.find_company_by_id_vec(&company_ids) so the company_map covers added nearby
bus lines; apply the same fix in the other block mentioned (around the 377-421
logic) and ensure get_train_types_by_station_id_vec still receives the full
station_ids set.

---

Duplicate comments:
In `@stationapi/src/use_case/interactor/query.rs`:
- Around line 297-305: 現在のグリッド単位キャッシュ(bus_candidate_cache keyed by (i64,i64)) と
get_by_coordinates(..., Some(50), ...) の組み合わせは、別駅で 300m に入る停留所を取りこぼすため、候補キャッシュを
station_g_cd(駅ごと)まで細かくするか、候補取得の limit に依存しない設計に変更してください: 具体的には QueryInteractor
内で get_by_coordinates を呼ぶ箇所を見つけ(参照: get_by_coordinates 呼び出しと bus_candidate_cache
の使用箇所)、キャッシュキーを station.station_g_cd に変更して駅ごとの最終候補(既に 300m フィルタ済み)を保存するか、あるいは
grid キャッシュにするなら get_by_coordinates の limit を除去して完全な候補セットを返すようにして
bus_candidate_cache の再利用で欠落が起きないようにしてください。また、update_station_vec_with_attributes
と build_route_tree_map が QueryInteractor の状態を変更する場合に副作用がないか確認し、必要なら station
ごとの処理でクローン/ローカル変数を使って QueryInteractor の共有状態を汚染しないよう修正してください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24a6ecba-86a1-48f9-a837-540c92e82d48

📥 Commits

Reviewing files that changed from the base of the PR and between 80ea028 and 417baee.

📒 Files selected for processing (1)
  • stationapi/src/use_case/interactor/query.rs

Comment thread stationapi/src/use_case/interactor/query.rs
Comment thread stationapi/src/use_case/interactor/query.rs
1. company_map now owns values and is extended with bus-only operator
   companies after nearby bus lines are added. Previously, bus lines
   from operators not in the initial rail lines had company=None.

2. Bus candidate cache key changed from grid coordinates to station_g_cd.
   Grid+LIMIT(50) could miss bus stops within 300m when a different
   station in the same grid cell had a different top-50 set. station_g_cd
   guarantees identical physical location, making the cache correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@TinyKitten TinyKitten merged commit 390baaa into dev Mar 20, 2026
11 checks passed
@TinyKitten TinyKitten deleted the feature/optimize-line-group-and-line-id-list-rpc-performance branch March 20, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 要望対応や課題解決

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant