ALICloudDB for PostgreSQL 试用报告 - 4 水平分库 之 节点扩展

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介:
RDS现在还欠缺一个功能,就是数据库克隆,你可以这么理解,给现有的数据库创建STANDBY,然后将这个STANDBY激活,就完成了对数据库的克隆。
为什么我们需要数据库克隆功能呢?
这会使得数据库的扩容变得非常简单,比如我们这里的应用场景,如果要将16个RDS,变成32个RDS,那么克隆无疑是最好的办法。因为不需要做逻辑数据迁移的事情,只需要删除不需要的数据库,以及调整plproxy的cluster配置即可。
我们先假设RDS有创建STANDBYD的功能(相信未来会增加),看看如何来实现RDS的扩容。

假设主RDS1包含db0,db16两个库,现在要拆分为两个RDS,RDS1(db0), RDS2(db16),分别包含db0和db16。
1. 为需要拆分的主RDS创建standby RDS, 确认流复制正常,确认standby RDS到达catchup状态。

2. 配置密码文件~/.pgpass,包含克隆库的认证信息。

3. 调整plproxy cluster配置。使用只读事务连接需要迁移的数据库,避免写操作带来的主备不一致。
例如 : 
alter server rds_pg_cluster options (set p16 'host=old_rds_hostname dbname=db16 port=3433 user=digoal keepalives_idle=30 keepalives_interval=10 keepalives_count=10 application_name=16 options=''-c default_transaction_read_only=true'' ');

4. 确认主RDS需迁移的库(db16)没有连接,确认standby处于catchup状态。

5. 激活standby。

6. 调整plproxy cluster配置。原连接RDS1(db16),修改为RDS2(db16)。
例如 : 
alter server rds_pg_cluster options (set p16 'host=new_rds_hostname dbname=db16 port=3433 user=digoal keepalives_idle=30 keepalives_interval=10 keepalives_count=10 application_name=16');

7. 删除主RDS节点已迁移的数据库(db16),删除standby节点多余的数据库(db0)。

循环1-7,将所有RDS拆分。

回到现实,现实是目前RDS没有提供克隆功能。那么我们需要人工实现数据迁移,需迁移的东西还挺多,包括表,视图,函数,。。。。。可以用pg_dump,但是怎么实现增量呢?可以通过PostgreSQL的logical decoding来实现增量复制。现在阿里云RDS PG已经支持了逻辑复制的功能。

下面是人力扩容的例子:
源:
postgres=> select datname,pg_database_size(oid)/1024/1024 from pg_database;
  datname  | ?column? 
-----------+----------
 template1 |        6
 template0 |        6
 postgres  |     3618
 digoal    |        6
 db7       |      179
 db23      |      179
(6 rows)

目标,把db23迁移到以下RDS:
postgres=> select datname,pg_database_size(oid)/1024/1024 from pg_database;
  datname  | ?column? 
-----------+----------
 template1 |        6
 template0 |        6
 postgres  |        6
 digoal    |        6
 db8       |      179
 db24      |      179
(6 rows)

在目标RDS创建db23数据库:
postgres=> create database db23;
CREATE DATABASE
postgres=> \c db23
psql (9.4.3, server 9.4.1)
You are now connected to database "db23" as user "digoal".
创建schema和需要迁移的函数:
db23=> create schema digoal;
CREATE SCHEMA
db23=> CREATE OR REPLACE FUNCTION digoal.dy(sql text)
db23->      RETURNS SETOF record
db23->      LANGUAGE plpgsql
db23->      STRICT
db23->     AS $function$
db23$>       declare
db23$>         rec record;
db23$>       begin
db23$>         for rec in execute sql loop
db23$>           return next rec;
db23$>         end loop;
db23$>         return;
db23$>       end;
db23$>     $function$;
CREATE FUNCTION
db23=> CREATE OR REPLACE FUNCTION digoal.dy_ddl(sql text)
db23->      RETURNS VOID
db23->      LANGUAGE plpgsql
db23->      STRICT
db23->     AS $function$
db23$>       declare
db23$>       begin
db23$>         execute sql;
db23$>         return;
db23$>       exception when others then return;
db23$>       end;
db23$>     $function$;
CREATE FUNCTION
准备需要迁移的数据的外部表:
db23=> create extension postgres_fdw;
CREATE EXTENSION

CREATE SERVER db23
        FOREIGN DATA WRAPPER postgres_fdw
        OPTIONS (host 'old.pg.rds.aliyuncs.com', port '3433', dbname 'db23');

CREATE USER MAPPING FOR digoal  --  locale user
        SERVER db23
        OPTIONS (user 'digoal', password 'digoal');  --  remote user/password

CREATE FOREIGN TABLE digoal.ft_userinfo (
        dbid int default 23,
        userid int,
    info text
)
        SERVER db23
        OPTIONS (schema_name 'digoal', table_name 'userinfo');

CREATE FOREIGN TABLE digoal.ft_session (
        dbid int default 23,
        userid int,
    last_login timestamp without time zone
)
        SERVER db23
        OPTIONS (schema_name 'digoal', table_name 'session');

CREATE FOREIGN TABLE digoal.ft_tbl_small (
        userid int,
    info text
)
        SERVER db23
        OPTIONS (schema_name 'digoal', table_name 'tbl_small'); 

CREATE FOREIGN TABLE digoal.ft_login_log (
        dbid int default 23,
        userid int,
        db_user name,
        client_addr inet,
        client_port int,
        server_addr inet,
        server_port int,
        login_time timestamp without time zone
)
        SERVER db23
        OPTIONS (schema_name 'digoal', table_name 'login_log');

创建物化视图,日志表(日志数据不迁移)
CREATE MATERIALIZED VIEW digoal.userinfo (
        dbid ,
        userid ,
    info 
) as select * from digoal.ft_userinfo;
set maintenance_work_mem='10GB';  --  超出RDS内存限制,可能会被杀掉
create unique index pk_userinfo on digoal.userinfo (userid);

CREATE MATERIALIZED VIEW digoal.session (
        dbid ,
        userid ,
    last_login 
) as select * from digoal.ft_session;
set maintenance_work_mem='10GB';  --  超出RDS内存限制,可能会被杀掉
create unique index pk_session on digoal.session (userid);

CREATE MATERIALIZED VIEW digoal.tbl_small (
        userid ,
    info 
) as select * from digoal.ft_tbl_small;
set maintenance_work_mem='10GB';  --  超出RDS内存限制,可能会被杀掉
create unique index pk_tbl_small on digoal.tbl_small (userid);

CREATE TABLE digoal.login_log (
        dbid int default 23,
        userid int,
        db_user name,
        client_addr inet,
        client_port int,
        server_addr inet,
        server_port int,
        login_time timestamp without time zone
);

创建需要迁移的函数:
CREATE OR REPLACE FUNCTION digoal.dy_generate_test_ddl()
     RETURNS VOID
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
        node int;
    sql text;
      begin
        select application_name::int into node from pg_stat_activity where pid=pg_backend_pid();
    sql := $a$insert into digoal.userinfo select $a$||node||$a$,generate_series($a$||node||$a$,32000000,32)$a$;
    execute sql;
    sql := $a$insert into digoal.session select dbid,userid from digoal.userinfo$a$;
    execute sql;
        return;
      exception when others then return;
      end;
    $function$;

CREATE OR REPLACE FUNCTION digoal.query_pk(IN i_userid int, OUT dbid int, OUT userid int, OUT info text)
     RETURNS record
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        select t.dbid,t.userid,t.info into dbid,userid,info from digoal.userinfo t where t.userid=i_userid;
        return;
      end;
    $function$;

CREATE OR REPLACE FUNCTION digoal.insert_log(IN i_userid int)
     RETURNS void
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        insert into digoal.login_log (userid,db_user,client_addr,client_port,server_addr,server_port,login_time)
        values (i_userid,current_user,inet_client_addr(),inet_client_port(),inet_server_addr(),inet_server_port(),now());
      end;
    $function$;

CREATE OR REPLACE FUNCTION digoal.query_insert(IN i_userid int, OUT dbid int, OUT userid int, OUT info text)
     RETURNS record
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        select t.dbid,t.userid,t.info into dbid,userid,info from digoal.userinfo t where t.userid=i_userid;
        insert into digoal.login_log (userid,db_user,client_addr,client_port,server_addr,server_port,login_time)
        values (i_userid,current_user,inet_client_addr(),inet_client_port(),inet_server_addr(),inet_server_port(),now());
        return;
      end;
    $function$;


CREATE OR REPLACE FUNCTION digoal.update_pk(IN i_userid int)
     RETURNS void
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        update digoal.session t set last_login=now() where t.userid=i_userid;
      end;
    $function$;

CREATE OR REPLACE FUNCTION digoal.query_update_insert(IN i_userid int, OUT dbid int, OUT userid int, OUT info text)
     RETURNS record
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        select t.dbid,t.userid,t.info into dbid,userid,info from digoal.userinfo t where t.userid=i_userid;
        insert into digoal.login_log (userid,db_user,client_addr,client_port,server_addr,server_port,login_time)
        values (i_userid,current_user,inet_client_addr(),inet_client_port(),inet_server_addr(),inet_server_port(),now());
        update digoal.session t set last_login=now() where t.userid=i_userid;
        return;
      end;
    $function$;

CREATE OR REPLACE FUNCTION digoal.query_smalltbl(IN i_userid int, OUT userid int, OUT info text)
     RETURNS record
     LANGUAGE plpgsql
     STRICT
    AS $function$
      declare
      begin
        select t.userid,t.info into userid,info from digoal.tbl_small t where t.userid=i_userid;
        return;
      end;
    $function$;


PL/Proxy节点操作如下:
配置.pgpass, 新增:
new.pg.rds.aliyuncs.com:3433:*:digoal:digoal
使用default_transaction_read_only默认读事务,屏蔽写操作.
在迁移时间段内,用户可以正常执行读请求,但是如果执行写请求会失败,这样确保数据迁移的一致性,同时降低迁移过程对业务的影响。
postgres# alter server rds_pg_cluster options (set p23 'host=old.pg.rds.aliyuncs.com dbname=db23 port=3433 user=digoal keepalives_idle=30 keepalives_interval=10 keepalives_count=10 application_name=23 options=''-c default_transaction_read_only=true''');    -- 注意里面是两个单引号
测试读正常,写失败:
postgres=# select query_pk(23);
 query_pk 
----------
 (23,23,)
(1 row)

postgres=# select insert_log(23);
ERROR:  public.insert_log(1): [db23] REMOTE ERROR: cannot execute INSERT in a read-only transaction
CONTEXT:  Remote context: SQL statement "insert into digoal.login_log (userid,db_user,client_addr,client_port,server_addr,server_port,login_time)
    values (i_userid,current_user,inet_client_addr(),inet_client_port(),inet_server_addr(),inet_server_port(),now())"
PL/pgSQL function insert_log(integer) line 4 at SQL statement

new RDS执行操作如下,刷新物化视图:
refresh materialized view CONCURRENTLY digoal.userinfo;
REFRESH MATERIALIZED VIEW
Time: 10953.220 ms
refresh materialized view CONCURRENTLY digoal.session;
REFRESH MATERIALIZED VIEW
Time: 11013.860 ms
refresh materialized view CONCURRENTLY digoal.tbl_small;
REFRESH MATERIALIZED VIEW
Time: 5084.118 ms

这里卡住,因为要修改数据字典需要超级用户。而RDS提供的用户是普通用户,无法修改数据字典(虽然有风险,这里只为演示)。所以这样迁移行不通。
如果是超级用户,那么操作请参考我前期写的BLOG
http://blog.163.com/digoal@126/blog/static/163877040201559105235803/

为了演示下去,我只能选择全量迁移。(其他增量方法也有,本文不演示)
db23=> drop materialized view digoal.session ;
DROP MATERIALIZED VIEW
Time: 16.528 ms
db23=> drop materialized view digoal.userinfo;
DROP MATERIALIZED VIEW
Time: 15.781 ms
db23=> drop materialized view digoal.tbl_small;
DROP MATERIALIZED VIEW
Time: 9.458 ms

为了提高迁移速度,用了一些手段。
set synchronous_commit=off;
set maintenance_work_mem='10GB';  --  超出RDS内存限制,可能会被杀掉
CREATE TABLE digoal.userinfo (
        dbid int default 23,
        userid int,
    info text
) with (autovacuum_enabled=off, toast.autovacuum_enabled=off);

CREATE TABLE digoal.session (
        dbid int default 23,
        userid int,
    last_login timestamp without time zone
) with (autovacuum_enabled=off, toast.autovacuum_enabled=off);

CREATE TABLE digoal.tbl_small (
        userid int,
    info text
) with (autovacuum_enabled=off, toast.autovacuum_enabled=off);

insert into digoal.userinfo select * from digoal.ft_userinfo;
INSERT 0 1000000
Time: 45290.701 ms
insert into digoal.session select * from digoal.ft_session;
INSERT 0 1000000
Time: 42212.278 ms
insert into digoal.tbl_small select * from digoal.ft_tbl_small;
INSERT 0 500000
Time: 22885.456 ms

alter table digoal.userinfo add constraint pk_userinfo primary key (userid);
ALTER TABLE
Time: 16962.174 ms
alter table digoal.session add constraint pk_session primary key (userid);
ALTER TABLE
Time: 20809.422 ms
alter table digoal.tbl_small add constraint pk_tbl_small primary key (userid);
ALTER TABLE
Time: 17484.201 ms

vacuum analyze digoal.userinfo;
Time: 65.790 ms
vacuum analyze digoal.session;
Time: 65.427 ms
vacuum analyze digoal.tbl_small;
Time: 45.453 ms

alter table digoal.userinfo set (autovacuum_enabled=on, toast.autovacuum_enabled=on);
alter table digoal.session set (autovacuum_enabled=on, toast.autovacuum_enabled=on);
alter table digoal.tbl_small set (autovacuum_enabled=on, toast.autovacuum_enabled=on);


PL/Proxy,  修改集群,db23的目标主机为新的RDS,并且开放读写权限:
postgres# alter server rds_pg_cluster options (set p23 'host=old.pg.rds.aliyuncs.com dbname=db23 port=3433 user=digoal keepalives_idle=30 keepalives_interval=10 keepalives_count=10 application_name=23');


删除老RDS上的db23.
psql -h old.pg.rds.aliyuncs.com -p 3433 -U digoal postgres
postgres=> drop database db23;
DROP DATABASE

测试plproxy分发是否正常分发到新的数据库:
postgres=# select * from query_pk(23);
 dbid | userid | info 
------+--------+------
   23 |     23 | 
(1 row)

vi test.sql
\setrandom id 1 32000000
select insert_log(:id);
pgbench -M prepared -n -r -f ./test.sql -P 1 -c 30 -j 30 -T 30
progress: 1.0 s, 7853.2 tps, lat 3.340 ms stddev 6.056
progress: 2.0 s, 10766.4 tps, lat 2.432 ms stddev 5.433
progress: 3.0 s, 11395.0 tps, lat 2.277 ms stddev 4.590
progress: 4.0 s, 11622.1 tps, lat 2.216 ms stddev 4.493
progress: 5.0 s, 10519.9 tps, lat 2.454 ms stddev 5.600
progress: 6.0 s, 11153.4 tps, lat 2.317 ms stddev 4.795
progress: 7.0 s, 11474.3 tps, lat 2.312 ms stddev 4.802
progress: 8.0 s, 11398.5 tps, lat 2.253 ms stddev 4.308
progress: 9.0 s, 12106.7 tps, lat 2.174 ms stddev 3.302
progress: 10.0 s, 12567.8 tps, lat 2.068 ms stddev 2.075

连接到新的db23:
psql -h new_rds -p 3433 -U digoal
postgres=> \c db23
psql (9.4.3, server 9.4.1)
You are now connected to database "db23" as user "digoal".
db23=> select count(*) from login_log ;
 count 
-------
 10547
(1 row)

[补充]
1. 如果实际使用内存超出了RDS内存限制,会被杀掉。
postgres=> set maintenance_work_mem='10GB';
SET
postgres=> alter table session add constraint pk_session primary key(userid);
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!> \q

[参考]
1. http://blog.163.com/digoal@126/blog/static/163877040201559105235803/
2. http://www.postgresql.org/docs/9.4/static/libpq-connect.html
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
12月前
|
存储 NoSQL 关系型数据库
PostgreSQL列存扩展hydra简单测试
Hydra是一款PostgreSQL的扩展,为PostgreSQL增加了列存引擎,使得PostgreSQL的olap性能大幅提升,本文介绍Hydra基本的使用方法。
|
12月前
|
存储 安全 关系型数据库
PostgreSQL物化视图增量更新扩展 -- pg_ivm
PostgreSQL不支持物化视图增量更新,需要定期执行REFRESH MATERIALIZED VIEW命令刷新物化视图。Incremental View Maintenance (IVM)是一种使物化视图保持最新的方法,其中只计算增量更改并将其应用于视图,而不是REFRESH MATERIALIZED VIEW那样从头开始重新计算内容。当只更改视图的一小部分时,IVM可以比重新计算更高效地更新物化视图。
|
1月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
11月前
|
关系型数据库 PostgreSQL
PostgreSQL pg_orphaned扩展
由于种种原因,PostgreSQL可能会产生一些孤儿文件,这些文件会占用磁盘空间,手工查找费时费力还容易出错,pg_orphaned扩展很好的解决了这个问题。
|
12月前
|
SQL 关系型数据库 Go
《增强你的PostgreSQL:最佳扩展和插件推荐》
《增强你的PostgreSQL:最佳扩展和插件推荐》
854 0
|
5月前
|
SQL 关系型数据库 C语言
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
82 0
|
5月前
|
SQL 关系型数据库 数据库
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
151 0
|
5月前
|
SQL 关系型数据库 编译器
PostgreSQL SQL扩展 ---- C语言函数(二)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
5月前
|
SQL 存储 关系型数据库
PostgreSQL SQL扩展 ---- C语言函数(一)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
5月前
|
弹性计算 关系型数据库 RDS
试用的ECS、RDS可以续期吗?
试用的ECS、RDS可以续期吗?
53 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 下一篇
    无影云桌面