Columnar: optimize write path.

pull/4566/head
Jeff Davis 2021-01-22 15:31:03 -08:00 committed by Hadi Moshayedi
parent 350e0c1d61
commit d62e54dc09
4 changed files with 125 additions and 28 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

@ -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;