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 1vYR1J-003PR5-14 for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Dec 2025 15:43:26 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vYR1I-0053Tj-0G for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Dec 2025 15:43:24 +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 1vYR1H-0053Ta-2K for pgsql-hackers@lists.postgresql.org; Wed, 24 Dec 2025 15:43:24 +0000 Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vYR1F-002XhF-1j for pgsql-hackers@lists.postgresql.org; Wed, 24 Dec 2025 15:43:23 +0000 Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-4779cc419b2so47894445e9.3 for ; Wed, 24 Dec 2025 07:43:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766590996; x=1767195796; darn=lists.postgresql.org; h=content-disposition:mime-version:message-id:subject:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=yH5QFwwGRjLcdJg8e6jNimSobM5wmtjoicGphOyJvV8=; b=IdAU/x0MncbnEZ35eZGHlTlEh+esgEDxEINMYHAAy+L3Ykc9HAWtRRnTwt3SLJGAp5 NVSRYGehHs26byVwmbxDUzG+jxcG02CKqDsjc+Oc7RguibfbLVRXmaXpGtrdxoVRLnI6 UyySTT4uT5+bPA3jVBQMzqEA32DqGTeM8BNkH9c2yIUKCGRsdhJLgM2XWaMkja9IlpiW hLemA1BWSIbqNmY6g76ID0AhOPC0eJoMHrHsWzRvuQhfNv6/DbGQmdBjWHTue4enqLNP M/9x3xZ2eeKrHcHRaO1ir4pnjGPX49IjQn33NIbMEU7I1LKesnfRMd8rzLskcTyMZrir O7/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766590996; x=1767195796; h=content-disposition:mime-version:message-id:subject:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=yH5QFwwGRjLcdJg8e6jNimSobM5wmtjoicGphOyJvV8=; b=GEfdxvKqzXc7wRv2LncL9Rb32IT5HLSAAQtaIAv3vKOD9VIWSXUL8G4Lbm9yfN4hEU 4eGlAIq3rJ94C1Um4auhT211GhJ0IpCjnQfxvWpUXDNxSgJzidcaBp7wQHH1CNeeTaJa gVbOx58XO3tdC3S7NIEuLJgh0cQmTDworxDVCfsyfuXROeUYsz1EoyUfWEFFbF2134lM y14jV0x5utQIo4nvJPCbPc66tR+P72vdMz4npen0k7Hhcfv+CtaANwDB0gSC1OjhPexI MuxWdUqB/jRmckMtyOinmIJvs7y+TFy5FDCNTkUjNTiwBdw2SYy/aRsMNY6t1+BPsVeK jG+Q== X-Gm-Message-State: AOJu0YxWwMv/Cs3te4rqewbsjrGKZATyrIX0bPvM2nYDKc1Ifu7ZqDM2 GswmIBcuKdAzwMWIQyGp62W8pFoeQoA4VhGZI5hQ+4ehb0iKpBk1lerEpCl+BHauZeA= X-Gm-Gg: AY/fxX4tv3ZmHI0kApPW80FvY+8WJi396Ke0jQxjGokR3uO37XQUxVg7BYwW4NW7CzK 3VvPc1EfUb4NiJIrwCr1ctcCAlX/uI3uC2s4L74C+mBmJdhWYYdiFpUS3V8HP3AMOYnG1uWiUyj AulUXo12loSy0XCFhJ36xK1STqlUpj6M12+txohAH++fI8F66zlpm3ar0Ey2wbe07JgE6KMzCRe Nurwum67H7tiLm165tt28Dmg23s7uBWPx2c0MLxP5YCdGdpmU+o1b/yiCx3EASMZZ+QdrMxbPPq zUXIKX4eA6xenlTgKeYLCsHhbWIPg6haS7Wl7P5HVG1WfBNrTwn3V9cOoPTiO0ZP9kaTQRIN3+e QlZ0XjUfKMzgypTSQp+iZwDF6fUXVGjNFD0LHN89KuMBhtjXEIpECT4jNHZxZBc3SnBNv17MJWF TZOFE= X-Google-Smtp-Source: AGHT+IHGVSA/8JgbtOE7Sc4rsRosOAlilqd4k6g/QImNYHjrca/JH27fMXX/6KwA/cGtWIjGWpSc1Q== X-Received: by 2002:a05:600c:444b:b0:477:561f:6fc8 with SMTP id 5b1f17b1804b1-47d19549625mr160866365e9.5.1766590995608; Wed, 24 Dec 2025 07:43:15 -0800 (PST) Received: from jrouhaud ([2a01:e0a:c:bcf0:7aec:e54:d98c:f301]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-47d193e329asm301903525e9.15.2025.12.24.07.43.14 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 24 Dec 2025 07:43:14 -0800 (PST) Date: Wed, 24 Dec 2025 23:43:13 +0800 From: Julien Rouhaud To: pgsql-hackers@lists.postgresql.org Subject: Cleaning up PREPARE query strings? Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="biRhQYMBrGQlqcCN" Content-Disposition: inline List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --biRhQYMBrGQlqcCN Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, Currently prepared statements store the whole query string that was submitted by the client at the time of the PREPARE as-is. This is usually fine, but if that query was a multi-statement query string it can lead to a waste of memory. There are some pattern that are more likely to have such overhead, mine being an application with a fixed set of prepared statements that are sent at the connection start using a single query to avoid extra round trips. One naive example of the outcome is as follow: #= PREPARE s1 AS SELECT 1\; PREPARE s2(text) AS SELECT oid FROM pg_class WHERE relname = $1\; PREPARE s3(int, int) AS SELECT $1 + $2; PREPARE PREPARE PREPARE =# SELECT name, statement FROM pg_prepared_statements ; name | statement ------+---------------------------------------------------------------------------- s1 | PREPARE s1 AS SELECT 1; PREPARE s2(text) AS SELECT oid FROM pg_class WHERE+ | relname = $1; PREPARE s3(int, int) AS SELECT $1 + $2; s2 | PREPARE s1 AS SELECT 1; PREPARE s2(text) AS SELECT oid FROM pg_class WHERE+ | relname = $1; PREPARE s3(int, int) AS SELECT $1 + $2; s3 | PREPARE s1 AS SELECT 1; PREPARE s2(text) AS SELECT oid FROM pg_class WHERE+ | relname = $1; PREPARE s3(int, int) AS SELECT $1 + $2; (3 rows) The more prepared statements you have the bigger the waste. This is also not particularly readable for people who want to rely on the pg_prepared_statements views, as you need to parse the query again yourself to figure out what exactly is the associated query. I assume that some other patterns could lead to other kind of problems. For instance if the query string includes a prepared statement and some DML, it could lead some automated program to replay both the PREPARE and DML when only the PREPARE was intended. I'm attaching a POC patch to fix that behavior by teaching PREPARE to clean the passed query text the same way as pg_stat_statements. Since it relies on the location saved during parsing the overhead should be minimal, and only present when some space can actually be saved. Note that I first tried to have the cleanup done in CreateCachedPlan so that it's done everywhere including things like the extended protocol but this lead to too many issues so I ended up doing it for an explicit PREPARE statement only. With this patch applied, the above scenario gives this new output: =# SELECT name, statement FROM pg_prepared_statements ; name | statement ------+---------------------------------------------------- s1 | PREPARE s1 AS SELECT 1 s2 | PREPARE s2(text) AS SELECT oid FROM pg_class WHERE+ | relname = $1 s3 | PREPARE s3(int, int) AS SELECT $1 + $2 (3 rows) One possible issue is that any comment present at the beginning of the query text would be discarded. I'm not sure if that's something used by e.g. pg_hint_plan, but if yes it's always possible to put the statement in front of the SELECT (or other actual first keyword) rather than the PREPARE itself to preserve it. --biRhQYMBrGQlqcCN Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0001-Cleanup-explicit-PREPARE-query-strings.patch