Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wHH7w-0071Gm-2Y for pgsql-hackers@arkaria.postgresql.org; Mon, 27 Apr 2026 08:15:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wHH7w-00CwTw-0P for pgsql-hackers@arkaria.postgresql.org; Mon, 27 Apr 2026 08:15:36 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wHH7Y-00CsaA-14 for pgsql-hackers@lists.postgresql.org; Mon, 27 Apr 2026 08:15:12 +0000 Received: from mail-lf1-x130.google.com ([2a00:1450:4864:20::130]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wHH7Q-00000003Ir0-3vEq for pgsql-hackers@lists.postgresql.org; Mon, 27 Apr 2026 08:15:07 +0000 Received: by mail-lf1-x130.google.com with SMTP id 2adb3069b0e04-5a40b2d26a1so7520717e87.0 for ; Mon, 27 Apr 2026 01:15:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1777277702; cv=none; d=google.com; s=arc-20240605; b=IQHn1Ys6H4oc9hvfN6pnehjrCEA5pEvCkbZlqPVwp7g5l7mBsjQEEuzMQx6OcL8cZR 9vqdIUMMr85yoCbaK0Ip/9k41sgKsi5GcuYdJr6UFj0gxz+lVuLkZkB5KbTNlWHYw/6z dn6NO/dSxXtxO+ZgqyUMpFeAh2ZZrMDVUsBMb8UejaVQSdTnVspJBE6jNziKclbuT1Vq 26VhC2mcGpjOfr2qqBeMod/HMHz9lSK6jzBZMxd0H113QwUDkTR6UPogU70fICWBOgdV iCQjelzrz78wbzNmOMXK5sxMI9jEqBFSeF7mttHU7GCc8gnCfmtKFPGZSeFoEACNqfP0 XWIw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=m66sdQ2grkplu8xfQwBO7bqIpIT1aWjvtzZNDuMCQ7k=; fh=J2mwhZ32xgxlKmZccZ3CvU2o81Prn4uNvPK0+looEfA=; b=P+mX+4OzIFe6TzB5wbEGwXpWfk4J3XbMqy/zJ3uhQWpFNIArBBHx27Jpe2OFx5Q0Mx TAFw74bdifXc2MjOQLyTxuezMmfasktU7DIHGo6KqJZJEPQccTVpJq6rqII95p5VoRT/ 5twdyCcLDHZQ8Jd5cEpknmEovqBRNDWCadGQN+13NNGUQ6sVeTN/lIadRCaE/Uvgqj8T +T9UDpKSFWTWSi/7rX31Yir+XikcZi9K7wOZV6skHfVgsIO2wzIrG69pGFoPOETK5M04 rgOmtDVmwU+8MqpLPwCIeBuJ/O+W2Km7F1NIK+f2GfQi81TA5Mi+z/5vRG7/fiQkttMa YlBw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb.com; s=google; t=1777277702; x=1777882502; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=m66sdQ2grkplu8xfQwBO7bqIpIT1aWjvtzZNDuMCQ7k=; b=Xpo9gAPSxOcnV3zzb7IUdvFwOkqZFIEO2HS52vfm+IGQqJ7gHiSCV9BtZqL+MOY/C8 ZHHIooXSei+pZinSG6WujCd6tuRSz76sEc73YAJK68NnY13EK2/UpHw87vlgPVVXZmak lapUDivOjrjOoysWJdQi8DmMztxX5lrXO0JMAf5TJUKx9k5FMS5YP+P+DmZypEz6ydyt 2UPhEatn+5Tjp+psbqrNV3KOoHdz5OcoeXPNGJtthbMJ5jVI9L+0qpfqfJeH97uHXOQM kMBkALOZPlNYdIyz96KeBMjoIJazB928YkZsAv+D7IpqLAEXLzfBZEpDhO1IUnCKfaJn wI7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777277702; x=1777882502; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=m66sdQ2grkplu8xfQwBO7bqIpIT1aWjvtzZNDuMCQ7k=; b=XG2/iKWYxDWnrNT4QQSnM4MaSko/h/HgkwdTfO4wg1VmlpZfK8s06edFs+ALsgLLbT K5kCzCM32xr8XA3YK4FNVV536O42nLgZo2fpJkpm+d1Dzgii9OyAGiRP8REcK8MIwV4w G/P0t+m43hWnNmPz05HiDZ/1Bk5aPCnII818Tu+0n1eFSARFE3e8//3fbLaBUUs6E+3o s1TOf6e3l25lNSFgewmdYPmns+bscIaOfYowPueC2FSjtJ2J3fWsQQdWnn42n+2hc/52 LthoUD++bPxUbzt3sMOVOyRhGCeaJXREU0m//otNMEyf0fmwFXgt+b03+Z4w72muv0td JtVQ== X-Gm-Message-State: AOJu0Yy7ktoqSbPlu6EPFetAcLOOp0QC3vxrBQaCv49jW1RSf8D9HUSI Nl6dGH4TMlh83UJOltCwIAjM5GD1rFenctEI/puiwWaJxKZ3bdg/sIHc3V7pL3Xw1okRSKfVhHg SocGsHkK2t/Oasru7tNM+2Qo+old7Y8OE46sZDbSA2ln0hUtYzTv+VL/n X-Gm-Gg: AeBDievgEEvi3XmRGxJs/KooCztPE4Fd5/Qoc2O6HxjZpPtfESbUm1DRkETpb/8bZ+V xG4IeYy8lhF/06AJWCRqp/VGTktrqRvwHCcR8jToOdALHJBCRvCm32DffGGsyevubcE9CBqHFlx nSDZAGZtBB0BGTekGAR0g0AvTKd6Ewl4lW3nAKnceSvTsKWZf7dSD97bYPCd+vhXly2aj0zKE2N /iIjOVUXgGyr3lUZuE3BS/1uFkumcfrE8YPZceylJBqJsyUhSR2LSqhQM1cPxnoriyW3opjVcuG oSG1LbTi8MPhO4Tww4CdfjjNuWNgUSFQMGIVxv+NEnV8F9p+if/sWtjr31kNRPvF/M/Yz0YZ++G LH5I= X-Received: by 2002:a05:6512:3b28:b0:5a4:b18:427c with SMTP id 2adb3069b0e04-5a4172c0f49mr12772711e87.9.1777277701887; Mon, 27 Apr 2026 01:15:01 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Jakub Wartak Date: Mon, 27 Apr 2026 10:14:50 +0200 X-Gm-Features: AVHnY4ILBRUAOGNNXKVUBWlH0vXKc5rzlgK9LgYkh4VUPnRQrShGa0a9ZsA88fw Message-ID: Subject: Re: question on visibility map To: Fabrice Chapuis Cc: PostgreSQL Hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Fri, Apr 24, 2026 at 10:13=E2=80=AFAM Fabrice Chapuis wrote: > > Hi, > > In this test I found that not all pages are not marked as all_visible aft= er vacuuming, any explanation? > > CREATE TABLE test ( > id SERIAL PRIMARY KEY, > name TEXT, > created_at TIMESTAMP DEFAULT now() > ); > > INSERT INTO test (name) > SELECT > 'name_' || g > FROM generate_series(1, 100000) AS g; > CREATE TABLE > INSERT 0 100000 > cpr [2720498]=3D# > cpr [2720498]=3D# > cpr [2720498]=3D# table test limit 10; > +----+---------+----------------------------+ > | id | name | created_at | > +----+---------+----------------------------+ > | 1 | name_1 | 2026-04-24 09:35:46.561014 | > | 2 | name_2 | 2026-04-24 09:35:46.561014 | > | 3 | name_3 | 2026-04-24 09:35:46.561014 | > | 4 | name_4 | 2026-04-24 09:35:46.561014 | > | 5 | name_5 | 2026-04-24 09:35:46.561014 | > | 6 | name_6 | 2026-04-24 09:35:46.561014 | > | 7 | name_7 | 2026-04-24 09:35:46.561014 | > | 8 | name_8 | 2026-04-24 09:35:46.561014 | > | 9 | name_9 | 2026-04-24 09:35:46.561014 | > | 10 | name_10 | 2026-04-24 09:35:46.561014 | > +----+---------+----------------------------+ > (10 rows) > > # ALTER TABLE test SET (autovacuum_enabled =3D false); > ALTER TABLE > # SELECT reloptions > FROM pg_class > WHERE relname =3D 'test'; > +----------------------------+ > | reloptions | > +----------------------------+ > | {autovacuum_enabled=3Dfalse} | > +----------------------------+ > (1 row) > > # update test set name =3D 'name_x' where id =3D 1; > UPDATE 1 > > # create extension pg_visibility; > CREATE EXTENSION > # SELECT > c.relpages AS total_pages, > s.all_visible, > s.all_frozen, > round(100.0 * s.all_visible / NULLIF(c.relpages, 0), 1) AS pct_visibl= e, > round(100.0 * s.all_frozen / NULLIF(c.relpages, 0), 1) AS pct_frozen > FROM pg_class c > CROSS JOIN LATERAL pg_visibility_map_summary(c.oid) s > WHERE c.relname =3D 'test'; > +-------------+-------------+------------+-------------+------------+ > | total_pages | all_visible | all_frozen | pct_visible | pct_frozen | > +-------------+-------------+------------+-------------+------------+ > | 637 | 635 | 0 | 99.7 | 0.0 | > +-------------+-------------+------------+-------------+------------+ > (1 row) > > vacuum test; > VACUUM > # SELECT > c.relpages AS total_pages, > s.all_visible, > s.all_frozen, > round(100.0 * s.all_visible / NULLIF(c.relpages, 0), 1) AS pct_visibl= e, > round(100.0 * s.all_frozen / NULLIF(c.relpages, 0), 1) AS pct_frozen > FROM pg_class c > CROSS JOIN LATERAL pg_visibility_map_summary(c.oid) s > WHERE c.relname =3D 'test'; > +-------------+-------------+------------+-------------+------------+ > | total_pages | all_visible | all_frozen | pct_visible | pct_frozen | > +-------------+-------------+------------+-------------+------------+ > | 637 | 636 | 0 | 99.8 | 0.0 | > +-------------+-------------+------------+-------------+------------+ > (1 row) > Hi Fabrice, with both recent 12 and 13.23 I'm was getting as the result: total_pages | all_visible | all_frozen | pct_visible | pct_frozen -------------+-------------+------------+-------------+------------ 637 | 637 | 0 | 100.0 | 0.0 but starting with 14+, I've started getting those 1-2 not visible blocks too at the begging: total_pages | all_visible | all_frozen | pct_visible | pct_frozen -------------+-------------+------------+-------------+------------ 637 | 636 | 636 | 99.8 | 99.8 and with select * from pg_visibility_map('test'::regclass) where blkno <=3D 2 and all_visible=3D'f'; blkno | all_visible | all_frozen -------+-------------+------------ 0 | f | f so it is always block #0 where Your update landed. After VACUUM FULL + VACUUM however it back to 100% again. Now, re-trying Your's test case (to get it again to 636/637) to inspect it further we apparently have this situation on 14..master: CREATE EXTENSION pageinspect; SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_ctid, t_infomask, t_infomask2 FROM heap_page_items(get_raw_page('test', 0)) lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_ctid | t_infomask | t_infomask2 -----+--------+----------+--------+--------+--------+---------+------------= +------------- 1 | 0 | 3 | 0 | | | | | 2 | 8144 | 1 | 48 | 753 | 0 | (0,2) | 2306 | 3 3 | 8096 | 1 | 48 | 753 | 0 | (0,3) | 2306 | 3 4 | 8048 | 1 | 48 | 753 | 0 | (0,4) | 2306 | 3 5 | 8000 | 1 | 48 | 753 | 0 | (0,5) | 2306 | 3 6 | 7952 | 1 | 48 | 753 | 0 | (0,6) | 2306 | 3 Note lp_flags=3D=3D3, so with 14+ we started zero-ing out the block and putting it at the end of relation when looking just at the result of the testcase: SELECT blkno, count(*) AS item_count FROM generate_series(0, (SELECT pg_relation_size('test') / 8192) - 1) AS blkno, LATERAL heap_page_items(get_raw_page('test', blkno::int)) GROUP BY 1 ORDER BY 1; [..] 634 | 157 635 | 157 636 | 149 // this has 148 -> 149 LPs now, due to that single UPDA= TE appending it there So with blkno=3D636 being larger by one LP, you can new row @ 149 LP: lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_ctid | t_infomask | t_infomask2 -----+--------+----------+--------+--------+--------+-----------+----------= --+------------- [..] 146 | 1184 | 1 | 48 | 799 | 0 | (636,146) | 2306 | 3 147 | 1136 | 1 | 48 | 799 | 0 | (636,147) | 2306 | 3 148 | 1088 | 1 | 48 | 799 | 0 | (636,148) | 2306 | 3 149 | 1040 | 1 | 48 | 801 | 0 | (636,149) | 10498 | 3 So on 14+, I think UPDATE just zeroed out blkno#0 lp_1 (and later it had se= t lp_flags =3D=3D 3 =3D=3D LP_DEAD (!) flag was set there by VACUUM), and tha= t UPDATE put new record at the end of table, but VACUUM somewhat is not able to remo= ve that LP_DEAD unless indexes are guranteed to be cleaned up from stale entri= es. If you take a look on 14+ then can force it do so `VACUUM (INDEX_CLEANUP true) test;` and it will have 100% pct_visible set afterwards. See more for default toggle 'INDEX_CLEANUP AUTO' which says `the default is AUTO, which allows VACUUM to skip index vacuuming when appropriate` and this was introduced in 14.0 as part of`Allow vacuum to skip index vacuuming when the number of removable index entries is insignificant`. So the LP_DEAD is just sign that VACUUM in Your's testcase did not clean indexes (unless forced to do so). That feature references two commits [0][1] and first one even says in the commitmsg: "We only skip index vacuuming when 2% or less of the table's pages have one or more LP_DEAD items -- bypassing index vacuuming as an optimization must not noticeably impede setting bits in the visibility map." Hope that helps. -J. [0] - https://git.postgresql.org/gitweb/?p=3Dpostgresql.git;a=3Dcommitdiff;= h=3D5100010ee [1] - https://git.postgresql.org/gitweb/?p=3Dpostgresql.git;a=3Dcommitdiff;= h=3D3499df0de