diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 1575a81b01..7adf16131a 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,15 @@ 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; + /* + * Preserve references to the original uncompressed data before attempting + * the compression. So that during externalize if we decide not to store + * the compressed data then we have the original data with us. For more + * details refer to comments atop toast_tuple_opt_externalize. + */ + 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 +225,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_externalize_wrapper(&ttc, biggest_attno, options, + toast_values_copy[biggest_attno], + &toast_attr_copy[biggest_attno]); } /* @@ -231,7 +244,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_externalize_wrapper(&ttc, biggest_attno, options, + toast_values_copy[biggest_attno], + &toast_attr_copy[biggest_attno]); } /* @@ -267,7 +282,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_externalize_wrapper(&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..02ec0f2bc4 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)); @@ -252,7 +251,7 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) /* * Move an attribute to external storage. */ -void +static void toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) { Datum *value = &ttc->ttc_values[attribute]; @@ -268,6 +267,62 @@ toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } +/* + * Wrapper function for the external TOAST storage. + * + * TOAST the compressed value only if we are saving at least 1 chunk + * (2KB default) of disk storage else use the uncompressed one. In this way, we + * will save decompression costs everytime fetching that data without losing + * much on storage. + */ +void +toast_tuple_externalize_wrapper(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]; + + /* + * We are applying this optimization only if the data is compressed + * recently, In the case, data is uncompressed or data was already + * compressed even before TOAST-ing 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 an exception when + * 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, release memory for the + * compressed data. + */ + pfree(DatumGetPointer(*value)); + *value = orig_toast_value; + *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..2cce8d17b8 100644 --- a/src/include/access/toast_helper.h +++ b/src/include/access/toast_helper.h @@ -106,8 +106,10 @@ extern int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, bool for_compression, bool check_main); 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_externalize_wrapper(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,