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.94.2) (envelope-from ) id 1ut0Vs-00EOsi-Rq for pgsql-performance@arkaria.postgresql.org; Mon, 01 Sep 2025 09:07:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ut0Vq-005EGD-8z for pgsql-performance@arkaria.postgresql.org; Mon, 01 Sep 2025 09:07:42 +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.94.2) (envelope-from ) id 1ut0Vp-005EFu-UQ for pgsql-performance@lists.postgresql.org; Mon, 01 Sep 2025 09:07:42 +0000 Received: from mail-ej1-x633.google.com ([2a00:1450:4864:20::633]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1ut0Vo-002xwW-0w for pgsql-performance@lists.postgresql.org; Mon, 01 Sep 2025 09:07:42 +0000 Received: by mail-ej1-x633.google.com with SMTP id a640c23a62f3a-afee6037847so624327866b.1 for ; Mon, 01 Sep 2025 02:07:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756717656; x=1757322456; darn=lists.postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=SU0StealhOPEVkKtDyIJgjjNpFmgqkD+DNFrnbNSv0s=; b=Di66/JlseGjPGJnhoCZHfh99TKpADke3lKtain7euHR+5MtzzC1Q6/57EpUv2AvHrv UZVf8tbU7yaRDFmNjVkEKHpBJqoKDmqKG/IGyj3zeVc5zims7eEDh8IHO/6CQAzVeTEb ZJdnUwvLBYrDG7ke7O9plG82koodTwfJpGwoFoKIi7fhgXOQqq6TW33TQ8oNBAxytgDI f29GASzhmYFfB0RV20puNcjVVvVB8on/h1gl93dtt0MWuILWvxEYDOrghAAeHmwQJ82j a5fEafWULRU7pgGH4YN7honTEvwbVjGQE5XUQUHMQPvizFXrluyw3ZBnBUxsLoU3fkz6 NM8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756717656; x=1757322456; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=SU0StealhOPEVkKtDyIJgjjNpFmgqkD+DNFrnbNSv0s=; b=Rse5UAonvmIpcaKFMHtbSMOTc7QeqqCPMVImajMtf3u0BCC02NgbCCKCxCw4NegQx9 /9/8rGzzb6KkSu2/1oNICogHaf9WBEaYUAfh41w3HIHJvFu1GwTwahXI72R65XVnUPV+ h+RZPYEAlNXAY0AXSsdfF6z6zaujD42l3DsRrLnvDdPBRl41sfl46/FzYnSUDM22wPUq 00q2/eVUhey+tyI+tTCtphYeBFg7qIz9mJEoN5WZvwD7DzsVJy5cFCRlm4ERUK1Kcdz/ QsBEkswHvWsReVEhR94CNKdrG0ZAb4k0R0YTVhRsmb0QPL+ccVDOd5Hr8knd+PhSHVy/ CFDQ== X-Gm-Message-State: AOJu0YxI4ba6szZrj7haV07igIJwuqoEfY/4VDw30qiSCisfHbd2Qc0d 8XAd54e3ITAjQ+p6fcxgQWCubaOGGZNt8pE/zuo+fItzKEZV88va3e6gAWlRDDhlEV+51l6VQD0 Hrr4Iao14I01AtfC1nMwXNW4x655YySy7HVBg X-Gm-Gg: ASbGnctWJgOurC//znSTq62Z1gM7+YBRo/BEhn4v14FKQfgi1u0FjF25CcHREiYlt+2 Tpg/tFq8DXpuubFzE7+RbtUQZ1VNBijxxJk021A2WO719fCZ1oLykaesBd05dQw7H+aD3wzHz3B lxRbrDLH1xMmpHK/92V/RwqcdTnAumpmSuP6jsH95tCaG3ieXZkx7bsYYXnbHLz2QMPrs+kyQAD j5kTKhAvhN6wDC7EUQ= X-Google-Smtp-Source: AGHT+IFoNH/iUtq6P5HjWFHbPZ8Uli+19MaVlgzyr3Gny744tlWsaLPw6lAjM7c9/dRKF6UI5Yb5LCRVyBgzkkLIZf0= X-Received: by 2002:a17:906:730d:b0:aff:b99:e279 with SMTP id a640c23a62f3a-b01d9730790mr672826066b.32.1756717656280; Mon, 01 Sep 2025 02:07:36 -0700 (PDT) MIME-Version: 1.0 From: =?UTF-8?B?0JDQu9C10LrRgdC10Lkg0JHQvtGA0YnRkdCy?= Date: Mon, 1 Sep 2025 12:07:24 +0300 X-Gm-Features: Ac12FXwtYeeg2JYi-dvTqtuUQqPukfqTrG5tuXaB8hb-FBIZJ_4KABsF2eAJtkM Message-ID: Subject: Planner makes sub-optimal execution plan To: pgsql-performance@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, all! The execution plan was broken during upgrade PG13 to PG14 We've managed to emulate table and reproduce on PG17: # cat /etc/*release PRETTY_NAME="Debian GNU/Linux trixie/sid" select version(); PostgreSQL 17.4 (Debian 17.4-1.pgdg110+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit -- DROP TABLE IF EXISTS docum CASCADE; CREATE TABLE docum ( id BIGSERIAL PRIMARY KEY, dt TIMESTAMP, dt_real TIMESTAMP NOT NULL ); -- Fill table with 12M data rows (approx. 30 sec): DO $$ DECLARE batch_size INT := 50000; total_rows INT := 12000000; -- 12 M records current_row INT := 0; base_date TIMESTAMP := '2024-01-01 00:00:00'::timestamp; cutoff_date TIMESTAMP := '2025-08-14 09:44:09.033592'::timestamp; time_increment INTERVAL; BEGIN RAISE NOTICE 'Start. Total rows %', total_rows; ALTER TABLE docum SET (autovacuum_enabled = false); -- Calculate time increment for even distribution -- Data for ~590 days before cutoff_date time_increment := (cutoff_date - base_date) / (total_rows * 0.995); -- Insert sorted data for high correlation WHILE current_row < total_rows LOOP INSERT INTO docum (dt, dt_real) SELECT -- dt: almost in sync with dt_real bu with some noise and 5% NULL CASE WHEN random() < 0.05 THEN NULL ELSE base_date + (time_increment * (current_row + i)) + (random() * INTERVAL '1 hour' - INTERVAL '30 minutes') -- noise END, -- dt_real: monotiniacly increasing, correlation 0.999 CASE -- 99.5% records before cutoff_date WHEN current_row < total_rows * 0.995 THEN base_date + (time_increment * (current_row + i)) -- 0.5% records after cutoff_date (~60K records) ELSE cutoff_date + ((current_row + i - total_rows * 0.995) * INTERVAL '10 seconds') END FROM generate_series(0, LEAST(batch_size - 1, total_rows - current_row - 1)) AS i; current_row := current_row + batch_size; -- Show progress: IF current_row % 500000 = 0 THEN RAISE NOTICE 'Processed: % rows (%.1f%%)', current_row, (current_row::float / total_rows * 100); END IF; END LOOP; RAISE NOTICE 'Completed. Total rows inserted: %', current_row; END $$; -- Indexes: CREATE INDEX docum_dt ON docum(dt); CREATE INDEX docum_dt_real ON docum(dt_real); ALTER TABLE docum SET (autovacuum_enabled = true); -- Gather statistics: ALTER TABLE docum ALTER COLUMN dt SET STATISTICS 400; ALTER TABLE docum ALTER COLUMN dt_real SET STATISTICS 400; -- default_statistics_target = 400 will also work. VACUUM ANALYZE docum; -- The problematic query: EXPLAIN (ANALYZE, VERBOSE, BUFFERS, SUMMARY, SETTINGS, TIMING) SELECT MIN(docum.dt) AS "dt__min", MAX(docum.dt_real) AS "dt_real__max" FROM docum WHERE docum.dt_real >= '2025-08-14T09:44:09.033592'::timestamp; -- The plan I've got is: Result (cost=8.38..8.39 rows=1 width=16) (actual time=2660.034..2660.036 rows=1 loops=1) Output: (InitPlan 1).col1, (InitPlan 2).col1 Buffers: shared hit=9358751 read=30994 written=1 InitPlan 1 -> Limit (cost=0.43..7.91 rows=1 width=8) (actual time=2660.006..2660.007 rows=1 loops=1) Output: docum.dt Buffers: shared hit=9358747 read=30994 written=1 -> Index Scan using docum_dt_7ee1d676 on public.docum (cost=0.43..420487.43 rows=56222 width=8) (actual time=2660.004..2660.005 rows=1 loops=1) Output: docum.dt Index Cond: (docum.dt IS NOT NULL) Filter: (docum.dt_real >= '2025-08-14 09:44:09.033592'::timestamp without time zone) Rows Removed by Filter: 11342966 Buffers: shared hit=9358747 read=30994 written=1 InitPlan 2 -> Limit (cost=0.43..0.46 rows=1 width=8) (actual time=0.022..0.022 rows=1 loops=1) Output: docum_1.dt_real Buffers: shared hit=4 -> Index Only Scan Backward using docum_dt_real_2b81c58c on public.docum docum_1 (cost=0.43..1689.22 rows=59245 width=8) (actual time=0.021..0.021 rows=1 loops=1) Output: docum_1.dt_real Index Cond: (docum_1.dt_real >= '2025-08-14 09:44:09.033592'::timestamp without time zone) Heap Fetches: 0 Buffers: shared hit=4 Settings: work_mem = '16MB', search_path = 'public, public, "$user"' Planning: Buffers: shared hit=12 Planning Time: 0.148 ms Execution Time: 2660.056 ms This plan is not good: Execution Time: 2660.056 ms, But cost=8.38..8.39 only - looks too small! On this step: -> Index Scan using docum_dt_7ee1d676 on public.docum (cost=0.43..420487.43 ... Higher cost = 420487 looks adequate, but it was lost somehow and did not count into total query plan cost. -- There is a possible fix for query: just add count(*) column to SELECT: EXPLAIN (ANALYZE, VERBOSE, BUFFERS, SUMMARY, SETTINGS, TIMING) SELECT count(*) as cnt, MIN(docum.dt) AS "dt__min", MAX(docum.dt_real) AS "dt_real__max" FROM docum WHERE docum.dt_real >= '2025-08-14T09:44:09.033592'::timestamp; Aggregate (cost=2512.56..2512.57 rows=1 width=24) (actual time=10.179..10.180 rows=1 loops=1) Output: count(*), min(dt), max(dt_real) Buffers: shared hit=383 read=165 -> Index Scan using docum_dt_real_2b81c58c on public.docum (cost=0.43..2068.22 rows=59245 width=16) (actual time=0.013..6.430 rows=59999 loops=1) Output: id, dt, dt_real Index Cond: (docum.dt_real >= '2025-08-14 09:44:09.033592'::timestamp without time zone) Buffers: shared hit=383 read=165 Settings: work_mem = '16MB', search_path = 'public, public, "$user"' Planning: Buffers: shared hit=9 read=1 Planning Time: 0.927 ms Execution Time: 10.199 ms -- This plan cost = 2512 is much higher, but execution time is way better! Did You manage to reproduce this plan? Can I help You with more details?