-
Notifications
You must be signed in to change notification settings - Fork 903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix update triggers issue caused by decompression #6903
Conversation
b65f9e2
to
e127bc5
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6903 +/- ##
==========================================
+ Coverage 80.06% 81.01% +0.94%
==========================================
Files 190 199 +9
Lines 37181 37317 +136
Branches 9450 9740 +290
==========================================
+ Hits 29770 30231 +461
- Misses 2997 3186 +189
+ Partials 4414 3900 -514 ☔ View full report in Codecov by Sentry. |
1b9bb6a
to
4bfbedb
Compare
SELECT 'f2ca7073-1395-5770-8378-7d0339804580', '2024-04-16 04:50:00+02', | ||
1100.00, '2024-04-23 11:56:38.494095+02' FROM generate_series(1,2500,1) c; | ||
|
||
VACUUM FULL update_trigger_test; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why VACUUM FULL
here??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue depends on the tuple storage on block level. This was the only way to get a consistent reproduction case.
@antekresic can you explain more about this issue? What row was it trying to lock that was invisible? |
/* use a static copy of current transaction snapshot | ||
* this needs to be a copy so we don't read trigger updates | ||
*/ | ||
estate->es_snapshot = RegisterSnapshot(GetTransactionSnapshot()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that on the next update cycle, we're trying the tuple previously modified by the BEFORE UPDATE trigger on the previous cycle? And the "invisible" error happens in GetTupleForTrigger()
?
I think normally the new tuples from the previous update cycles are skipped because they are TM_SelfModified
and not TM_Invisible
. This is determined by HeapTupleSatisfiesUpdate
. So if we're getting TM_Invisible
, this means we either forgot to update the command counter after modifying the tuple, or we're taken the new snapshot with command counter higher than the previous update. I guess here it's the latter and you prevent that by making a copy of the snapshot?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally the es_snapshot
is initialized with a registered snapshot copy (standard_ExecutorStart
), so this is probably correct. But I wonder if we should re-take the snapshot like this at all? Not sure if it's a standard practice. Normally the update code uses the es_output_cid
as the current command id for locking the tuples, so maybe just incrementing the output cid after decompression would be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, exactly, what you said. By using a static snapshot which includes the decompressed tuples, we are making sure we see only those tuples and not the updated versions after this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to update the snapshot of the estate so the decompressed tuples (which are inserted in the same transaction) are included in the scan of the uncompressed chunk. That's the main reason for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I wonder if we should re-take the snapshot like this at all? Not sure if it's a standard practice. Normally the update code uses the es_output_cid as the current command id for locking the tuples, so maybe just incrementing the output cid after decompression would be enough?
Looks like we must do it, otherwise we won't see the tuples we've decompressed. The es_output_cid
comes into play later, when we do see the tuples and want to determine if they are updated by us.
/* Modify snapshot only if something got decompressed */ | ||
if (ts_cm_functions->decompress_target_segments && | ||
ts_cm_functions->decompress_target_segments(ht_state)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think I managed to understand the rationale why we add the RegisterSnapshot
below. But what about this new check, why is it needed? Your test passes without this change with just the RegisterSnapshot
. I also double-checked that it fails w/o the RegisterSnapshot
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems reasonable to me that the update process should work even if we just increment command counter and take the new snapshot unconditionally always.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New check is to disable messing with snapshots (which I really don't like) when there is nothing decompressed.
I started of by fixing this issue for uncompressed hypertables and that was the first change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that seems to be the correct way to work with the snapshots there, so maybe we should instead apply it unconditionally? If you only apply it under some complicated conditions, the bugs will be more difficult to find. E.g. now it looks like we should add isolation tests with both REPEATABLE READ and READ COMMITTED to test the result == TM_Deleted && !IsolationUsesXactSnapshot()
code path. We still have to do the potentially problematic snapshot operations in some cases, and if we add more conditions to invoke them, I would say it doesn't really improve the things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take issue with increasing command counter when there isnt anything that warrants it. Also snapshot manipulation is just not necessary if nothing is decompressed.
So I personally don't see a problem here. There is already a check for compression support which skips all this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to add an isolation test for the "deleted" branch.
Update triggers were broken due to snapshot manipulation and usage of dynamic snapshots for scanning tuples. Using a static snapshot guarantees we cannot see tuples that would be invisible during an update trigger.
4bfbedb
to
156e1cc
Compare
Automated backport to 2.15.x not done: cherry-pick failed. Git status
|
Update triggers were broken due to snapshot manipulation and usage of dynamic snapshots for scanning tuples. Using a static snapshot guarantees we cannot see tuples that would be invisible during an update trigger.
Fixes #6858