Skip to content

db: faster ptr() as an inlinable SQL function - #469

Merged
kofrezo merged 1 commit into
mainfrom
md_ptr_sql_function
Sep 21, 2026
Merged

kofrezo merged 1 commit into
mainfrom
md_ptr_sql_function

Conversation

@brainexe

Copy link
Copy Markdown
Member

Faster public.ptr() as an inlinable SQL function

db/ptr.sql ships public.ptr(inet), which builds the reverse DNS name
(x.x.x.x.in-addr.arpa / nibbles.ip6.arpa) of an address and backs the
expression indexes server_ptr_idx and server_inet_attribute_ptr_idx.
It was a plpgsql function that split host() output with regexes, looped
over the IPv6 groups and re-assembled the labels with string_agg, which
made it expensive per row: any query that computes it for many rows, such
as a view exposing PTR records, spent most of its time inside ptr().

Change

Rewrite it as a LANGUAGE sql IMMUTABLE PARALLEL SAFE function that reads
the address bytes from inet_send() (4 header bytes, then the address):

  • IPv4: the four octets via get_byte(), reversed, plus .in-addr.arpa.
  • IPv6: encode(..., 'hex') gives the 32 fully expanded nibbles, then
    reverse() and one regexp_replace to insert the dots, plus .ip6.arpa.

The body has no STRICT, no sub-SELECT and only immutable calls, which
is what lets Postgres inline it into the calling query instead of calling a
function per row. The netmask is ignored as before (host() did the same).
ptr(NULL) still returns NULL.

Results

Measured on a staging database with 41,845 rows in server_inet_attribute
(26,602 IPv6, 15,243 IPv4), single worker, everything in shared buffers:

old plpgsql new SQL
SELECT ptr(value) FROM server_inet_attribute 2251 ms 465 ms
per row ~54 µs ~11 µs
inlined by the planner no yes

Equivalence was checked over every address in server.intern_ip and
server_inet_attribute.value:

SELECT value FROM server_inet_attribute WHERE ptr(value) IS DISTINCT FROM ptr_new(value)
UNION ALL
SELECT intern_ip FROM server WHERE intern_ip IS NOT NULL AND ptr(intern_ip) IS DISTINCT FROM ptr_new(intern_ip);
-- (0 rows)

Because the output is byte-identical, the existing expression indexes stay
valid. CREATE OR REPLACE swaps the body in place on databases that
already have the function.

Same body as serveradmin_extras migration 0006_ptr_function: expands the
address from the inet_send() bytes instead of a plpgsql regex loop. Returns
identical text for every address in server and server_inet_attribute, runs
4.8x faster (465 ms vs 2251 ms over 41845 inet attributes) and is inlined
by the planner, which makes the powerdns_records view's PTR branches cheap.
The expression indexes below stay valid since the results do not change.
@kofrezo
kofrezo merged commit 446ff9a into main Sep 21, 2026
5 checks passed
@kofrezo
kofrezo deleted the md_ptr_sql_function branch September 21, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants