Matthias,

First off, I can't thank you enough for taking the time to review in detail the 
patch.  I appreciate and value your time and excellent feedback.

Second, I think that I should admit to the fact that I've also been working on 
making PHOT functional again.  I have it rebased against master, however it is 
still at the proof-of-concept phase and there are some larger issues to flesh 
out.  One of the changes in this patch would have enabled that future with 
PHOT, specifically the bitmap as the method for conveying what indexes need 
updating.

That said, I agree that this patch should simply focus on expanding HOT to 
expression indexes and partial indexes.  With that in mind I will return to the 
TU_UpdateIndexes approach, even though I'm not a fan of it, and later propose 
the bitmap approach (or something better) as part of PHOT if and when that's 
ready.

Changes v5 to v6:
* reverted to TU_UpdateIndexes rather than Bitmapset
* renamed ExecIndexesRequiringUpdates to ExecIndexesExpressionsWereNotUpdated
* ExecIndexesExpressionsWereNotUpdated returns bool, exits early if possible
* simple_update paths can be HOT once again
* removed efforts to determine the subset of updated summarizing indexes
* create filtered IndexInfo list in relcache containing only indexes with 
expressions
* now using index TupleDesc CompactAttributes for arguments to datum_is_equal() 
<- did I get this one right, I'm not sure it is what you had in mind

best.

-greg


> On Feb 15, 2025, at 5:49 AM, Matthias van de Meent 
> <boekewurm+postg...@gmail.com> wrote:
> 
> On Thu, 13 Feb 2025 at 19:46, Burd, Greg <gregb...@amazon.com> wrote:
> 
> -----
> 
> I'm not a fan of how you replaced TU_UpdateIndexes with a bitmap. It
> seems unergonomic and a waste of performance.
> 
> In HEAD, we don't do any expensive computations for the fast path of
> "indexed attribute updated" - at most we do the bitmap compare and
> then set a pointer. With this patch, the way we signal that is by
> allocating a bitmap of size O(n_indexes). That's potentially quite
> expensive (given 1000s of indexes), and definitely more expensive than
> only a pointer assignment.

Fair point.  I'm not a fan of the TU_UpdateIndexes enum, but I appreciate your 
argument against the bitmapset.  Under the conditions you describe (1000s of 
indexes) it could grow unwieldy and impact performance.

> In HEAD, we have a clear indication of which classes of indexes to
> update, with TU_UpdateIndexes. With this patch, we have to derive that
> from the (lack of) bits in the bitmap that might be output by the
> table_update procedure.

Yes, but... that "clear indication" is lacking the ability to convey more 
detailed information.  It doesn't tell you which summarizing indexes really 
need updating just that as a result of being on the HOT path all summarizing 
indexes require updates.

> I think we can do with an additional parameter for which indexes would
> be updated (or store that info in the parameter which also will hold
> EState et al). I think it's cheaper that way, too - only when
> update_indexes could be TU_SUMMARIZING we might need the exact
> information for which indexes to insert new tuples into, and it only
> really needs to be sized to the number of summarizing indexes (usually
> small/nonexistent, but potentially huge).

Okay, yes with this patch we need only concern ourselves with all, none, or 
some subset of summarizing as before.  I'll work on the opaque parameter next 
iteration.

> -----
> 
> I think your patch design came from trying to include at least two
> distinct optimizations:
>  1) to make the HOT-or-not check include whether the expressions of
> indexes were updated, and
>  2) to only insert index tuples into indexes that got updated values
> when table_tuple_update returns update_indexes=TU_Summarizing.

It did.  (1) for sure, but the second is more related to the PHOT work (as 
mentioned above).  With that work almost all updates are on the HOT/PHOT path 
and so the bitmap of changed indexes is small.  It would take an update of a 
table with 1000s of indexes where almost all those were modified to create a 
bitmap that was large, which certainly could (and somewhere likely does) exist, 
but it's not the common case (I'd imagine).  In that case the work to update 
all those indexes will likely dwarf the work to build that bitmap, but I could 
be wrong.

But you're fundamentally right, I'm conflating two ideas and I shouldn't.  I 
will focus the changes required for this idea without pulling in changes useful 
to a future ones.

> While they touch similar code (clearly seen here), I think those
> should be implemented in different patches. For (1), the current API
> surface is good enough when the EState is passed down. For (2), you'll
> indeed also need an additional argument we can use to fill with the
> right summarizing indexes, but I don't think that can nor should
> replace the function of TU_UpdateIndexes.

Okay, I'll combine the earlier v3 patch with the changes in v5.  That should 
leave TU_UpdateIndexes in place and allow for HOT with expression indexes.  
I'll change the ExecIndexesRequiringUpdates() a bit (and rename it) so that it 
is just a boolean test for any index that spoils the HOT path and can exit 
early in that case potentially avoiding extra work.

> If you agree with my observation of those being distinct
> optimizations, could you split this patch into parts (but still within
> the same series) so that these are separately reviewable?

I agree, but I think that a single simple more focused patch will suffice.

> -----
> 
> I notice that ExecIndexesRequiringUpdates() does work on all indexes,
> rather than just indexes relevant to this exact phase of checking. I
> think that is a waste of time, so if we sort the indexes in order of
> [hotblocking without expressions, hotblocking with expressions,
> summarizing], then (with stored start/end indexes) we can save time in
> cases where there are comparatively few of the types we're not going
> to look at.

If I plan on just having ExecIndexesRequiringUpdates() return a bool rather 
than a bitmap then sorting, or even just filtering the list of IndexInfo to 
only include indexes with expressions, makes sense.  That way the only indexes 
in question in that function's loop will be those that may spoil the HOT path.  
When that list is length 0, we can skip the tests entirely.

> As an extreme example: we shouldn't do the (comparatively) expensive
> work evaluating expressions to determine which of 1000s of summarizing
> indexes has been updated when we're still not sure if we can apply HOT
> at all.

That makes sense, and your sorting idea would inform that kind of work.  I'll 
keep that in mind if I reintroduce code that aims to only update changed 
summarized indexes.

> (Sidenote: Though, arguably, we could be smarter by skipping index
> insertions into unmodified summarizing indexes altogether regardless
> of HOT status, as long as the update is on the same page - but that's
> getting ahead of ourselves and not relevant to this discussion.)
> 
> -----
> 
> I noticed you've disabled any passing of "HOT or not" in the
> simple_update cases, and have done away with the various checks that
> are in place to prevent corruption. I don't think that's a great idea,
> it's quite likely to cause bugs.

Yes.  I'll resurrect that.

> -----
> 
> You're extracting type info from the opclass, to use in
> datum_image_eq(). Couldn't you instead use the index relation's
> TupleDesc and its stored attribute information instead? That saves us
> from having to do further catalog lookups during execution. I'm also
> fairly sure that that information is supposed to be a more accurate
> representation of attributes' expression output types than the
> opclass' type information (though, they probably should match).

I hadn't thought of that, I think it's a valid idea and I'll update 
accordingly.  I think I understand what you are suggesting.
 
> 
> -----
> 
> The operations applied in ExecIndexesRequiringUpdates partially
> duplicate those done in index_unchanged_by_update. Can we (partially)
> unify this, and pass which indexes were updated through the IndexInfo,
> rather than the current bitmap?

I think I do that now, feel free to say otherwise.  When the expression is 
checked in ExecIndexesExpressionsWereNotUpdated() I set:

/* Shortcut index_unchanged_by_update(), we know the answer. */                 
                                                                                
                           indexInfo->ii_CheckedUnchanged = true;               
                                                                                
                                                      
indexInfo->ii_IndexUnchanged = !changed;

That prevents duplicate effort in index_unchanged_by_update().

> -----
> 
> I don't see a good reason to add IndexInfo to Relation, by way of
> rd_indexInfoList. It seems like an ad-hoc way of passing data around,
> and I don't think that's the right way.

At one point I'd created a way to get this set via relcache, I will resurrect 
that approach but I'm not sure it is what you were hinting at.  The current 
method avoids pulling a the lock on the index to build the list, but doing that 
once in relcache isn't horrible.  Maybe you were suggesting using that opaque 
struct to pass around the list of IndexInfo?  Let me know on this one if you 
had a specific idea.  The swap I've made in v6 really just moves the IndexInfo 
list to a filtered list with a new name created in relcache.


  

Attachment: v6-0001-Expand-HOT-update-path-to-include-expression-and-.patch
Description: v6-0001-Expand-HOT-update-path-to-include-expression-and-.patch

Reply via email to