timescaledb/test/sql/copy_memory_usage.sql
Nikhil Sontakke ebbca2dd77 Fix leaks with functions in DML
If plpgsql functions are used in DML queries then we were leaking 8KB
for every invocation of that function. This can quickly add up.

The issue was that the "CurTransactionContext" was not getting cleaned
up after every invocation. The reason was that we were inadvertantly
allocating a temporary list in that context. Postgres then thought that
this CurTransactionContext needs to be re-used further and kept it
around. We now use a proper memory context to avoid this.

Fixes #7053
2024-07-02 13:32:06 +05:30

81 lines
4.1 KiB
PL/PgSQL

-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- Test that transaction memory usage with COPY doesn't grow.
-- We need memory usage in PortalContext after the completion of the query, so
-- we'll have to log it from a trigger that runs after the query is completed.
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
create table uk_price_paid(price integer, "date" date, postcode1 text, postcode2 text, type smallint, is_new bool, duration smallint, addr1 text, addr2 text, street text, locality text, town text, district text, country text, category smallint);
-- Aim to about 100 partitions, the data is from 1995 to 2022.
select create_hypertable('uk_price_paid', 'date', chunk_time_interval => interval '90 day');
-- This is where we log the memory usage.
create table portal_memory_log(id serial, bytes bigint);
-- Returns the amount of memory currently allocated in a given
-- memory context. Only works for PortalContext, and doesn't work for PG 12.
create or replace function ts_debug_allocated_bytes(text) returns bigint
as :MODULE_PATHNAME, 'ts_debug_allocated_bytes'
language c strict volatile;
-- Log current memory usage into the log table.
create function log_memory() returns trigger as $$
begin
insert into portal_memory_log
values (default, ts_debug_allocated_bytes('PortalContext'));
return new;
end;
$$ language plpgsql;
-- Add a trigger that runs after completion of each INSERT/COPY and logs the
-- current memory usage.
create trigger check_update after insert on uk_price_paid
for each statement execute function log_memory();
-- Memory leaks often happen on cache invalidation, so make sure they are
-- invalidated often and independently (at co-prime periods).
set timescaledb.max_open_chunks_per_insert = 2;
set timescaledb.max_cached_chunks_per_hypertable = 3;
-- Try increasingly larger data sets by concatenating the same file multiple
-- times.
\copy uk_price_paid from program 'bash -c "cat <(zcat < data/prices-10k-random-1.tsv.gz)"';
\copy uk_price_paid from program 'bash -c "cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)"';
\copy uk_price_paid from program 'bash -c "cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)"';
\copy uk_price_paid from program 'bash -c "cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)"';
\copy uk_price_paid from program 'bash -c "cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)"';
select count(*) from portal_memory_log;
-- Check that the memory doesn't increase with file size by using linear regression.
select * from portal_memory_log where (
select regr_slope(bytes, id - 1) / regr_intercept(bytes, id - 1)::float > 0.05
from portal_memory_log
);
-- Test plpgsql leaks
CREATE TABLE test_ht(tm timestamptz, val float8);
SELECT * FROM create_hypertable('test_ht', 'tm');
-- Use a plpgsql function to insert into the hypertable
CREATE OR REPLACE FUNCTION to_double(_in text, INOUT _out double precision)
LANGUAGE plpgsql IMMUTABLE parallel safe
AS $$
BEGIN
SELECT CAST(_in AS double precision) INTO _out;
EXCEPTION WHEN others THEN
--do nothing: _out already carries default
END;
$$;
-- TopTransactionContext usage needs to remain the same after every insert
-- There was a leak earlier in the child CurTransactionContext
BEGIN;
INSERT INTO test_ht VALUES ('1980-01-01 00:00:00-00', to_double('23.11', 0));
SELECT sum(total_bytes) from pg_backend_memory_contexts where parent = 'TopTransactionContext';
INSERT INTO test_ht VALUES ('1980-02-01 00:00:00-00', to_double('24.11', 0));
SELECT sum(total_bytes) from pg_backend_memory_contexts where parent = 'TopTransactionContext';
COMMIT;