PostgreSQL中使用FDW+MV玩转数据同步
mhr18 2024-10-07 10:24 16 浏览 0 评论
一、问题的提出
经常有这样的 case ,业务线开发同学需要 DBA 帮助其在不同的 PostgreSQL 数据库或集群之间同步数据。
数据量较小时,使用 pg_dump 工具或 copy 命令即可完成同步需求。但当数据量较大时,直接同步数据容易引发源 DB 或目标 DB 出现 slow query 或主从同步延迟等问题,容易对数据库的性能及稳定性造成不良影响。
而且无论数据量大小与否,有时候手工或写定时任务同步数据都很容易出错,且目标DB的数据如果恰好有人在使用极易与同步数据的任务相互阻塞。
是否存在其他数据同步方案避免上述问题呢?通过调研加实践,PostgreSQL中运用FDW与Materialized View可以较好的优化上述问题。
二、FDW
2003年,SQL/MED(SQL Management of External Data)作为新规范被添加到 SQL 标准中,它是处理从 SQL 数据库访问远程对象的标准化方法。
PostgreSQL实现了部分的SQL/MED规范,2011年 PostgreSQL 9.1提供了对该规范的只读支持,2013年 PostgreSQL 9.3 增加了对写的支持。SQL/MED 规范的实现,允许我们使用普通 SQL 查询来访问位于 PostgreSQL 之外的数据。
PostgreSQL 通过 FDW(Foreign Data Wrapper),即外部数据包装器来访问外部数据。FDW 是一个可以与外部数据源通信的库,它隐藏了连接到数据源并从中获取数据的细节。
PostgreSQL 官方提供的 FDW 有 postgres_fdw 和 file_fdw ,其他类型的 FDW 由第三方扩展实现。通过不同的 FDW , PostgreSQL 能够访问不同的外部同构/异构数据源,除了 PostgreSQL、Oracle、MySQL 等关系型数据库,还支持访问 MongoDB、Redis、Hive/Hadoop/HDFS、文件等远程数据存储。
无论使用什么类型的FDW,外部数据表只能从外部数据源获取数据,或者在更新命令的情况下传送数据到外部数据源。
在 FDW 出现之前,dblink 也可以实现远端数据库的访问,但 FDW 是一种更现代和更加兼容标准的架构,提供了更为强大的功能。
比如 dblink 无法使用源 DB 表上的 index ,而 FDW 会区分 where 或 join 等限制条件,将能够发送到 remote 端的限制条件发送到 remote 端执行,此时就只需从 remote 端仅取回需要的数据。
PostgreSQL 9.5 支持使用 IMPORT FOREIGN SCHEMA 命令导入整个 schema,PostgreSQL 9.6 已经支持 join 条件的 remote 端下推, PostgreSQL 10 已支持聚合函数的下推。PostgreSQL 14 还增加了使用 FDW 查询 remote Database时的并行执行功能,postgres_fdw 还支持批量插入,使用 IMPORT FOREIGN SCHEMA 命令导入分区表,以及外部表上的 TRUNCATE 命令。
可见 PostgreSQL 对 FDW 的支持越来越完善,功能越来越强大。所以还在使用 PostgreSQL 9.x 的用户,升级至 PostgreSQL 11+,是十分有必要且明智的选择!
FDW同步数据示例:
school_info=# \! hostname
remotehost
--外部数据
school_info=# select * from instructor;
id | name | dept_name | salary
----+------------+-----------+--------
1 | einstein | physics | 95000
2 | wu | finance | 90000
3 | elsaid | history | 60000
4 | katz | comp.sci. | 75000
5 | kim | elec.eng. | 80000
6 | crick | biology | 72000
7 | srinivasan | comp.sci. | 65000
8 | califieri | history | 62000
9 | brandt | comp.sci. | 92000
10 | mozart | music | 40000
11 | gold | physics | 87000
12 | singh | finance | 80000
(12 rows)
school_info_backup=# \! hostname
localhost
--定义一个新的外部服务器
school_info_backup=# create server school foreign data wrapper postgres_fdw options(host 'remotehost',port '5432',dbname 'school_info');
CREATE SERVER
--定义一个用户到一个外部服务器的新映射
school_info_backup=# create user mapping for local_dba server school options(user 'remote_dba',password 'xxxxxx');
CREATE USER MAPPING
--定义一个新的外部表
school_info_backup=# create foreign table instructor_fdw(
id int,
name varchar(20),
dept_name varchar(20),
salary int)
server school options(table_name 'instructor');
CREATE FOREIGN TABLE
--从外部表获取数据,用法和本地表没有区别
school_info_backup=# select * from instructor_fdw;
id | name | dept_name | salary
----+------------+-----------+--------
1 | einstein | physics | 95000
2 | wu | finance | 90000
3 | elsaid | history | 60000
4 | katz | comp.sci. | 75000
5 | kim | elec.eng. | 80000
6 | crick | biology | 72000
7 | srinivasan | comp.sci. | 65000
8 | califieri | history | 62000
9 | brandt | comp.sci. | 92000
10 | mozart | music | 40000
11 | gold | physics | 87000
12 | singh | finance | 80000
(12 rows)
可见,有了 FDW ,可以实现跨 DB ,或跨 DB 集群,甚至跨DBMS(比如Qunar的广告系统通过 Oracle_fdw 实现 PostgreSQL 访问 Oracle 中的数据)的数据同步。
在 PostgreSQL 服务器中外部表不存储数据,只是指向外部数据源的一个链接,数据依旧存储在外部数据源中。在本地库通过访问这张外部表,就可以访问外部数据源中相对应的待同步的表。
使用 FDW 同步数据时,真实的数据依然在源 DB ,主要的同步数据应用场景:
- 目标 DB 需要跨 DB 获取实时数据,必须每次都使用FDW获取最新数据
- 目标 DB 端的应用的 query 的速度不需要像运行在本地表上一样快,且参数不固定,可能涉及到全量原始数据
但是有些不需要获取源DB中实时数据的数据同步场景,尤其统计/报表等数据仓库类的应用,基本是同步T-1的数据即可满足需求,需要某一维度(比如按日期或按用户)的聚合后的数据,且查询的query 频率虽然较高,但是参数和结果在一定时间内相对固定,有没有更优的方案呢?
经过调研,答案是肯定的!
接下来我们就来了解一下开篇提到的 Materialized View 。
三、MV
View(视图)是表示数据库查询结果的虚拟表。当定义一个视图的时候,数据库只存储定义该视图的查询语句,每当查询涉及该视图时,数据库就会将其转换为已存储的查询表达式。因此无论我们何时执行这个查询,视图关系都被重新计算。
MV(Materialized View),即物化视图,是一个其内容已计算并存储的视图。MV将查询结果缓存为一张具体的表,可以不时地从定义视图的实际关系中更新。这使得访问更加有效,但代价是额外的存储和一些可能过期的数据。2013年 PostgreSQL 9.3 提供了对物化视图的支持。
MV 带来一个问题,就是它们必须能够在视图定义所使用的数据变化时保持更新。这种保持MV与原始数据同步更新的任务称作视图维护( View Maintenance )。不同的数据库系统采取不同的视图维护策略,PostgreSQL 中使用 REFRESH MATERIALIZED VIEW语句更新MV。
REFRESH MATERIALIZED VIEW table_name ;
不指定 CONCURRENTLY 选项时,使用更少的资源并且完成地更迅速,但是会锁定其他应用从该物化视图读数据的连接。
REFRESH MATERIALIZED VIEW CONCURRENTLY table_name ;
使用 PostgreSQL 9.4 新支持的 CONCURRENTLY 选项可以在 refresh MV 时不阻塞对该物化视图的查询 query ,虽然 refresh 的速度会变的稍慢,但是这种以时间来换取查询锁还是值得的。
物化视图使用示例:
school_info=# select * from department;
dept_name | building | budget
-----------+----------+--------
comp.sci. | taylor | 100000
biology | watson | 90000
elec.eng. | taylor | 85000
music | packard | 80000
finance | painter | 120000
(5 rows)
--定义一个物化视图
school_info=# create materialized view department_mv as select * from department ;
SELECT 5
school_info=# insert into department values ('history','painter',50000);
INSERT 0 1
school_info=# select * from department_mv ;
dept_name | building | budget
-----------+----------+--------
comp.sci. | taylor | 100000
biology | watson | 90000
elec.eng. | taylor | 85000
music | packard | 80000
finance | painter | 120000
(5 rows)
--刷新物化视图
school_info=# refresh materialized view department_mv;
REFRESH MATERIALIZED VIEW
school_info=# select * from department_mv;
dept_name | building | budget
-----------+----------+--------
comp.sci. | taylor | 100000
biology | watson | 90000
elec.eng. | taylor | 85000
music | packard | 80000
finance | painter | 120000
history | painter | 50000
(6 rows)
school_info=# insert into department values ('physics','watson',70000);
INSERT 0 1
school_info=# select * from department_mv ;
dept_name | building | budget
-----------+----------+--------
comp.sci. | taylor | 100000
biology | watson | 90000
elec.eng. | taylor | 85000
music | packard | 80000
finance | painter | 120000
history | painter | 50000
(6 rows)
--并行刷新物化视图
school_info=# refresh materialized view concurrently department_mv;
REFRESH MATERIALIZED VIEW
--在PostgreSQL 14中,REFRESH MATERIALIZED VIEW 命令也可以使用并行查询
school_info=# select * from department_mv ;
dept_name | building | budget
-----------+----------+--------
comp.sci. | taylor | 100000
biology | watson | 90000
elec.eng. | taylor | 85000
music | packard | 80000
finance | painter | 120000
history | painter | 50000
physics | watson | 70000
(7 rows)
四、FDW+MV
如何运用 FDW 与 Materialized View 二者的组合拳来解决前述场景的数据同步问题呢?
数据同步方案的架构图如下:
在该方案中,通过外部数据包装器postgres_fdw访问外部数据,左侧是一个外部数据源,包含一张待同步的表(instructor),右侧是在本地创建的一张外部表(instructor_fdw)。
方案具体示例如下:
school_info_backup=# \dew+
List of foreign-data wrappers
Name | Owner | Handler | Validator | Access privileges | FDW options | Description
--------------+----------+----------------------+------------------------+-------------------+-------------+-------------
postgres_fdw | postgres | postgres_fdw_handler | postgres_fdw_validator | | |
(1 row)
school_info_backup=# \des+
List of foreign servers
Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
--------+----------+----------------------+---------------------+------+---------+----------------------------------------------------------------------------+-------------
school | postgres | postgres_fdw | postgres=U/postgres+| | | (host 'remotehost', port '5432', dbname 'school_info') |
| | | local_dba=U/postgres| | | |
(1 row)
school_info_backup=# \deu+
List of user mappings
Server | User name | FDW options
--------+-----------+--------------------------------------------------------------------
school | local_dba | ("user" 'remote_dba', password 'xxxxxx')
(1 row)
school_info_backup=# \det+
List of foreign tables
Schema | Table | Server | FDW options | Description
--------+----------------+--------+---------------------------+-------------
public | instructor_fdw | school | (table_name 'instructor') |
(1 row)
school_info_backup=# \dm+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+---------------+-------------------+----------+-------+-------------
public | instructor_mv | materialized view | local_dba| 32 kB |
(1 row)
可见,在外部表 instructor_fdw 上定义一个 MV : instructor_mv ,即将符合业务需求的数据以 MV 的形式周期性持久化在本地库中,至此数据同步的目标已实现。
此后日常的数据同步将变得很简单,只需要定时 refresh MV 即可。
五、小结
现简要对比总结一下各种数据同步的方案的适用主要场景:
方案 | 适用场景 |
pg_dump&&reload |
|
FDW |
|
FDW+MV |
|
通过运用 PostgreSQL 中独有的 FDW 与 MV ,为特定场景下的数据同步提供了一种全新的方案, FDW+MV 二者灵活组合运用,效果更佳!
实例
Qunar有很多运用FDW+MV组合拳进行数据同步的实例,下面看一个某业务线订单相关数据同步到产品实例的实践案例。
--源DB
order_product=# \! hostname
remotehost
order_product=# \dt+ order_detail
List of relations
Schema | Name | Type | Owner | Size | Description
--------+--------------+-------+----------+-------+-------------
public | order_detail | table | postgres | 20 GB |
(1 row)
--目标DB
b2c_product=# \des
List of foreign servers
Name | Owner | Foreign-data wrapper
---------------------+-------+----------------------
remote_order_server | pgdba | postgres_fdw
(1 row)
b2c_product=# \deu
List of user mappings
Server | User name
---------------------+-----------
remote_order_server | pgdba
(1 row)
b2c_product=# \det+ order_detail
List of foreign tables
Schema | Table | Server | FDW options | Description
--------+--------------+---------------------+---------------------------------------------------+-------------------
public | order_detail | remote_order_server | (schema_name 'public', table_name 'order_detail') | 远程订单库订单明细外部表
(1 row)
b2c_product=# \dm+ order_product_report
List of relations
Schema | Name | Type | Owner | Size | Description
--------+----------------------+-------------------+-------+---------+-------------
public | order_product_report | materialized view | pgdba | 0 bytes | 产品当年销量物化视图
(1 row)
b2c_product=# \d+ order_product_report
Materialized view "public.order_product_report"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
------------+---------+-----------+----------+---------+---------+--------------+-------------
product_id | integer | | | | plain | |
count | bigint | | | | plain | |
View definition:
SELECT order_detail.product_id,
count(1) AS count
FROM order_detail
WHERE order_detail.create_time >= '2021-01-01 00:00:00+08'::timestamp with time zone
GROUP BY order_detail.product_id;
b2c_product=# refresh materialized view order_product_report;
REFRESH MATERIALIZED VIEW
Time: 542.331 ms
--refresh materialized view CONCURRENTLY 必须有unique index
b2c_product=# refresh materialized view CONCURRENTLY order_product_report;
ERROR: cannot refresh materialized view "public.order_product_report" concurrently
HINT: Create a unique index with no WHERE clause on one or more columns of the materialized view.
Time: 0.375 ms
b2c_product=# create unique index CONCURRENTLY ON order_product_report(product_id);
CREATE INDEX
Time: 25.973 ms
b2c_product=# refresh materialized view CONCURRENTLY order_product_report;
REFRESH MATERIALIZED VIEW
Time: 681.510 ms
b2c_product=# select count(1) from order_product_report;
count
-------
666888
(1 row)
Time: 4.488 ms
--查询当年销量大于100单的产品
b2c_product=# select title
from product p
join order_product_report o
on p.id = o.product_id
where count >100 and p.title ~ '北京'
order by random()
limit 1;
title
---------------------------------------
北京八达岭长城+颐和园+鸟巢纯玩品质跟团一日游
(1 row)
Time: 11.478 ms
可见,源 DB 中 20 GB 的表,通过 FDW +MV ,完美的将所需的外部远程实例订单库中的数据正确且高效的同步至产品库中。
与传统的数据同步方案相比,在性能和可维护性等方面具有非常强大的优势!
相关推荐
- 使用 Docker 部署 Java 项目(通俗易懂)
-
前言:搜索镜像的网站(推荐):DockerDocs1、下载与配置Docker1.1docker下载(这里使用的是Ubuntu,Centos命令可能有不同)以下命令,默认不是root用户操作,...
- Spring Boot 3.3.5 + CRaC:从冷启动到秒级响应的架构实践与踩坑实录
-
去年,我们团队负责的电商订单系统因扩容需求需在10分钟内启动200个Pod实例。当运维组按下扩容按钮时,传统SpringBoot应用的冷启动耗时(平均8.7秒)直接导致流量洪峰期出现30%的请求超时...
- 《github精选系列》——SpringBoot 全家桶
-
1简单总结1SpringBoot全家桶简介2项目简介3子项目列表4环境5运行6后续计划7问题反馈gitee地址:https://gitee.com/yidao620/springbo...
- Nacos简介—1.Nacos使用简介
-
大纲1.Nacos的在服务注册中心+配置中心中的应用2.Nacos2.x最新版本下载与目录结构3.Nacos2.x的数据库存储与日志存储4.Nacos2.x服务端的startup.sh启动脚...
- spring-ai ollama小试牛刀
-
序本文主要展示下spring-aiollama的使用示例pom.xml<dependency><groupId>org.springframework.ai<...
- SpringCloud系列——10Spring Cloud Gateway网关
-
学习目标Gateway是什么?它有什么作用?Gateway中的断言使用Gateway中的过滤器使用Gateway中的路由使用第1章网关1.1网关的概念简单来说,网关就是一个网络连接到另外一个网络的...
- Spring Boot 自动装配原理剖析
-
前言在这瞬息万变的技术领域,比了解技术的使用方法更重要的是了解其原理及应用背景。以往我们使用SpringMVC来构建一个项目需要很多基础操作:添加很多jar,配置web.xml,配置Spr...
- 疯了!Spring 再官宣惊天大漏洞
-
Spring官宣高危漏洞大家好,我是栈长。前几天爆出来的Spring漏洞,刚修复完又来?今天愚人节来了,这是和大家开玩笑吗?不是的,我也是猝不及防!这个玩笑也开的太大了!!你之前看到的这个漏洞已...
- 「架构师必备」基于SpringCloud的SaaS型微服务脚手架
-
简介基于SpringCloud(Hoxton.SR1)+SpringBoot(2.2.4.RELEASE)的SaaS型微服务脚手架,具备用户管理、资源权限管理、网关统一鉴权、Xss防跨站攻击、...
- SpringCloud分布式框架&分布式事务&分布式锁
-
总结本文承接上一篇SpringCloud分布式框架实践之后,进一步实践分布式事务与分布式锁,其中分布式事务主要是基于Seata的AT模式进行强一致性,基于RocketMQ事务消息进行最终一致性,分布式...
- SpringBoot全家桶:23篇博客加23个可运行项目让你对它了如指掌
-
SpringBoot现在已经成为Java开发领域的一颗璀璨明珠,它本身是包容万象的,可以跟各种技术集成。本项目对目前Web开发中常用的各个技术,通过和SpringBoot的集成,并且对各种技术通...
- 开发好物推荐12之分布式锁redisson-sb
-
前言springboot开发现在基本都是分布式环境,分布式环境下分布式锁的使用必不可少,主流分布式锁主要包括数据库锁,redis锁,还有zookepper实现的分布式锁,其中最实用的还是Redis分...
- 拥抱Kubernetes,再见了Spring Cloud
-
相信很多开发者在熟悉微服务工作后,才发现:以为用SpringCloud已经成功打造了微服务架构帝国,殊不知引入了k8s后,却和CloudNative的生态发展脱轨。从2013年的...
- Zabbix/J监控框架和Spring框架的整合方法
-
Zabbix/J是一个Java版本的系统监控框架,它可以完美地兼容于Zabbix监控系统,使得开发、运维等技术人员能够对整个业务系统的基础设施、应用软件/中间件和业务逻辑进行全方位的分层监控。Spri...
- SpringBoot+JWT+Shiro+Mybatis实现Restful快速开发后端脚手架
-
作者:lywJee来源:cnblogs.com/lywJ/p/11252064.html一、背景前后端分离已经成为互联网项目开发标准,它会为以后的大型分布式架构打下基础。SpringBoot使编码配置...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)