diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 1575a81b01..d5b56013d3 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -106,8 +106,10 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, bool toast_isnull[MaxHeapAttributeNumber]; bool toast_oldisnull[MaxHeapAttributeNumber]; Datum toast_values[MaxHeapAttributeNumber]; + Datum toast_values_copy[MaxHeapAttributeNumber]; Datum toast_oldvalues[MaxHeapAttributeNumber]; ToastAttrInfo toast_attr[MaxHeapAttributeNumber]; + ToastAttrInfo toast_attr_copy[MaxHeapAttributeNumber]; ToastTupleContext ttc; /* @@ -176,6 +178,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, /* now convert to a limit on the tuple data size */ maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff; + memcpy(toast_attr_copy, toast_attr, sizeof(toast_attr)); + memcpy(toast_values_copy, toast_values, sizeof(toast_values)); + /* * Look for attributes with attstorage EXTENDED to compress. Also find * large attributes with attstorage EXTENDED or EXTERNAL, and store them @@ -214,7 +219,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, */ if (toast_attr[biggest_attno].tai_size > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) - toast_tuple_externalize(&ttc, biggest_attno, options); + toast_tuple_opt_externalize(&ttc, biggest_attno, options, + toast_values_copy[biggest_attno], + &toast_attr_copy[biggest_attno]); } /* @@ -231,7 +238,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, biggest_attno = toast_tuple_find_biggest_attribute(&ttc, false, false); if (biggest_attno < 0) break; - toast_tuple_externalize(&ttc, biggest_attno, options); + toast_tuple_opt_externalize(&ttc, biggest_attno, options, + toast_values_copy[biggest_attno], + &toast_attr_copy[biggest_attno]); } /* @@ -267,7 +276,9 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, if (biggest_attno < 0) break; - toast_tuple_externalize(&ttc, biggest_attno, options); + toast_tuple_opt_externalize(&ttc, biggest_attno, options, + toast_values_copy[biggest_attno], + &toast_attr_copy[biggest_attno]); } /* diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index 0cc5a30f9b..f1621bf90c 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/detoast.h" +#include "access/heaptoast.h" #include "access/table.h" #include "access/toast_helper.h" #include "access/toast_internals.h" @@ -235,8 +236,6 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) if (DatumGetPointer(new_value) != NULL) { /* successful compression */ - if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) - pfree(DatumGetPointer(*value)); *value = new_value; attr->tai_colflags |= TOASTCOL_NEEDS_FREE; attr->tai_size = VARSIZE(DatumGetPointer(*value)); @@ -268,6 +267,55 @@ toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } +/* + * Don't save compressed data to external storage unless it saves I/O while + * accessing the same data by reducing the number of chunks. + */ +void +toast_tuple_opt_externalize(ToastTupleContext *ttc, int attribute, int options, + Datum orig_toast_value, ToastAttrInfo *orig_attr) +{ + Datum *value = &ttc->ttc_values[attribute]; + ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; + + /* Sanity check: if data is not compressed then we can proceed as usual. */ + if (*value == orig_toast_value) + toast_tuple_externalize(ttc, attribute, options); + else + { + /* + * Here we decides whether to store compressed form or not by checking + * if number of chunks is reduced. However we made any exception where + * data size is small. + * Experiments show that we are not gaining much by storing uncompressed + * values where uncompressed form size < 2 * TOAST_MAX_CHUNK_SIZE, in + * some cases we end up using significantly more disk space by storing + * uncompressed data, to avoid this we are going with compressed data + * for such cases. + */ + if (orig_attr->tai_size < 2 * TOAST_MAX_CHUNK_SIZE || + (attr->tai_size / TOAST_MAX_CHUNK_SIZE < + orig_attr->tai_size / TOAST_MAX_CHUNK_SIZE)) + { + if ((orig_attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) + pfree(DatumGetPointer(orig_toast_value)); + toast_tuple_externalize(ttc, attribute, options); + } + else + { + /* Using the uncompressed data instead, deleting compressed data. */ + pfree(DatumGetPointer(*value)); + *value = orig_toast_value; + + /* incompressible, ignore on subsequent compression passes. */ + orig_attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; + *attr = *orig_attr; + + toast_tuple_externalize(ttc, attribute, options); + } + } +} + /* * Perform appropriate cleanup after one tuple has been subjected to TOAST. */ diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h index 1e2aaf3303..347562a280 100644 --- a/src/include/access/toast_helper.h +++ b/src/include/access/toast_helper.h @@ -108,6 +108,9 @@ extern int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, extern void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute); extern void toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options); +extern void toast_tuple_opt_externalize(ToastTupleContext *ttc, int attribute, + int options, Datum old_toast_value, + ToastAttrInfo *old_toast_attr); extern void toast_tuple_cleanup(ToastTupleContext *ttc); extern void toast_delete_external(Relation rel, Datum *values, bool *isnull,