Thread: Strange avg value size for index on expression in pg_stats

Strange avg value size for index on expression in pg_stats

From
Jehan-Guillaume de Rorthais
Date:
Hello,

I'm not sure if I should post here or on pgsql-hackers.

While investigating about a wrong result with my btree bloat estimation query,
I found a strange stat deviation between the average size of a value in its
table and its average size in one index on the "lower" expression.

Take the following scenario:

  postgres@test=# create table test as
  test-# select md5(t::text) AS t
  test-# from generate_series(1, 1000000) t;
  SELECT 1000000

  postgres@test=# create index ON test (lower(t));
  CREATE INDEX

  postgres@test=# analyze test;
  ANALYZE

  postgres@test=# select tablename, attname, avg_width
  test-# from pg_stats
  test-# where schemaname = 'public';
     tablename    | attname | avg_width
  ----------------+---------+-----------
   test           | t       |        33
   test_lower_idx | lower   |        36

Md5 values are always 32 bytes long. In the table, pg_stats reports 33 because
of the text header. In the index, the reported value is 36!

Looking at the page layout documentation and in the index using hexdump, I can
not find any answer about this 3 bytes. PFA the "hexdump -C" output from the
index. For each row, we clearly see a 8 bytes row header followed by a ONE byte
value header (43 'C'), the 32 bytes of the md5 and 7 bytes of padding (00).

A wild guess would be that ANALYZE is considering a text field from an
expression has always a 4 bytes header whatever its actual size (text field
header size is one if the value is < 127 bytes long, 4 in other situations).


This tiny difference is the source of a very bad estimation with the Btree bloat
estimation query when values are around an alignement boundary. As instance,
here is the use case that lead me to this:

       tablename      | attname | avg_width
  --------------------+---------+-----------
   customer           | email   |        23
   customer_lower_idx | lower   |        26

We have an index on email, and another one on lower(index). The first one is
aligned on 24, the second one on 32. Leading to bloat estimation of 17% for
the first one and -1% for the second one (the estimated index size is bigger
than the real one).

Any information about this from a hacker? Do anyone have an explanation about
this? Is it something that's worth posting on pgsql-hackers?


Regards,

Attachment

Re: Strange avg value size for index on expression in pg_stats

From
Jehan-Guillaume de Rorthais
Date:
On Mon, 10 Nov 2014 11:52:54 +0100
Jehan-Guillaume de Rorthais <ioguix@free.fr> wrote:

> Hello,
>
> I'm not sure if I should post here or on pgsql-hackers.
>
> While investigating about a wrong result with my btree bloat estimation query,
> I found a strange stat deviation between the average size of a value in its
> table and its average size in one index on the "lower" expression.

Oh, I forgot to add links to this query, for testing purpose:

  * https://gist.github.com/ioguix/c29d5790b8b93bf81c27
  * explanations, see http://blog.ioguix.net/tag/bloat/

Cheers,


Re: Strange avg value size for index on expression in pg_stats

From
Tom Lane
Date:
Jehan-Guillaume de Rorthais <ioguix@free.fr> writes:
> While investigating about a wrong result with my btree bloat estimation query,
> I found a strange stat deviation between the average size of a value in its
> table and its average size in one index on the "lower" expression.

ANALYZE doesn't look at the physical index at all.  For an expression
index, it will recompute the index expression at each sampled row, and
then take statistics on those values.  In the case you've got here, it's
re-running the lower() function and looking at the uncompressed result.
So that accounts for the discrepancy in average width stats.

> This tiny difference is the source of a very bad estimation with the
> Btree bloat estimation query when values are around an alignement
> boundary.

TBH, if that query is relying on ANALYZE width estimates to be accurate
to the last byte, its reliability is going to be horrid anyway.

            regards, tom lane


Re: Strange avg value size for index on expression in pg_stats

From
Jehan-Guillaume de Rorthais
Date:
On Mon, 10 Nov 2014 09:39:23 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Jehan-Guillaume de Rorthais <ioguix@free.fr> writes:
> > While investigating about a wrong result with my btree bloat estimation
> > query, I found a strange stat deviation between the average size of a value
> > in its table and its average size in one index on the "lower" expression.
>
> ANALYZE doesn't look at the physical index at all.  For an expression
> index, it will recompute the index expression at each sampled row, and
> then take statistics on those values.  In the case you've got here, it's
> re-running the lower() function and looking at the uncompressed result.
> So that accounts for the discrepancy in average width stats.

Ok, understood.

But in my demo scenario, I used *only* md5 to populate the test table. So
data length is always 32 and the average width length is exact. No possible
deviation, even with lower(). To be quite dense:

  postgres@test=# select
    length(lower(md5('a'))) = length(md5('b')),
    length(md5('c')) = length(md5('d'));
  -[ RECORD 1 ]
  ?column? | t
  ?column? | t

And here is another test with a static string for all rows:

  postgres@test=# create table test as
  select '1234567890123456789012'::text as t
  from generate_series(1,100);
  SELECT 100

  postgres@test=# create index ON test (lower(t));
  CREATE INDEX

  postgres@test=# analyze test;
  ANALYZE

  postgres@test=# select tablename, attname, avg_width
  from pg_stats
  where schemaname = 'public';
     tablename    | attname | avg_width
  ----------------+---------+-----------
   test           | t       |        23
   test_lower_idx | lower   |        26


AFAIU, we should not have a discrepancy here.

> > This tiny difference is the source of a very bad estimation with the
> > Btree bloat estimation query when values are around an alignement
> > boundary.
>
> TBH, if that query is relying on ANALYZE width estimates to be accurate
> to the last byte, its reliability is going to be horrid anyway.

Well, I'm aware of that. I don't need an accuracy to the last byte. This query
doesn't even pay attention to the data alignment padding anyway (I measured some
10% deviation in a case because of this).

This request only helps guessing the bloat evolution in Btrees or quickly
discover *big* deviations. In many situations, we can not afford a call to
pgstattuple.avg_leaf_density().

But this statistic difference between two values with the exact same size is
itching me. Sa far, I couldn't find a logical explanation and it just looks
like a wrong statistic.

Regards,
--
Jehan-Guillaume (ioguix) de Rorthais