Session-grouped queries treat every sessionless event as one session (wrong results, ClickHouse OOM)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 52/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- clickhouse, sql, typescript
調査の方向性
Start with funnel.sql.ts, conversion.sql.ts, sankey.sql.ts, overview.sql.ts and pages.sql.ts, following the query names listed in the issue. Add the empty-session guard to per-session query paths and make sessionless view duration zero; leave profile grouping and the listed follow-ups out of scope. Done means sessionless events no longer join into shared paths or conversions, while profile-grouped results and view and visitor counts remain unaffected.
索引モデルが issue の本文から書いたものです。
説明
Problem
Events tracked from a backend are stored with session_id = '' by design. This covers secret-authenticated /track calls with a server user agent when the profile has no live browser session (incoming-event-handler.ts falls back to the '' that getDeviceId returns), and events whose timestamp is in the past.
The queries that group, partition or join by session_id don't exclude ''. Every sessionless event in the project and date range therefore lands in one group, which the query treats as a single session:
| Surface | Query | Effect |
|---|---|---|
| Funnel, grouped by session (the default) | funnel.sql.ts funnelBase (GROUP BY session_id + windowFunnel) |
Step 1 from user A and step 2 from user B count as one completed funnel |
| Conversion, grouped by session | conversion.sql.ts conversionQuery |
Same as the funnel: conversions that never happened |
| Flows (sankey) | sankey.sql.ts (GROUP BY session_id + groupArray) |
One path stitched together from every server-side user's events. The start_event_sessions / end_event_sessions anchors match '' too |
| Overview → User Journey | overview.sql.ts topEntriesQuery / transitionsQuery (GROUP BY session_id + groupArray(path)) |
Fake entry pages and transitions, plus one array holding every sessionless screen view in the range |
| Page-filtered overview metrics, Pages table | overview.sql.ts metricsWithPageFilterQuery, pages.sql.ts topPagesQuery (lead() OVER (PARTITION BY session_id …)) |
A sessionless view's "duration" is the gap to the next sessionless view by anyone |
Profile-grouped funnels and conversions are not affected, since they group by profile_id.
OpenPanel already treats '' as "no session" elsewhere. The import pipeline only builds sessions from session_id != '' (and never for device = 'server'), and code migration 4-add-sessions filters session_id != ''. The analytics queries just never got the same guard.
Reproduction
-
Track two server-side events for two different profiles with a client secret and a non-browser user agent. Neither profile has a browser session:
for p in user-a:screen_view user-b:sign_up; do curl -sS -X POST "$API_URL/track" -H 'content-type: application/json' \ -H "openpanel-client-id: $CLIENT_ID" -H "openpanel-client-secret: $CLIENT_SECRET" \ -H 'user-agent: my-backend/1.0' \ -d "{\"type\":\"track\",\"payload\":{\"name\":\"${p#*:}\",\"profileId\":\"${p%%:*}\",\"properties\":{\"__path\":\"/x\"}}}" done -
SELECT profile_id, session_id, name FROM events WHERE project_id = '<project>' ORDER BY created_atshows both rows withsession_id = ''. -
Create a funnel
screen_view → sign_upgrouped by session. It shows 1 conversion, although no user did both steps. Grouped by profile it correctly shows 0. -
Open Flows starting at
screen_view. It drawsscreen_view → sign_up, a path nobody took. -
Send more sessionless
screen_views with different paths, then open Overview → User Journey. The paths are stitched together across users.
On a project that sends a lot of server-side screen_views, step 5 also shows the memory problem. All of those rows go into a single groupArray, which can't be split across threads or shards.
Impact
- Wrong numbers. Per-session funnels and conversions are inflated, and flows and user journeys show paths that don't exist. Nothing in the UI shows it, so the numbers look fine.
- Memory blow-up. The combined "session" grows with all sessionless traffic in the range. On 2026-10-20 a self-hosted ClickHouse was OOM-killed by the Overview User Journey query grouping about 97k sessionless
screen_viewrows into one session. A local reproduction with 1M sessionless rows: the user-journey grouping peaked at about 92 MB for one 1,000,000-element path. With the guard it peaked at about 9 MB, and the longest path was 5.
Proposed fix
Add session_id != '' wherever a query builds per-session state, and leave profile grouping alone:
- Funnel and conversion: add the filter to the scan only when the group is
session_id. - Sankey: add it to the path scan and to the start/end anchor CTEs.
- Overview user journey: add it to
ordered_events. - The
lead() OVER (PARTITION BY session_id)screen-view duration: give a sessionless view 0, the same as a session's last view, rather than the gap to someone else's event. View and visitor counts stay as they are.
Out of scope for this issue, possible follow-ups:
uniq(session_id)/countDistinct(session_id)session counts (the overview, realtime, widgets, the chart "sessions" segment) count''as one extra session. That's off by at most one and doesn't blow up memory.- The realtime map's "latest event per session" list can show one
''row. - The
session.performed_eventfilter'ssession_id IN (SELECT DISTINCT session_id …)matches every sessionless event once any sessionless event performed the step.
- 主要言語
- TypeScript
- スター
- 7.2k
- フォーク
- 523
- 平均マージ
- 19時間 23分
- マージ済み PR(30日)
- 25
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Openpanel-dev/openpanel のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
Openpanel-dev/openpanel#579 ·
メンテナーはふだん 1 日以内に返信
-
ClickHouse retry logs print CLICKHOUSE_URL with its password対応中かも @ppjsw が 1 日前に担当しました。 オープン
難易度 1/5 1〜3時間 初心者へのやさしさ 92/100
Openpanel-dev/openpanel#551 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
login page needs refinement再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
Openpanel-dev/openpanel#495 ·
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
Openpanel-dev/openpanel#563 ·
メンテナーはふだん 1 日以内に返信
-
Session replay: early chunks saved under the wrong session再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 35/100
Openpanel-dev/openpanel#537 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
Openpanel-dev/openpanel の issue をすべて見る
似ている issue
-
Add: Pbs Kidsオープンcheck:passed streams:add
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
メンテナーはふだん 2 日以内に返信
-
maintanance
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
inmanta/web-console#7583 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
deltachat/deltachat-desktop#6881 · コメント 1 件 ·
メンテナーはふだん 3 日以内に返信
-
[Bug Report][3.13.7] VDataTable with select-strategy="single" shows select-all checkbox in mobile mode対応中かも @lazerg が今日担当しました。 オープンS: triage
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
phivk/rubiks-lab#33 ·
メンテナーはふだん 1 日以内に返信