Skip to content
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

Enhancing-copy-throughput-by-storing-offsets-for-tuple #21929

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

naanagon
Copy link

@naanagon naanagon commented Apr 11, 2024

In the sample table we have almost all char columns and few varchar columns for both of these data types attlen in pg_attribute table is -1. Since we have varlen attributes in nocachegetattr function it is going to this part of the code (nocachecode, ybcModifyTable) . In this way the time complexity is O(n*n) where n is number of attributes.

there is an another for loop in (nocachecode) as it is doing the same work every time, whey can't we build a prefix sum array once for the row and just use that for all the attributes.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Nandini Krishna Anagondi seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@jasonyb jasonyb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the core issue is with upstream PG code, unless this access pattern by YB is discouraged by upstream PG:

for (AttrNumber attnum = minattr; attnum <= natts; attnum++)
	column->datum = heap_getattr(tuple, attnum, tupleDesc, &column->is_null);

Comment on lines 732 to 734
* Otherwise, check for non-fixed-length attrs up to and including
* target. If there aren't any, it's safe to cheaply initialize the
* cached offsets for these attrs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that, ever since the beginning as far as Git history shows, caching attr offsets was disabled for variable-length attributes. I don't know why exactly that is the case, but it calls into question whether your caching is safe.

Copy link
Author

@naanagon naanagon Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since they are varlen attributes the offsets of attributes will changes from one tuple and another tuple and i think that is reason offsets aren't cached. I'm caching for each tuple and marking the array NULL after that.


for (j = 0; j <= attnum; j++)
{
if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supposing it is safe to cache varlen offsets for the case you are targetting, I think a cleaner solution is to reuse the existing attcacheoff rather than duplicate code. You could add a YB exception here (perhaps by a boolean argument to the function) so that slow is not set to true, and the caller can reset these attcacheoff back to -1 when done? Note I haven't given the idea much thought and there could be holes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is safe to cacheoffsets for each row. Even i was thinking in the same line but i was stuck on how to reset to -1 only for this varlen case at ybcModifyTable level. But if we throw exception then that requires changes at many places which is hard for now to review.. Let me also rethink on this.

Comment on lines 759 to 763
* ... only we don't have it yet, or we'd not have got here. Since
* it's cheap to compute offsets for fixed-width columns, we take the
* opportunity to initialize the cached offsets for *all* the leading
* fixed-width columns, in hope of avoiding future visits to this
* routine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this is the O(n^2) behavior in case all your columns are not fixed-width.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slow becomes true as we have varlen attributes. This is the loop that is causing O(n^2) - link

naanagon

This comment was marked as duplicate.

@andrei-mart
Copy link
Contributor

It is correctly pointed out that repeatable calls to heap_getattr are generally inefficient.
However, in cases like this, when all or most of values are fetched, you can simply use heap_deform_tuple.
Same or even better efficiency as heap_getattr with cache, no need to add anything to the library.

@naanagon naanagon force-pushed the enhancing-copy-thoughput branch 4 times, most recently from e0f846e to 107e5d4 Compare April 20, 2024 17:20
@naanagon
Copy link
Author

naanagon commented Apr 20, 2024

It is correctly pointed out that repeatable calls to heap_getattr are generally inefficient. However, in cases like this, when all or most of values are fetched, you can simply use heap_deform_tuple. Same or even better efficiency as heap_getattr with cache, no need to add anything to the library.

Thanks andrei for taking a quick look. Based on your suggestion given in slack, using TupleTableSlot is the best option. I have updated the pr accordingly. Didn't combine tupDesc and slot because in classes like matview.c the tupleDesc is retrieved from diff struct like DR_transientrel, which i'm thinking may not be updated in slot.

Please lmk if i'm missing anything.

Copy link

netlify bot commented May 1, 2024

Deploy Preview for infallible-bardeen-164bc9 ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 2ea43a7
🔍 Latest deploy log https://app.netlify.com/sites/infallible-bardeen-164bc9/deploys/66326314d88c55000922e5a7
😎 Deploy Preview https://deploy-preview-21929--infallible-bardeen-164bc9.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@@ -824,8 +824,12 @@ InsertOneTuple(Oid objectid)
if (objectid != (Oid) 0)
HeapTupleSetOid(tuple, objectid);

if (IsYugaByteEnabled())
YBCExecuteInsert(boot_reldesc, tupDesc, tuple, ONCONFLICT_NONE);
if (IsYugaByteEnabled()){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New line before the opening brace

@@ -626,8 +626,9 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)

if (IsYBRelation(myState->rel))
{
slot->tts_tupleDescriptor=RelationGetDescr(myState->rel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExecSetSlotDescriptor should be used.
However, it is a bad idea to modify passed in slot.
Why do we think that slot does not have a descriptor already?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't used ExecSetSlotDescriptor as this clears the tuple from the slot.
slot should have descriptor otherwise it won't work in slot_getattr as that retrieves tupleDesc from slot->tts_tupleDescriptor. But, I have done a check

RelationGetDescr(rel)!=slot->tts_tupleDescriptor -> this is true as it has different tdtypeid and tdrefcount. So, I tried to update it.

Based on your comments i have done a recheck and I hope we aren't using tdtypeid and tdrefcount anywhere.

@@ -485,8 +485,9 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
tuple = ExecMaterializeSlot(slot);
if (IsYBRelation(myState->transientrel))
{
slot->tts_tupleDescriptor=RelationGetDescr(myState->transientrel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@@ -5417,11 +5417,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
/* Write the tuple out to the new relation */
if (newrel)
{
if (IsYBRelation(newrel))
if (IsYBRelation(newrel)){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New line before brace

@@ -5417,11 +5417,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
/* Write the tuple out to the new relation */
if (newrel)
{
if (IsYBRelation(newrel))
if (IsYBRelation(newrel)){
newslot->tts_tupleDescriptor=RelationGetDescr(newrel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe tuple descriptor assignment again

@@ -1573,6 +1573,7 @@ ExecUpdate(ModifyTableState *mtstate,
ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
if (is_pk_updated)
{
planSlot->tts_tupleDescriptor=RelationGetDescr(resultRelationDesc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and again

HeapTuple tuple, OnConflictAction onConflictAction, Datum *ybctid,
YBCPgTransactionSetting transaction_setting) {
Oid relfileNodeId = YbGetRelfileNodeId(rel);
AttrNumber minattr = YBGetFirstLowInvalidAttributeNumber(rel);
int natts = RelationGetNumberOfAttributes(rel);
Bitmapset *pkey = YBGetTablePrimaryKeyBms(rel);

TupleDesc tupleDesc = slot->tts_tupleDescriptor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if slot->tts_tupleDescriptor is NULL you probably can use RelationGetDescr(rel)

@andrei-mart
Copy link
Contributor

yugabyte=# CREATE TABLE test (r1 int, r2 int, i int, PRIMARY KEY (r1 ASC, r2 DESC));
CREATE TABLE
yugabyte=# INSERT INTO test VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3);
INSERT 0 3
yugabyte=# INSERT INTO test  VALUES (3, 3, 31) ON CONFLICT (r1, r2) DO UPDATE SET r1 = EXCLUDED.i, r2 = EXCLUDED.i;
INSERT 0 1
yugabyte=# select * from test;
 r1 | r2 | i  
----+----+----
  1 |  1 |  1
  2 |  2 |  2
 31 | 31 | 31
(3 rows)

Expected result is

yugabyte=# select * from test;
 r1 | r2 | i  
----+----+---
  1 |  1 | 1
  2 |  2 | 2
 31 | 31 | 3
(3 rows)

There are more failures of ./yb_build.sh --java-test 'org.yb.pgsql.TestPgUpdatePrimaryKey'

@@ -1573,7 +1573,8 @@ ExecUpdate(ModifyTableState *mtstate,
ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
if (is_pk_updated)
{
YBCExecuteUpdateReplace(resultRelationDesc, planSlot, tuple, estate);
slot->tts_tuple->t_ybctid = YBCGetYBTupleIdFromSlot(planSlot);
YBCExecuteUpdateReplace(resultRelationDesc, slot, tuple, estate);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a TODO already here to pass a transformed slot. I guess slot is the right option instead of planSlot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants