mirror of
https://github.com/postgres/postgres.git
synced 2025-07-20 05:03:10 +03:00
This patch addresses some issues in TOAST compression strategy that
were discussed last year, but we felt it was too late in the 8.3 cycle to change the code immediately. Specifically, the patch: * Reduces the minimum datum size to be considered for compression from 256 to 32 bytes, as suggested by Greg Stark. * Increases the required compression rate for compressed storage from 20% to 25%, again per Greg's suggestion. * Replaces force_input_size (size above which compression is forced) with a maximum size to be considered for compression. It was agreed that allowing large inputs to escape the minimum-compression-rate requirement was not bright, and that indeed we'd rather have a knob that acted in the other direction. I set this value to 1MB for the moment, but it could use some performance studies to tune it. * Adds an early-failure path to the compressor as suggested by Jan: if it's been unable to find even one compressible substring in the first 1KB (parameterizable), assume we're looking at incompressible input and give up. (Possibly this logic can be improved, but I'll commit it as-is for now.) * Improves the toasting heuristics so that when we have very large fields with attstorage 'x' or 'e', we will push those out to toast storage before considering inline compression of shorter fields. This also responds to a suggestion of Greg's, though my original proposal for a solution was a bit off base because it didn't fix the problem for large 'e' fields. There was some discussion in the earlier threads of exposing some of the compression knobs to users, perhaps even on a per-column basis. I have not done anything about that here. It seems to me that if we are changing around the parameters, we'd better get some experience and be sure we are happy with the design before we set things in stone by providing user-visible knobs.
This commit is contained in:
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.83 2008/02/29 17:47:41 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.84 2008/03/07 23:20:21 tgl Exp $
|
||||
*
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
@ -576,7 +576,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
/* ----------
|
||||
* Compress and/or save external until data fits into target length
|
||||
*
|
||||
* 1: Inline compress attributes with attstorage 'x'
|
||||
* 1: Inline compress attributes with attstorage 'x', and store very
|
||||
* large attributes with attstorage 'x' or 'e' external immediately
|
||||
* 2: Store attributes with attstorage 'x' or 'e' external
|
||||
* 3: Inline compress attributes with attstorage 'm'
|
||||
* 4: Store attributes with attstorage 'm' external
|
||||
@ -595,7 +596,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
maxDataLen = TOAST_TUPLE_TARGET - hoff;
|
||||
|
||||
/*
|
||||
* Look for attributes with attstorage 'x' to compress
|
||||
* Look for attributes with attstorage 'x' to compress. Also find large
|
||||
* attributes with attstorage 'x' or 'e', and store them external.
|
||||
*/
|
||||
while (heap_compute_data_size(tupleDesc,
|
||||
toast_values, toast_isnull) > maxDataLen)
|
||||
@ -606,7 +608,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
Datum new_value;
|
||||
|
||||
/*
|
||||
* Search for the biggest yet uncompressed internal attribute
|
||||
* Search for the biggest yet unprocessed internal attribute
|
||||
*/
|
||||
for (i = 0; i < numAttrs; i++)
|
||||
{
|
||||
@ -616,7 +618,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
continue; /* can't happen, toast_action would be 'p' */
|
||||
if (VARATT_IS_COMPRESSED(toast_values[i]))
|
||||
continue;
|
||||
if (att[i]->attstorage != 'x')
|
||||
if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
|
||||
continue;
|
||||
if (toast_sizes[i] > biggest_size)
|
||||
{
|
||||
@ -629,30 +631,58 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
break;
|
||||
|
||||
/*
|
||||
* Attempt to compress it inline
|
||||
* Attempt to compress it inline, if it has attstorage 'x'
|
||||
*/
|
||||
i = biggest_attno;
|
||||
old_value = toast_values[i];
|
||||
new_value = toast_compress_datum(old_value);
|
||||
|
||||
if (DatumGetPointer(new_value) != NULL)
|
||||
if (att[i]->attstorage == 'x')
|
||||
{
|
||||
/* successful compression */
|
||||
if (toast_free[i])
|
||||
pfree(DatumGetPointer(old_value));
|
||||
toast_values[i] = new_value;
|
||||
toast_free[i] = true;
|
||||
toast_sizes[i] = VARSIZE(toast_values[i]);
|
||||
need_change = true;
|
||||
need_free = true;
|
||||
old_value = toast_values[i];
|
||||
new_value = toast_compress_datum(old_value);
|
||||
|
||||
if (DatumGetPointer(new_value) != NULL)
|
||||
{
|
||||
/* successful compression */
|
||||
if (toast_free[i])
|
||||
pfree(DatumGetPointer(old_value));
|
||||
toast_values[i] = new_value;
|
||||
toast_free[i] = true;
|
||||
toast_sizes[i] = VARSIZE(toast_values[i]);
|
||||
need_change = true;
|
||||
need_free = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* incompressible, ignore on subsequent compression passes */
|
||||
toast_action[i] = 'x';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* incompressible data, ignore on subsequent compression passes
|
||||
*/
|
||||
/* has attstorage 'e', ignore on subsequent compression passes */
|
||||
toast_action[i] = 'x';
|
||||
}
|
||||
|
||||
/*
|
||||
* If this value is by itself more than maxDataLen (after compression
|
||||
* if any), push it out to the toast table immediately, if possible.
|
||||
* This avoids uselessly compressing other fields in the common case
|
||||
* where we have one long field and several short ones.
|
||||
*
|
||||
* XXX maybe the threshold should be less than maxDataLen?
|
||||
*/
|
||||
if (toast_sizes[i] > maxDataLen &&
|
||||
rel->rd_rel->reltoastrelid != InvalidOid)
|
||||
{
|
||||
old_value = toast_values[i];
|
||||
toast_action[i] = 'p';
|
||||
toast_values[i] = toast_save_datum(rel, toast_values[i],
|
||||
use_wal, use_fsm);
|
||||
if (toast_free[i])
|
||||
pfree(DatumGetPointer(old_value));
|
||||
toast_free[i] = true;
|
||||
need_change = true;
|
||||
need_free = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -761,9 +791,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* incompressible data, ignore on subsequent compression passes
|
||||
*/
|
||||
/* incompressible, ignore on subsequent compression passes */
|
||||
toast_action[i] = 'x';
|
||||
}
|
||||
}
|
||||
@ -1047,16 +1075,28 @@ toast_compress_datum(Datum value)
|
||||
Assert(!VARATT_IS_COMPRESSED(value));
|
||||
|
||||
/*
|
||||
* No point in wasting a palloc cycle if value is too short for
|
||||
* compression
|
||||
* No point in wasting a palloc cycle if value size is out of the
|
||||
* allowed range for compression
|
||||
*/
|
||||
if (valsize < PGLZ_strategy_default->min_input_size)
|
||||
if (valsize < PGLZ_strategy_default->min_input_size ||
|
||||
valsize > PGLZ_strategy_default->max_input_size)
|
||||
return PointerGetDatum(NULL);
|
||||
|
||||
tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize));
|
||||
|
||||
/*
|
||||
* We recheck the actual size even if pglz_compress() reports success,
|
||||
* because it might be satisfied with having saved as little as one byte
|
||||
* in the compressed data --- which could turn into a net loss once you
|
||||
* consider header and alignment padding. Worst case, the compressed
|
||||
* format might require three padding bytes (plus header, which is included
|
||||
* in VARSIZE(tmp)), whereas the uncompressed format would take only one
|
||||
* header byte and no padding if the value is short enough. So we insist
|
||||
* on a savings of more than 2 bytes to ensure we have a gain.
|
||||
*/
|
||||
if (pglz_compress(VARDATA_ANY(value), valsize,
|
||||
(PGLZ_Header *) tmp, PGLZ_strategy_default) &&
|
||||
VARSIZE(tmp) < VARSIZE_ANY(value))
|
||||
VARSIZE(tmp) < valsize - 2)
|
||||
{
|
||||
/* successful compression */
|
||||
return PointerGetDatum(tmp);
|
||||
|
Reference in New Issue
Block a user