Repository navigation
Video segment pairing fix - #177
Conversation
|
Thanks for the pull request, @felipemontoya! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h the intervals scan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32bdaa8 to
efb9e5c
Compare
|
@saraburns1 rebased the PR. I also included all your suggestions and took it a step further trimming comments to mostly one line at the top of the file or comments that are necessary to understand the intent of confusing lines. |
|
This is great overall! Because this is a bit of a new direction, and if it works well is probably something we would want to carry over to pages and problems, I have a couple of questions:
|
|
I've been benchmarking the engagement models against a dataset with known expected results (~14.8M xAPI events, 25k learners, realistic play/pause/seek journeys) on ClickHouse 25.8 limited to 4 CPU / 16 GB. I added a round for this PR before starting on other performance improvements, and here's what I found. I rendered the actual Superset chart SQL from tutor-contrib-aspects for the Course Dashboard and Individual Learner video tabs, then ran it directly. Good findings:
Issues found:
The cast(ifNull(end_video_position, 0) as int) + 1,
Largest course (~1,050 learners), warm runs:
Peak memory is about 7 GiB per chart on both. Loading the whole Videos tab at once still runs out of memory on 16 GB with either version. Since
On our dataset, v8.0.0's watched seconds come out only +0.4% above expected, not +13%. The "last end before the next play" defect only shows up on interleaved event sequences, which the random load generator produces much more often than real journeys do. The fix is still right, but real-world drift is probably less than 13%. I've been using this |
|
Fantastic review. I am a bit behind on my side, but I expect to get back on track on Tuesday. |
|
A correction and two more findings, now that we've profiled further. Correction: the view isn't what makes the video charts slow In my last comment I put the 2–3× slowdown of the video_watches charts down to
v8.0.0 looked faster mostly because it stored fewer rows. So there's no need to materialize What does fix it is a per-learner, per-video summary: distinct seconds watched, plus the most views of any second. That's all the Finding: In select type from system.columns
where table = 'video_playback_events' and name = 'emission_time_long'
-- DateTimeThe pairing window here orders by Finding: the off-by-one is easy to fix here The off-by-one you noted is range(
cast(start_video_position as int) + 1,
cast(ifNull(end_video_position, 0) as int) + 1,
1
)With both fixes, watched seconds match a known-answer dataset exactly (14.8M events, 25k learners). |
|
Last thing, then I'll stop spamming this ticket. In trying to figure out the tutor-contrib-aspects bug 1335 I built on top of this PR to get to #180 and openedx/tutor-contrib-aspects#1355 I think they fix the items I brought up above and get us to a much better overall place with both data loading and engagement chart performance, but would really appreciate your input and review. |
|
Thank you for all the great follow ups @bmtcril. I had my environment running a bunch of tests yesterday and I think I can answer your questions now.
Now on to the test runs: Setup
Correctness firstVideo seconds per learner and video, compared with the known answers:
1. Upgrade with substantial existing data
2. Per-refresh cost, partition pruning, minmax index
3. The 44.8M case with chunkingAt 44.74M video events, under the 16 GB cap:
Chunk count sweep, same SQL:
So unchunked it reproduces the failure in the description, and the default of 16 chunks passes with about 4× headroom. After this tests, I got a suggestion from claude to use ChartsSame course and learner, with course-staff row-level security, warm runs:
I finished running the test that was suggested for the chunking and if that goes well I'll update the PR in a while. |
|
Using final did not work. I'm keeping the current
After I updated the PR description, I decided that keeping the performance fixes in this PR was the best path. So I'm pushing the last commit from my local branch with your fixes. |
Backports two commits from openedx#180 (perf/engagement-models), by Tycho Hob, unchanged: - b1bedd3 fix: count video seconds from the start position and tolerate open intervals An interval from 10 to 20 covers seconds 11 to 20, not 10 to 20. ifNull stops the "Number of Views across Video Duration" chart failing on open intervals. - 70c7e1a fix: keep sub-second precision in video_playback_events.emission_time_long emission_time_long resolved to the DateTime alias and was stored as whole seconds, so events within the same second paired by verb and event id instead of time. A post_hook converts the column on existing installs; rows already stored keep their truncated times. Co-authored-by: Tycho Hob <ty@axim.org> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAGYaarieDgYgTehcBhggX
c4734e6 to
9650ee4
Compare
bmtcril
left a comment
There was a problem hiding this comment.
Thanks that all makes sense! Just a thing to note- The per-section / subsection charts reading 10.2M rows for one course is already fixed in #180 (0de2c67, which lets the course filter reach every read; 0.55 s → 0.28 s on M). So you shouldn't need to do anything there. I'll merge this and rebase 180 on it.
This PR contains the fix to issue #176. It has three commits: the fix, the tests and the performance changes.
The problem it fixes
fact_video_segments used to be a plain ClickHouse materialized view (MV) over video_playback_events. A plain MV only sees the rows in each insert batch. If a learner's played event and its closing paused event arrive in different inserts, which is normal for live traffic, they are never paired. That watched time is silently lost. fact_video_engagement was an MV built on top of it, so it lost the same data.
The new design
1. New model: models/video/fact_video_watch_intervals.sql
late or replayed ones. It runs in 16 learner chunks (ASPECTS_VIDEO_WATCH_INTERVALS_BACKFILL_CHUNKS)
to cap memory; each chunk reads all of video_playback_events.
2. The pairing logic: macros/video_watch_intervals.sql
seconds, so events in the same second were ordered by verb and event_id instead of time. A
post_hook converts the column on existing installs; rows already stored keep their truncated
times until video_playback_events is rebuilt with --full-refresh.
3. fact_video_segments is now a view
4. fact_video_engagement is now a view
5. Cleaning up the old objects
How it is tested
1. dbt unit tests (models/video/unit_tests.yaml)
2. CI integration check (.github/workflows/coverage.yml)
3. Performance:
In a local tutor environment. The host: 12 CPUs, 30 GiB RAM, of which ~11 GiB were free when testing started (the rest
held by the desktop and other projects), NVMe disk. ClickHouse 25.8.33.6 in
tutor_dev-clickhouse-1, no container limits,max_server_memory_usage27.9 GiB (0.9 × RAM),max_threads12, no per-querymax_memory_usage. The host has less free memory thanClickHouse thinks it may use, so host memory pressure can be the real limit.
Incremental: the refresh keeps up far beyond realistic traffic; memory is the limit, not time.
One refresh re-pairs the last day of video events:
At 31M video events per day (~70M xAPI events per day at the generator's 44% video share) a refresh
takes 37 s against a 5-minute interval. The next doubling would need more memory than this host has
free. Ingestion itself tops out at ~13k xAPI events/s (~1.1B/day) through the full Aspects MV chain,
far above both.
Full refresh: 10–28× faster than v8.0.0 and 4–10× less memory, but it does not stay small.
Like-for-like on the same course subsets (runs 25–27):
dbt run --full-refreshv8.0.0's extra 13% of watched seconds is its pairing defect (the last end before the next play).
Both still carry the inherited off-by-one at interval starts.The inherited off-by-one at interval starts is fixed in the last commit (backported from #180)After chunking, the 44.79M event run did not fail:
This performance review raised 5 findings, fixed in the last commit.
A more complete depiction of all the tests run was created using claude artifacts and its available here: https://claude.ai/artifact/Gctp5pViYWeFCh2cV2CtWn