From d62e54dc09ae003fece65255de5aaa376c39f34c Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 22 Jan 2021 15:31:03 -0800 Subject: [PATCH] Columnar: optimize write path. --- src/backend/columnar/cstore_tableam.c | 83 ++++++++++++------- .../columnar/columnar_version_compat.h | 4 + src/test/regress/expected/am_insert.out | 35 ++++++++ src/test/regress/sql/am_insert.sql | 31 +++++++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/src/backend/columnar/cstore_tableam.c b/src/backend/columnar/cstore_tableam.c index cd3883f61..43ce631ba 100644 --- a/src/backend/columnar/cstore_tableam.c +++ b/src/backend/columnar/cstore_tableam.c @@ -14,7 +14,7 @@ #include "access/tableam.h" #include "access/tsmapi.h" #if PG_VERSION_NUM >= 130000 -#include "access/heaptoast.h" +#include "access/detoast.h" #else #include "access/tuptoaster.h" #endif @@ -106,9 +106,10 @@ static void LogRelationStats(Relation rel, int elevel); static void TruncateColumnar(Relation rel, int elevel); static HeapTuple ColumnarSlotCopyHeapTuple(TupleTableSlot *slot); static void ColumnarCheckLogicalReplication(Relation rel); +static Datum * detoast_values(TupleDesc tupleDesc, Datum *orig_values, bool *isnull); /* Custom tuple slot ops used for columnar. Initialized in columnar_tableam_init(). */ -TupleTableSlotOps TTSOpsColumnar; +static TupleTableSlotOps TTSOpsColumnar; static List * RelationColumnList(Relation rel) @@ -435,24 +436,16 @@ columnar_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, TableWriteState *writeState = columnar_init_write_state(relation, RelationGetDescr(relation), GetCurrentSubTransactionId()); - MemoryContext oldContext = MemoryContextSwitchTo(writeState->perTupleContext); - HeapTuple heapTuple = ExecCopySlotHeapTuple(slot); - ColumnarCheckLogicalReplication(relation); - if (HeapTupleHasExternal(heapTuple)) - { - /* detoast any toasted attributes */ - HeapTuple newTuple = toast_flatten_tuple(heapTuple, - slot->tts_tupleDescriptor); - - ExecForceStoreHeapTuple(newTuple, slot, true); - } slot_getallattrs(slot); - ColumnarWriteRow(writeState, slot->tts_values, slot->tts_isnull); + Datum *values = detoast_values(slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + + ColumnarWriteRow(writeState, values, slot->tts_isnull); MemoryContextSwitchTo(oldContext); MemoryContextReset(writeState->perTupleContext); @@ -485,28 +478,23 @@ columnar_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, GetCurrentSubTransactionId()); ColumnarCheckLogicalReplication(relation); + + MemoryContext oldContext = MemoryContextSwitchTo(writeState->perTupleContext); + for (int i = 0; i < ntuples; i++) { TupleTableSlot *tupleSlot = slots[i]; - MemoryContext oldContext = MemoryContextSwitchTo(writeState->perTupleContext); - HeapTuple heapTuple = ExecCopySlotHeapTuple(tupleSlot); - - if (HeapTupleHasExternal(heapTuple)) - { - /* detoast any toasted attributes */ - HeapTuple newTuple = toast_flatten_tuple(heapTuple, - tupleSlot->tts_tupleDescriptor); - - ExecForceStoreHeapTuple(newTuple, tupleSlot, true); - } slot_getallattrs(tupleSlot); - ColumnarWriteRow(writeState, tupleSlot->tts_values, tupleSlot->tts_isnull); - MemoryContextSwitchTo(oldContext); + Datum *values = detoast_values(tupleSlot->tts_tupleDescriptor, + tupleSlot->tts_values, tupleSlot->tts_isnull); + + ColumnarWriteRow(writeState, values, tupleSlot->tts_isnull); + MemoryContextReset(writeState->perTupleContext); } - MemoryContextReset(writeState->perTupleContext); + MemoryContextSwitchTo(oldContext); } @@ -1400,6 +1388,45 @@ columnar_handler(PG_FUNCTION_ARGS) } +/* + * detoast_values + * + * Detoast and decompress all values. If there's no work to do, return + * original pointer; otherwise return a newly-allocated values array. Should + * be called in per-tuple context. + */ +static Datum * +detoast_values(TupleDesc tupleDesc, Datum *orig_values, bool *isnull) +{ + int natts = tupleDesc->natts; + + /* copy on write to optimize for case where nothing is toasted */ + Datum *values = orig_values; + + for (int i = 0; i < tupleDesc->natts; i++) + { + if (!isnull[i] && tupleDesc->attrs[i].attlen == -1 && + VARATT_IS_EXTENDED(values[i])) + { + /* make a copy */ + if (values == orig_values) + { + values = palloc(sizeof(Datum) * natts); + memcpy_s(values, sizeof(Datum) * natts, + orig_values, sizeof(Datum) * natts); + } + + /* will be freed when per-tuple context is reset */ + struct varlena *new_value = (struct varlena *) DatumGetPointer(values[i]); + new_value = detoast_attr(new_value); + values[i] = PointerGetDatum(new_value); + } + } + + return values; +} + + /* * ColumnarCheckLogicalReplication throws an error if the relation is * part of any publication. This should be called before any write to diff --git a/src/include/columnar/columnar_version_compat.h b/src/include/columnar/columnar_version_compat.h index 6409b149f..f6cce5e49 100644 --- a/src/include/columnar/columnar_version_compat.h +++ b/src/include/columnar/columnar_version_compat.h @@ -43,4 +43,8 @@ #define table_endscan heap_endscan #endif +#if PG_VERSION_NUM < 130000 +#define detoast_attr(X) heap_tuple_untoast_attr(X) +#endif + #endif /* COLUMNAR_COMPAT_H */ diff --git a/src/test/regress/expected/am_insert.out b/src/test/regress/expected/am_insert.out index 1ba1e0085..731ecacf7 100644 --- a/src/test/regress/expected/am_insert.out +++ b/src/test/regress/expected/am_insert.out @@ -96,3 +96,38 @@ DROP PUBLICATION test_columnar_publication; -- should succeed INSERT INTO test_logical_replication VALUES (3); DROP TABLE test_logical_replication; +-- +-- test toast interactions +-- +-- row table with data in different storage formats +CREATE TABLE test_toast_row(plain TEXT, main TEXT, external TEXT, extended TEXT); +ALTER TABLE test_toast_row ALTER COLUMN plain SET STORAGE plain; -- inline, uncompressed +ALTER TABLE test_toast_row ALTER COLUMN main SET STORAGE main; -- inline, compressed +ALTER TABLE test_toast_row ALTER COLUMN external SET STORAGE external; -- out-of-line, uncompressed +ALTER TABLE test_toast_row ALTER COLUMN extended SET STORAGE extended; -- out-of-line, compressed +INSERT INTO test_toast_row VALUES( + repeat('w', 5000), repeat('x', 5000), repeat('y', 5000), repeat('z', 5000)); +SELECT + pg_column_size(plain), pg_column_size(main), + pg_column_size(external), pg_column_size(extended) +FROM test_toast_row; + pg_column_size | pg_column_size | pg_column_size | pg_column_size +--------------------------------------------------------------------- + 5004 | 69 | 5000 | 65 +(1 row) + +CREATE TABLE test_toast_columnar(plain TEXT, main TEXT, external TEXT, extended TEXT) + USING columnar; +INSERT INTO test_toast_columnar SELECT plain, main, external, extended + FROM test_toast_row; +SELECT + pg_column_size(plain), pg_column_size(main), + pg_column_size(external), pg_column_size(extended) +FROM test_toast_columnar; + pg_column_size | pg_column_size | pg_column_size | pg_column_size +--------------------------------------------------------------------- + 5004 | 5004 | 5004 | 5004 +(1 row) + +DROP TABLE test_toast_row; +DROP TABLE test_toast_columnar; diff --git a/src/test/regress/sql/am_insert.sql b/src/test/regress/sql/am_insert.sql index b02fbde42..8dce707da 100644 --- a/src/test/regress/sql/am_insert.sql +++ b/src/test/regress/sql/am_insert.sql @@ -66,3 +66,34 @@ DROP PUBLICATION test_columnar_publication; -- should succeed INSERT INTO test_logical_replication VALUES (3); DROP TABLE test_logical_replication; + +-- +-- test toast interactions +-- + +-- row table with data in different storage formats +CREATE TABLE test_toast_row(plain TEXT, main TEXT, external TEXT, extended TEXT); +ALTER TABLE test_toast_row ALTER COLUMN plain SET STORAGE plain; -- inline, uncompressed +ALTER TABLE test_toast_row ALTER COLUMN main SET STORAGE main; -- inline, compressed +ALTER TABLE test_toast_row ALTER COLUMN external SET STORAGE external; -- out-of-line, uncompressed +ALTER TABLE test_toast_row ALTER COLUMN extended SET STORAGE extended; -- out-of-line, compressed + +INSERT INTO test_toast_row VALUES( + repeat('w', 5000), repeat('x', 5000), repeat('y', 5000), repeat('z', 5000)); + +SELECT + pg_column_size(plain), pg_column_size(main), + pg_column_size(external), pg_column_size(extended) +FROM test_toast_row; + +CREATE TABLE test_toast_columnar(plain TEXT, main TEXT, external TEXT, extended TEXT) + USING columnar; +INSERT INTO test_toast_columnar SELECT plain, main, external, extended + FROM test_toast_row; +SELECT + pg_column_size(plain), pg_column_size(main), + pg_column_size(external), pg_column_size(extended) +FROM test_toast_columnar; + +DROP TABLE test_toast_row; +DROP TABLE test_toast_columnar;