fix(etl): four prod-clone-run bugs + #307 test fixes folded in - #308
Merged
Conversation
Findings from running the indexer against a production database clone for ~3h and diffing the result against a live production read replica. This PR includes the corrected versions of #307's test fixes (one had a regression we caught in the diff) plus four new prod-only bugs. 1. save_type/repost_type mistyped on entity-id collisions (REGRESSION from PR #307). When chain entity_type was "Playlist", #307 fell through to inferSaveType, which ranks tracks first. Whenever a track existed with the same numeric id as the playlist being saved (track_id and playlist_id are independent namespaces — collisions are real), the save was written with save_type='track' instead of 'playlist'. Observed against prod: ~1% of saves mistyped. Fix: resolveSaveType / resolveRepostType only disambiguate "Playlist" via playlists.is_album; never cross over to "track". New TestSave_Playlist_WhenTrackIdCollides locks this in. 2. mergeNullStr treats "" as "clear field" instead of "no change". Chain User Update txs commonly include "handle":"" when the client doesn't want to change handle; our handler was wiping users.handle to NULL while leaving handle_lc populated. Real data corruption observed against prod (e.g. user_id=169901003 'richard627'). Fix: empty string now preserves the existing value, matching the prod indexer. 3. PRIMARY KEY violations on re-delivered txs (shares_pkey, reposts_pkey, saves_pkey, follows_pkey, subscriptions_pkey). Bare INSERTs into row-versioned (entity_keys..., txhash) PKs hit 23505 when the prefetcher re-delivers a chain tx (root cause deferred). All five domain inserts now have ON CONFLICT (...) DO NOTHING — txhash is content-addressable, so re-delivered data is identical. 4. blockhash NOT NULL on prod schema. Our migrations declare `blockhash ... NOT NULL DEFAULT ''` (so omitting the column locally silently writes ''), but prod schemas have no default — every INSERT must include it. Observed events.blockhash 23502 violation in prod-clone run. Added blockhash to INSERTs into events, associated_wallets, muted_users. Also folded in from #307 (the five non-regression fixes): 5. AssociatedWalletCreate: SOL base58 case preservation. ETH addresses lowercase; SOL preserve case (uppercase `L` → invalid base58 `l`). New `canonicalizeWallet` helper handles both based on chain or `0x` prefix. 6. AssociatedWalletDelete: drop bogus `chain = $4` filter from the UPDATE — delete tx doesn't include chain, so the filter was always matching empty string and updating nothing. Validation already keys on (user_id, wallet); UPDATE now matches. 7. dashboard_wallet_test.go: lowercase wallet address in test queries to match handler's canonical storage form. 8. track_update_test.go: seed user wallet matching the signer so the test reaches the existence check it means to exercise. Closes #307 (superseded). Deferred: "no rows in result set" raw error (needs context attribution), TCP "can't assign requested address" (environmental). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
Merged
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Findings from running the indexer for ~3h against a production database clone (audius_discovery on 104.198.147.20) and diffing the result against the live production read replica. Bundles fixes for four prod-only bugs that only surface against populated/prod-schema data, plus the five non-regression fixes from #307 (which this PR supersedes — see "Why this supersedes #307" below).
Bugs and fixes
1.
save_type/repost_typemistyped on entity-id collisions — REGRESSION from PR #307When chain entity_type is
"Playlist", PR #307'sresolveSaveType/resolveRepostTypefell through toinferSaveType, which ranks tracks first. Whenever a track exists with the same numeric id as the playlist being saved (track_id and playlist_id are independent namespaces — collisions are real in prod), the save was written withsave_type='track'instead of'playlist'.Observed against prod:
{"is_save_of_repost":false}save_type='playlist'✓save_type='track'✗Fix:
resolveSaveType/resolveRepostTypeonly disambiguate"Playlist"viaplaylists.is_album— they never cross over to"track". NewTestSave_Playlist_WhenTrackIdCollideslocks this in: seeds both a track and a playlist with the same id and asserts the Playlist save writessave_type='playlist'.2.
mergeNullStrempty-string semanticsChain User Update txs commonly include
"handle":""when the client doesn't want to change handle. The oldmergeNullStrtreated""as "clear the field" → wipedusers.handleto NULL while leavinghandle_lcpopulated (inconsistent row state).Observed against prod: user
richard627(id 169901003) hadhandle=NULLon clone buthandle='richard627'on replica — same chain tx, different interpretation.Fix:
""now preserves the existing value, matching the prod indexer's behavior.3. PRIMARY KEY violations on re-delivered txs (family bug)
shares_pkey,reposts_pkey,saves_pkey,follows_pkey,subscriptions_pkey— all five have(entity_keys..., txhash)row-versioning PKs. The bare INSERTs in social_save / social_repost / social_share / social_follow hit 23505 when the prefetcher re-delivers a chain tx (root cause of re-delivery deferred — separate investigation).Fix: each INSERT now has
ON CONFLICT (entity_keys..., txhash) DO NOTHING.txhashis content-addressable, so re-delivered data is identical by construction.4.
blockhash NOT NULLviolations on prod schemaOur migrations declare
blockhash ... NOT NULL DEFAULT ''(so omitting the column locally silently writes''), but the prod schema for 14 tables has no default — every INSERT must includeblockhashexplicitly. Observed:events.blockhash23502 violations during prod-clone run.Fix: added
params.BlockHashto INSERTs intoevents,associated_wallets,muted_users. (Sweeping the other 11 NOT-NULL-blockhash tables is a follow-up; the handlers for them already include blockhash.)Why this supersedes #307
PR #307 had six test fixes; one of them (the
resolveSaveTypeforTestSave_Album_Success) introduced the save_type regression in bug #1 above. Rather than force-push #307, this PR includes the corrected version of that fix plus the other five (which were good). Recommend closing #307 in favor of this PR.The five non-regression fixes carried forward:
AssociatedWalletCreateSOL base58 case preservation (newcanonicalizeWallethelper)AssociatedWalletDeleteUPDATE drops the boguschain = $4filter that matched empty stringdashboard_wallet_test.goqueries use lowercase wallet (matches handler's canonical storage)track_update_test.goseeds user wallet matching the signerTest plan
go build ./...+go vet ./...cleanTestSave_Album_Success)TestSave_Playlist_WhenTrackIdCollidescovers the regressiongo test ./pkg/etl/...clean except for the pre-existing migrate-down/up flake onTestTrackCreate_AppliesAccessNormalization(unrelated)Deferred follow-ups
no rows in result setraw error from some handler (needs context attribution —indexer.go:425log line doesn't carry entity_type/action; sweeping handlers for unwrappedQueryRow.Scanwill catch it)🤖 Generated with Claude Code