Use delete_all for ClosetList hangers to avoid N+1 on deletion #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dice/fix-closet-list-delete-performance"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hanger after_destroy callbacks only touch last_trade_activity_at, which
is already covered by ClosetList's own after_destroy callback.
👍 Had the robot gut-check what the relevant hooks actually are, looks like we're solid!
Analysis results
Switching
ClosetList#hangerstodependent: :delete_allThe problem
Destroying a
ClosetListwithdependent: :destroyloads every hanger intomemory, instantiates each one, and runs its callbacks one at a time. That's the
hang on large lists. Switching to
delete_allissues a singleDELETE FROM closet_hangers WHERE list_id = ?instead.What
delete_allskipsPer-hanger
destroyruns these, anddelete_allbypasses them:after_destroy :log_trade_activity, if: :trading?— touchesuser.last_trade_activity_at.belongs_to :list, touch: true— touches the list'supdated_at.That's the entire list. Crucially:
ClosetHangerhas nohas_many ... dependent:, sodelete_allcan't orphan any child rows. (This is the thing that makesdelete_alldangerous in the general case — and it doesn't apply here.)itemoruser. Nothing is left stale.Are the skipped behaviors a real loss?
Basically no.
belongs_to :list, touch: true— touches the list'supdated_at, but thelist is being deleted in the same transaction. Pure waste, zero loss.
log_trade_activity— looks scary, but isn't, because of howtrading?resolves. A hanger's
trading?delegates topossibly_null_closet_list.trading?,and since these hangers all have a
list, that's justlist.trading?. So:ClosetListhas its ownafter_destroy :log_trade_activity, if: :trading?, which still fires underdelete_all(we're only changing the dependent strategy; the list itself isstill
destroyed). Souser.last_trade_activity_atis still touched — once,instead of once-per-hanger.
log_trade_activityis justtouch(:last_trade_activity_at), an idempotenttimestamp bump. Today we do it N+1 times; after the change, once. Same end state.
Verdict
Safe and good. No meaningful behavior lost, and the activity-logging that matters
is preserved by the list's own
after_destroy. Atomicity is preserved too —dependent: :delete_allstill runs inside the parent destroy's transaction.