Repository navigation
Pathway catalog and enrollment sink - #274
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. |
fed90f3 to
f05116b
Compare
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Raising here is not an option: PathwayEnrollment is CASCADE-deleted with its user, so an | ||
| exception would block user deletion. | ||
| """ | ||
| logger.warning( |
There was a problem hiding this comment.
@bmtcril I believe this is the major open point I have at this moment. We don't have things that delete their clickhouse counterpart yet in the codebase. The only Sink that issues a delete command is the UserRetirement, but that is for PII reasons.
I see three options:
- Drop the handler. Consistent with the existing sinks. A deleted pathway stays in the ClickHouse
catalog_pathwaytable forever and keeps showing up in reports. I think this is not ideal for data consistency. - Delete the row in ClickHouse (lightweight
DELETE FROM … WHERE id = …, queued on commit). Needs only the id and mirrors whatUserRetirementSinkalready does. ClickHouse deletes are relatively expensive, which is acceptable given how rarely pathways are deleted. There is produect decision on wheter we want the reports to show that a catalog pathway existed and then was deleted because we will still keep the item fulfillment in clickhouse. - Tombstone column (
is_deleted) set by the delete handler. Keeps a "deleted at" history. The row is gone from MySQL, so the handler must serialize the in-memory instance rather than queue a dump by id, which breaks the current sink pattern. It also needs a new column in the tutor-contrib-aspects schema andReplacingMergeTree(ver, is_deleted).
There was a problem hiding this comment.
I think we want option 3 for this. It could have a separate sink for the tombstone path than the model save.
There was a problem hiding this comment.
So the pattern would be a separate tombstone sink class that writes to the same catalog_pathway table, and is_deleted has to be added to the table in the tutor-contrib-aspects alembic migration. Correct?
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f05116b to
450277b
Compare
|
@bmtcril I added the tombstone field for CatalogPathways sink here and the fields in the alembic PR |
|
As mentioned on the other PR, I have some unvalidated AI feedback. I can dig into it more Monday or if you get to it sooner that's fine too. Here's what I've got: Should fix:
Fine as-is:
How the fake data compares to the real data (xapi-db-load #286)
|
Deleting a pathway doesn't cascade. In openedx-core, PathwayEnrollment.catalog_pathway is on_delete=PROTECT, put there on purpose so deleting the wrong pathway can't wipe out enrollment history. delete_catalog_pathway() documents that it raises ProtectedError while anyone is or ever was enrolled. To check it, I ran this in the LMS shell on my env: pick an existing enrollment, then call .delete() on its pathway inside a transaction.atomic() that rolls back. It raised ProtectedError: Cannot delete some instances of model 'CatalogPathway' because they are referenced through protected foreign keys: 'PathwayEnrollment.catalog_pathway'. Unenrolling doesn't delete the row either. It sets is_active=False and sends post_save (that's the openedx-core fix), so the sink already picks it up. The only hard deletes left are a user deletion (the user FK is CASCADE) and someone deleting an enrollment by hand in the admin. Both are rare, since retirement doesn't delete auth_user.
This one is true. For each row, get_actor_id goes through ExternalId.add_new_user_id, which does a get_or_create. Each new ExternalId fires post_save, and the existing handler queues an ExternalIdSink task. I followed this path in the code. I didn't reproduce it at 50k scale. Some context on the size of it: tasks are only queued for learners who don't have an xapi ExternalId yet. event-routing-backends makes the same add_new_user_id call the first time a learner emits an xAPI event, so anyone with any activity already has one. With PII off, external_id isn't enabled, so each queued task returns "Disabled" right away. The cost is the queue churn. One more thing the report missed: even when nothing is created, it's 2 extra MySQL queries per enrollment. I didn't document any of this because I thought it is a detail of implementation that it's very rare to hit real actual users and I didn't want to overdocument stuff. I have this issue with my coding assistant that it wants to write comments for every line and I tent towards only writing really important one liners. If you think it's worth the annotation, I can update the readme.
True, and it's on purpose, but I didn't document why (similar to the prev comment). catalog_pathway needs the tombstone, and ReplacingMergeTree(ver, is_deleted) only collapses versions when the sort key is just id and there's a real version column, hence the DateTime64. pathway_enrollment has no delete path (unenroll is a save with is_active=False), so I kept the same layout as every other sink table (String time_last_dumped, ORDER BY (id, time_last_dumped)). I checked migrations 0007, 0016/0027, 0026, 0036 and 0039, and they all use that layout. Downstream it gets deduped like the others, with ROW_NUMBER() OVER (PARTITION BY id ORDER BY time_last_dumped DESC). If you'd rather have both on the replacing layout for consistency, I can switch pathway_enrollment too. As with the last one if you think this is really key to document I can update the docstrings in the migration. About the xapi-db-load I fixed the datetime format to match what comes out of a real pathway sink. Also added the code to load pathway enrollments. The update is in the same PR. |
|
This is released as v2.2.0 |
What the PR adds
Two model sinks for the openedx-core Pathways models (
openedx_catalog, openedx-core ≥ 1.14), built like the existing tag and external-id sinks: apost_savehandler queuesdump_data_to_clickhouseon commit, and the existing backfill command works for both (--object catalog_pathway,--object pathway_enrollment).Requirements:
Tests
get_querysetof both sinks, both signal handlers deferring toon_commit, the serializer'sactor_id, andget_xapi_actor_id(happy path and missingxapitype).Checked by hand on a Tutor
maindev stack (edx-platformmaster, openedx-coremain, ERB 10.1.0, PII off), with the ompanion tables applied.--forcebackfill wrote the pre-existing ones.xapiExternalIdbeforehand; the dump created it, and the row'sactor_idmatches it. For an existing learner the id equals ERB'sget_anonymous_user_id(user_id, "XAPI").ExternalId.Pending:
Merge checklist:
Check off if complete or not applicable:
Conventional Commits type — they determine the
release:
fix:→ patch,feat:→ minor,!/BREAKING CHANGE:→ major