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 1whU0f-000Fj4-2n for pgsql-hackers@arkaria.postgresql.org; Wed, 08 Jul 2026 15:16: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 1whU0d-007cbp-0J for pgsql-hackers@arkaria.postgresql.org; Wed, 08 Jul 2026 15:16:23 +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 1whU0c-007cbg-25 for pgsql-hackers@lists.postgresql.org; Wed, 08 Jul 2026 15:16:23 +0000 Received: from mail-yx1-xb12c.google.com ([2607:f8b0:4864:20::b12c]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1whU0a-00000000I4s-1muI for pgsql-hackers@postgresql.org; Wed, 08 Jul 2026 15:16:22 +0000 Received: by mail-yx1-xb12c.google.com with SMTP id 956f58d0204a3-667627ae83aso1015645d50.0 for ; Wed, 08 Jul 2026 08:16:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783523779; cv=none; d=google.com; s=arc-20260327; b=Tp6qkvjGRMC+YjIQQq+vUwalp6raofxlhNJZyk1tNO2zR6/ZOPEwUp3beebthQSBgZ C5wuO4Gur9KR7cnzB9Ej8n0tpAVugvmxiWRlMw9TeYk4ohodJPmlhhM6BxlxCcyRFPRO p9EysfQCmTyBB3MWQF+kiphAEu4zRtRZnR7Oaxd1SYU3AkPufsOCdDpaVYFPNLIbRVUh fZaLEG94BLTurz0gQgjpg74y+41U3agsCSp0ay2rGyOnL9obximd2CVTD0Yqv1QVQhGs peVbhP78d7G5KzoSa7yk44ymjdLKLGLh/u+GPaWS9kWU80yJk0HKcDq4kH9jsvC4vG4w 8b+w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=j58RvjMNpV5XGQNEM3Jx89jHdQWvUZhM6umv7O+LzI0=; fh=8ctfZ/foxFsrb3BP0tfKHCuqyHoVGFwt7y+OqVrpXK4=; b=Wl8iNW675qPhNsz6rtr+J8G0xXBQi0OfHeMCBfCGe2tjuh/lgs1ZWjpfSIYUtjRD2i ROAoHubed39ktuyn7paearj0rE7dMFlmou17XAUsohhA9PJ3QGQyvkNSN0+ZOtaElYOy 66TJXD/einmmA/XEExddc2iRO99tvAHTn/iPCuy23tBbpVeT9MPqMGsXWjQxoDnmoBGl kB4EAVapaJICqy9qbFXEisf5K3wku5IrwqcY6a0uLY6amPYoi4QvlkfAf31tZXea8juP lBqvMAlf8Yr8qlLy2zmbmDon5r95IgvcEQSxW6xB9w+nFOjMBC+CcsOEdyxOB8MHIU4H p1Sg==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dunslane-net.20251104.gappssmtp.com; s=20251104; t=1783523779; x=1784128579; darn=postgresql.org; h=content-type:cc:to:subject:message-id:date:from:in-reply-to :references:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=j58RvjMNpV5XGQNEM3Jx89jHdQWvUZhM6umv7O+LzI0=; b=Vaijtt2N3dJ9LEDpcyNUJG+eZkN8ITcxuB0Wxe95aPf2PV5/jYzXwLiUfE8eXQqhoF 6F8K57CWwoOgTlVqvwlxOuE6Ak/QGtd5/yHnCy5DTlD+eSXmjQYx5I6Z1OeXfwhsyUdx o2yCat0iCUWnO9rwAfpCHU4J974ZO0pk0hoO2IixfimgmpZFxlRLU4m5TM8MzlMh6qix ws5Ct03G4tktQt7q3h3MVXwSTI67vp1ZliOZKDeTYnrAffAfkL8Y/PgdGZnsK6FZuYiH 1tXQN3Phzf87Zk0J0CrGCqpumvuhG1eR2JliXGL4tyM4l4dvfKeklAAUf5gRIq+lSFY+ v50A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783523779; x=1784128579; h=content-type: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:content-type; bh=j58RvjMNpV5XGQNEM3Jx89jHdQWvUZhM6umv7O+LzI0=; b=QuWNX3+308ZXyjD6nRSdEfzISQpENxcyeWzBHiHzE9IEEtYA+7TTE9GgJV6TWKzliM 89oA/of/IHTwceuN4ekrLpIb8L7w0U4D1TxiWtW/sBzbzN0Od7vlFMyVHITchybZLput xxM1j/IY3Hrwxy/8nI5Rqh/YyCnR/JbOMV3NdI9t430Y1QghBGFShHWEGcdXwPjuyIQ4 g33odjj3DQXb0Cy1wQ6UcI00QwtUYigAU38koeiSRXroR8VgIEUxJlpHxfj0Y14s8PtJ UUcbnpoxevVj3SQWCiaj02vjqkj1IZuChSCaag7m4CHkiHgPgY8BC2n2+WCLfECebctA Z9Ag== X-Gm-Message-State: AOJu0YzegBdBNAmOBwS4L4S8qCFGXv7jxpkRHu+ZiO6cDquVVCNs+MkH DrV9dLeguT+JZlDXjQo12tRMCVk0gY9beYNJjhKzYtnnIZnGq5HNH4ALhQ4BJQ0zX+NUhXyYBk8 Dy5yS+isZ90T3WizJ7V8Sia9yZWOtd6yOvcgbGkWa6Q== X-Gm-Gg: AfdE7cmSVDdrWuuHi9znhjVC6o3aFsSYgbSL0iSFsN3yeA4LmcAzJ57syuNhJcbZAxj 1mwV1OIS1kZg6Uz3un7Wkfi/YPelzHtLanQZRR5Soo/XLdusmCD0Eotet1ZihLpvPS/M6+lYvV2 CjcGcL0ciojAzW8Qu6ciN9KvUp4tE/RBCMg4XpCzcHUR/G/SjsaoHD/RN/teZgD/6k95dTSAIQ4 Vw6wqUF0yiOmGbq55rIAXTe2GJB5gQsGfj9KJTP48SM0nbnwF5u2e5xJ7QSyM48M3ZDXljedLW3 rlLUvBW4fQ== X-Received: by 2002:a05:690e:4548:20b0:664:baca:a9bd with SMTP id 956f58d0204a3-6679f06e9ecmr1625730d50.30.1783523778746; Wed, 08 Jul 2026 08:16:18 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Andrew Dunstan Date: Wed, 8 Jul 2026 11:16:07 -0400 X-Gm-Features: AVVi8CeUiQdq3GVuM7qKbBUQkaO3DFT4q3jn3Hr1CTJPa8fB8UqLzGozlXaw2vI Message-ID: Subject: Re: json/jsonb cleanup + FmgrInfo caching To: Robert Haas Cc: "pgsql-hackers@postgresql.org" Content-Type: multipart/alternative; boundary="00000000000048360406561afd45" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --00000000000048360406561afd45 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, Jul 2, 2026 at 12:25=E2=80=AFPM Robert Haas = wrote: > Hi, > > While working on my "sandboxing untrusted code" project, I found > myself investigating how the JSON and JSONB code calls type output and > cast functions. I feel like this needs some cleanup in order to avoid > blocking that project, and it turns out that there are also > significant opportunities to improve performance, so here are some > patches. One caveat: one of these patches causes a small backward > compatibility break, because the current behavior is wrong. See below > for full details. > > First, a quick performance demonstration: > > CREATE TABLE hstores AS SELECT hstore('k', g::text) x FROM > generate_series(1,4000000) g; > SELECT any_value(json_build_object('a', x)) FROM hstores; > > Unpatched, median-of-three runs was 398.571 ms. Patched, 193.714 ms. > That's a 2.05x speedup, which is the highest I observed in my test > queries. What I found is that this technique shows the largest gains > with user-defined types like hstore, smaller gains with built-in types > like int, and the smallest gains of all with container types such as a > user-defined record type. Generally, jsonb benefited more than json. > The aggregates - json_agg and jsonb_agg - showed very little benefit > or even small regressions, but I'm pretty confident that the > regressions are simply noise that goes away with a sufficiently large > number of sufficiently-careful test runs. Everything else gets faster, > often by 50%+. Leaving aside the aggregates which are expected to show > little or no benefit, here's one of the less-sympathetic cases: > > CREATE TABLE ints AS SELECT x FROM generate_series(1,4000000) x; > SELECT any_value(to_json(x)) FROM ints; > > The speedup is smaller here because it's json rather than jsonb and > because it's int rather than hstore, but it's still 117.461 ms > unpatched vs. 89.895 patched, a 30% speedup. > > OK, now let's go through the patches: > > 0001 refactors the json_categorize_type() function to initialize an > FmgrInfo instead of returning a base function OID. All of the built-in > JSON aggregates are updated to cache this FmgrInfo across calls, but > it really doesn't save much. In the process of working on this > refactoring it came to light that the current behavior of > json_check_mutability() is incorrect: it erroneously treats record, > anyarray, and anycompatiblearray as immutable when in fact they should > be treated as stable. This refactoring preserves that incorrect > behavior. > > 0002 fixes the bug discovered during the development of 0001 by > removing the special case. AFAICT, the anyarray and anycompatiblearray > cases are unreachable, but the record case is reachable, and the > included test case shows how this could hypothetically matter. It > seems unlikely we'll inconvenience any significant number of users by > changing this, but in theory somebody's upgrade could fail. > > 0003 moves some code around to avoid problems with circular header > dependencies, creating new files jsontypes.c/h. > > 0004 refactors the SQL-level JSON constructors -- JSON_OBJECT, > JSON_ARRAY, and JSON_SCALAR -- to make use of the new type caching > infrastructure. > > 0005 refactors the SQL-callable functions similarly. This means > to_json(b), json(b)_build_object, and json(b)_build_array. > > > Looks pretty good. The old `add_json()`/`add_jsonb()` helpers (removed in 0004/0005) checked `val_type =3D=3D InvalidOid` and raised a clean `ERRCODE_INVALID_PARAMETER_VALUE` / "could not determine input data type" error. Now I think we'd get something like "cache lookup failed for type 0", which is rather more opaque. Not sure if that matters. For VARIADIC arrays, for (int i =3D 0; i < nargs; ++i) fmgr_info_copy(&(*outflinfos)[i], &jcache->flinfos[0], CurrentMemoryContext); is going to copy the same flinfo for every array element for every row, The comment says that it's done that way to avoid complicating the code, and that seems reasonable. I don't know how often these functions are used with explicit VARIADIC array arguments, so it might be a niche case. cheers andrew --00000000000048360406561afd45 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Thu, Jul 2, = 2026 at 12:25=E2=80=AFPM Robert Haas <robertmhaas@gmail.com> wrote:
Hi,

While working on my "sandboxing untrusted code" project, I found<= br> myself investigating how the JSON and JSONB code calls type output and
cast functions. I feel like this needs some cleanup in order to avoid
blocking that project, and it turns out that there are also
significant opportunities to improve performance, so here are some
patches. One caveat: one of these patches causes a small backward
compatibility break, because the current behavior is wrong. See below
for full details.

First, a quick performance demonstration:

CREATE TABLE hstores AS SELECT hstore('k', g::text) x FROM
generate_series(1,4000000) g;
SELECT any_value(json_build_object('a', x)) FROM hstores;

Unpatched, median-of-three runs was 398.571 ms. Patched, 193.714 ms.
That's a 2.05x speedup, which is the highest I observed in my test
queries. What I found is that this technique shows the largest gains
with user-defined types like hstore, smaller gains with built-in types
like int, and the smallest gains of all with container types such as a
user-defined record type. Generally, jsonb benefited more than json.
The aggregates - json_agg and jsonb_agg - showed very little benefit
or even small regressions, but I'm pretty confident that the
regressions are simply noise that goes away with a sufficiently large
number of sufficiently-careful test runs. Everything else gets faster,
often by 50%+. Leaving aside the aggregates which are expected to show
little or no benefit, here's one of the less-sympathetic cases:

CREATE TABLE ints AS SELECT x FROM generate_series(1,4000000) x;
SELECT any_value(to_json(x)) FROM ints;

The speedup is smaller here because it's json rather than jsonb and
because it's int rather than hstore, but it's still 117.461 ms
unpatched vs. 89.895 patched, a 30% speedup.

OK, now let's go through the patches:

0001 refactors the json_categorize_type() function to initialize an
FmgrInfo instead of returning a base function OID. All of the built-in
JSON aggregates are updated to cache this FmgrInfo across calls, but
it really doesn't save much. In the process of working on this
refactoring it came to light that the current behavior of
json_check_mutability() is incorrect: it erroneously treats record,
anyarray, and anycompatiblearray as immutable when in fact they should
be treated as stable. This refactoring preserves that incorrect
behavior.

0002 fixes the bug discovered during the development of 0001 by
removing the special case. AFAICT, the anyarray and anycompatiblearray
cases are unreachable, but the record case is reachable, and the
included test case shows how this could hypothetically matter. It
seems unlikely we'll inconvenience any significant number of users by changing this, but in theory somebody's upgrade could fail.

0003 moves some code around to avoid problems with circular header
dependencies, creating new files jsontypes.c/h.

0004 refactors the SQL-level JSON constructors -- JSON_OBJECT,
JSON_ARRAY, and JSON_SCALAR -- to make use of the new type caching
infrastructure.

0005 refactors the SQL-callable functions similarly. This means
to_json(b), json(b)_build_object, and json(b)_build_array.




Looks pretty good.= =C2=A0

The old `add_json()`/`add_jsonb()` helpers = (removed in 0004/0005) checked `val_type =3D=3D InvalidOid` and raised a cl= ean `ERRCODE_INVALID_PARAMETER_VALUE` / "could not determine input dat= a=C2=A0type" error. Now I think we'd get something like=C2=A0"= ;cache lookup failed for type 0", which is rather more opaque. Not sur= e if that matters.

For VARIADIC arrays,
=
=C2=A0 =C2=A0for (int i =3D 0; i < nargs; ++i)
=C2=A0 = =C2=A0 =C2=A0 =C2=A0fmgr_info_copy(&(*outflinfos)[i], &jcache->f= linfos[0], CurrentMemoryContext);

is going to copy the sa= me flinfo for every array element for every row, The comment says that it&#= 39;s done that way to avoid complicating the code, and that seems reasonabl= e. I don't know how often these functions are used with explicit VARIAD= IC array arguments, so it might be a niche case.

c= heers

andrew
--00000000000048360406561afd45--