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 1vSFfN-009jwK-2y for pgsql-hackers@arkaria.postgresql.org; Sun, 07 Dec 2025 14:23:14 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vSFfL-00DBhL-26 for pgsql-hackers@arkaria.postgresql.org; Sun, 07 Dec 2025 14:23:11 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vSFfK-00DBhB-39 for pgsql-hackers@lists.postgresql.org; Sun, 07 Dec 2025 14:23:11 +0000 Received: from mail-ej1-x636.google.com ([2a00:1450:4864:20::636]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vSFfH-003bON-0a for pgsql-hackers@lists.postgresql.org; Sun, 07 Dec 2025 14:23:09 +0000 Received: by mail-ej1-x636.google.com with SMTP id a640c23a62f3a-b7277324204so608448866b.0 for ; Sun, 07 Dec 2025 06:23:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=f10-com-br.20230601.gappssmtp.com; s=20230601; t=1765117384; x=1765722184; darn=lists.postgresql.org; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=cd39PLfNvpZoOMgvAasqWVRDpEXkiqYZI1BdobYIwgo=; b=C+iAVItf7wHqdeFftyJKdrlFm0mq58gx0u+/cHIiAEW0LWJHB5LUzb9MWQy3TdHGMW VVo17E65xviUFvvFDhESFRYen+RmcGieRPRX4alyr0IE3yhUUd+Dyr+FH0Eym+xeIQ6Y fYEsfEfGAtErj5nj8VHE6Dlxp+lkvk2LfZg/wQc2oJi3PDzZN+vCbSykmuAGGfy/xJvE 8J5yXBjAA9+NxheV3V4OJQKcHkGRdKOB2iTfCeE7I1FmxjokTZOC6hfyajZDQ2P8U9Wd 8UnalGZhDxiIVakle3qyXx3moy1eDRVFo+QJy0a9IYz7yO1ZdZpIH980lxNgCnZOsZKd QlaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1765117384; x=1765722184; h=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; bh=cd39PLfNvpZoOMgvAasqWVRDpEXkiqYZI1BdobYIwgo=; b=YwTHiQDr6lxa8l9JD8HdNEoYKr82gtQUym9lNVwIil1pQhLe7MwLlBv1m2PDxsb5Y7 cTlVxHSHsUlrBVX37a3Qfl3MBW3+e+pQTX7L/ZiMvmuo8Dy4GjujybqI/fOwdBXGnr10 IetJpk6ekIY3BDEgTiOxL42QlblC0DuTkSoJbNeFkIrelaO73gGyrVzlCTjAm4yAZ0fk Zp3F1tw4Giweu4YDoSmfmwU+nP7IaZQfaiLyWnsS11qMkHPJJ5Q9pLBI6hpWb6yMAUKV /O1kPNyfT+UeFhCJXacwn7vSF0//7jSmuBYKQEAXb1hz4Bbeb3PjHQmhsqAacJZqI3a7 Sruw== X-Gm-Message-State: AOJu0Yw34W9huSpj1xRlH75Vk3QRELljusV+pMAbM8X/eJwwGZ0QkRaZ iKR1DEKnTUBNWiUA9ny0RK2xsa4TcvlNI20eng+mj0Ihhv8HM04ibFWCAilrmi3rHBU5D0+undE HwizRB9dJsxgjpdSkNUheEL/0ZTpkco1Q0ZHc+/Dv+/XP9cS8cu5X1g== X-Gm-Gg: ASbGnctIjMd2GkRguggzhW9UGQdCBHaH2YaoLZOLP9q0XyZR8INx+IGOV1khizEYYLu GtEB6Jw+oN6F1MS6VfwIKbvYr3c0go5E3SzuINUgs8f9Gptncoea/nHi876f/Mow0Odv0FV2fFo wg78c9uJUH0/L7G9NiOXzJkj6pAkxl4bfniEM1zWHCV2qaO8DtSQphnJCUedo4vCXX+XhaQEC0q K2jG7w76Hjabx1X8gE14z9lXL+Br61IggFDB3U9Z0PlsyeuhoKAKBUdfA5MBSwrpdM3aZRXKA== X-Google-Smtp-Source: AGHT+IE2R8WwlysyFwebPYl+hkejD963I5f0f2rLrHPVuPZ8JREX1uqm2jnGdf0DlYvQUF2x2FCBLwUc+c+PTXJQo78= X-Received: by 2002:a17:907:94ce:b0:b76:f57f:a2c3 with SMTP id a640c23a62f3a-b7a24274303mr513997466b.12.1765117384193; Sun, 07 Dec 2025 06:23:04 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: From: Marcos Pegoraro Date: Sun, 7 Dec 2025 11:22:27 -0300 X-Gm-Features: AQt7F2pKs9KbCyx850NTUuUDad61x65Q2uQ_d9PejPw_vAKHe8O1b69TIvHSC9w Message-ID: Subject: Re: Initial COPY of Logical Replication is too slow To: PostgreSQL Hackers Content-Type: multipart/alternative; boundary="000000000000ac04f606455d6a6a" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000ac04f606455d6a6a Content-Type: text/plain; charset="UTF-8" You can see how much time a subscriber will need to get all files which were added with this. Run first time and will create 10 thousand tables, publish them and measure how much time to get all tables Ready on pg_subscription_rel. Run again to add more 10 thousand tables and see that time will increase, more tables and more time. This is just to show that if you create a subscription with a high number of tables it spends more time doing select on pg_get_publication_tables than the time spent actually copying. My use case I have 50 thousand tables, and it takes 5 seconds every time it needs to get next table to copy. --Create a empty publication create publication my_pub; --Run these 3 following anonymous blocks to create schemas, tables and add them to publication. --Need to have 3 blocks because I cannot create a table in a schema that is not committed. And the same for a publication. do $$ declare Schemas_Add integer = 100; Actual_Schema text; begin for Actual_Schema in select 'test_'||(select to_char(coalesce(max(substring(nspname,'test_(\d+)')::integer),0)+g,'FM00000') from pg_namespace where nspname ~ 'test_\d+') from generate_series(1,Schemas_Add) g loop execute format('create schema %s',Actual_Schema); end loop; end;$$; do $$ declare Tables_Add integer = 100; Actual_Schema text; begin for Actual_Schema in select nspname from pg_namespace where nspname ~ 'test_\d+' and not exists(select from pg_class where relnamespace = pg_namespace.oid) loop for j in 1..Tables_Add loop execute format('create table %s.test_%s as select generate_series(1,random(0,10))::integer id;', Actual_Schema,to_char(j,'FM00000')); end loop; end loop; end;$$; do $$ declare Schemas_To_Add text = (select string_agg(nspname,',') from pg_namespace n where nspname ~ 'test_\d+' and not exists(select from pg_publication_namespace where pnnspid = n.oid)); begin execute format('alter publication my_pub add tables in schema %s;',Schemas_To_Add); end;$$; --Then you can see what was generated and go to the subscriber side to refresh the subscription and measure time spent to synchronize. select * from pg_Namespace where nspname ~ 'test_\d+'; select pnnspid::regnamespace, * from pg_publication_namespace; select oid::regclass, * from pg_Class where relnamespace::regnamespace::text ~ 'test_\d+' and relkind = 'r'; --Later just clean what you do. drop publication my_pub; do $$ declare Schema_Drop text; begin for Schema_Drop in select nspname from pg_Namespace where nspname ~ 'test_\d+' loop execute format ('drop schema %s cascade;',Schema_Drop); end loop; end;$$; regards Marcos --000000000000ac04f606455d6a6a Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
You can see how much time a subscriber wi= ll need to get all files which were added with this.
Run first ti= me and will create 10 thousand tables, publish them and measure how much ti= me to get all tables Ready on pg_subscription_rel.
R= un again to add more 10 thousand=C2=A0tables and see that time will increas= e, more tables and more time.

This is just to show that if you create a subscription with a high = number of tables it spends more time doing select on pg_get_publication_tab= les than the time spent actually copying. My use case I have 50 thousand ta= bles, and it takes 5 seconds every time it needs to get next table to copy.=

--Create a empty=C2=A0p= ublication
create publication my_pub;

--Run t= hese 3 following anonymous blocks to create schemas, tables and add them to= publication.
--Need to have 3 blocks because I cann= ot create a table in a schema that is not committed. And the same for a pub= lication.
do $$ declare Schemas_Add integer =3D 100; Actual_Schema text;= begin
=C2=A0 for Actual_Schema in select 'test_'||(select to_ch= ar(coalesce(max(substring(nspname,'test_(\d+)')::integer),0)+g,'= ;FM00000')
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0from pg_namespace where nspname ~ 'test_\d+') from genera= te_series(1,Schemas_Add) g loop
=C2=A0 =C2=A0 execute format('create= schema %s',Actual_Schema);
=C2=A0 end loop;
end;$$;

do $$= declare Tables_Add integer =3D 100; Actual_Schema text; begin
=C2=A0 fo= r Actual_Schema in select nspname from pg_namespace where nspname =C2=A0~ &= #39;test_\d+' and
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 not exists(select from pg_class where relnamesp= ace =3D pg_namespace.oid) loop
=C2=A0 =C2=A0 for j in 1..Tables_Add loop=
=C2=A0 =C2=A0 =C2=A0 execute format('create table %s.test_%s as sel= ect generate_series(1,random(0,10))::integer id;',
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Actual_Schema,to= _char(j,'FM00000'));
=C2=A0 =C2=A0 end loop;
=C2=A0 end loop;=
end;$$;

do $$ declare Schemas_To_Add text =3D (select string_agg= (nspname,',') from pg_namespace n where nspname ~ 'test_\d+'= ; and
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 not exists(select from pg_publicatio= n_namespace where pnnspid =3D n.oid)); begin
=C2=A0 execute format('= alter publication my_pub add tables in schema %s;',Schemas_To_Add);
= end;$$;

--Then you can see what was generated and go to the subscrib= er side to refresh the subscription and measure time spent to synchronize.<= br>select * from pg_Namespace where nspname ~ 'test_\d+';
select= pnnspid::regnamespace, * from pg_publication_namespace;
select oid::reg= class, * from pg_Class where relnamespace::regnamespace::text ~ 'test_\= d+' and relkind =3D 'r';

--Later just clean what you do.=
drop publication my_pub;

do $$ declare Schema_Drop text; begin=C2=A0 for Schema_Drop in select nspname from pg_Namespace where nspname = ~ 'test_\d+' loop
=C2=A0 =C2=A0 execute format ('drop schema= %s cascade;',Schema_Drop);
=C2=A0 end loop;
end;$$;

regards
Marco= s


--000000000000ac04f606455d6a6a--