关于PostgreSQL同步复制下主从切换时的数据丢失问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 背景 MySQL 5.7对半同步复制做了一个增强,增加了一个rpl_semi_sync_master_wait_point参数控制master什么时候等待slave的应答。

背景

MySQL 5.7对半同步复制做了一个增强,增加了一个rpl_semi_sync_master_wait_point参数控制master什么时候等待slave的应答。
默认是AFTER_SYNC,即事务提交时,master按照下面的顺序执行
1. InnoDB prepare
2. 写事务到binlog以及发送到slave
3. binlog刷盘
4. 等待slave的接受应答
5. InnoDB commit
6. 返回客户端

rpl_semi_sync_master_wait_point的另外一个取值是AFTER_COMMIT,这也是MySQL 5.6及之前版本的行为
1. InnoDB prepare
2. 写事务到binlog以及发送到slave
3. binlog刷盘
4. InnoDB commit
5. 等待slave的接受应答
6. 返回客户端

简言之,AFTER_SYNC和AFTER_COMMIT的区别就是交换了下4和5的顺序。
这么做主要可以回避MySQL5.6 及以前的版本中存在的这样一个问题:
在事务已经提交到存储引擎但还没有收到slave的应答的期间,其它会话可以看到这个更新,如果此时master宕机,
slave升级为新的master后,可能会丢失这个事务,那么对于那些已经看到过这个事务的修改的会话来说就是发生了“数据丢失”。
详细参考:http://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html

那么,对于PostgreSQL来说,有没有这个问题呢?

PostgreSQL的同步复制

PostgreSQL的同步复制过程大体如下:
1. 写WAL记录到本地WAL文件和slave
2. 刷WAL记录到本地WAL文件
3. 更新CLOG
4. 等待slave的接受应答
5. 返回客户端

这和MySQL5.6或者是5.7的AFTER_COMMIT类似(参考函数RecordTransactionCommit()),所以也应该存在丢失数据的问题。那么事实是不是这样呢?

实验验证

经过测试验证,确实存在前面担心的问题。测试过程如下:

1. 配置基于Pacemaker+Corosync的1主2从集群

步骤参考 http://clusterlabs.org/wiki/PgSQL_Replicated_Cluster

2. 同时执行下面2个测试脚本,验证主从切换时有没有数据丢失

failover_test.py
循环更新一个字段,每次加1。发生故障后,重建连接,等连接成功后,检查新master中的数值和最后一次更新的数据是否一致。这个数值等于最后一次成功的更新或最后一次失败的更新中的任意一个都说明没有丢数据。

点击(此处)折叠或打开

  1. #!/usr/bin/python
  2. import psycopg2
  3. import time

  4. url="host=192.168.1.246 dbname=postgres user=postgres"
  5. conn = psycopg2.connect(url)
  6. conn.autocommit=True

  7. cur = conn.cursor()
  8. cur.execute("CREATE TABLE IF NOT EXISTS pgsql_ha_test(id serial PRIMARY KEY, num integer);")
  9. cur.execute("truncate pgsql_ha_test;")
  10. cur.execute("insert into pgsql_ha_test values(1,0);")


  11. print "Update process had started,please kill the master..."
  12. i=0
  13. num=-1
  14. try:
  15.     while True:
  16.         i+=1
  17.         cur.execute("UPDATE pgsql_ha_test set num = %s where id=1",(i,))
  18.         num=i
  19.         if i % 1000 == 0:
  20.             print time.time()," current num=",num

  21. except psycopg2.Error as e:
  22.     print time.time(),"The master has down, last num:",num
  23.     print e.pgerror
  24.     conn.close()

  25. time1= time.time()
  26. connect_success = False
  27. while connect_success == False:
  28.     try:
  29.         conn = psycopg2.connect(url)
  30.         conn.autocommit=True
  31.         connect_success=True
  32.     except psycopg2.Error as e:
  33.         pass

  34. time2= time.time()
  35. print time.time()," connect success after %f second"%(time2-time1)

  36. cur = conn.cursor()
  37. cur.execute("select num from pgsql_ha_test where id=1;")
  38. newnum = cur.fetchone()[0]
  39. print "current num:",newnum

  40. if not (newnum==num or newnum==num+1):
  41.     print "NG"
  42.     exit(1)

  43. cur.close()
  44. conn.close()
  45. print "OK"

failover_test2.py
循环读取failover_test.py更新的值。发生故障后,重建连接,等连接成功后,比较新master中的数值和最后一次成功读到的值。新值大于或等于最后一次成功读到的值都说明没有丢数据。

点击(此处)折叠或打开

  1. #!/usr/bin/python
  2. import psycopg2
  3. import time

  4. url="host=192.168.1.246 dbname=postgres user=postgres"
  5. conn = psycopg2.connect(url)
  6. conn.autocommit=True

  7. cur = conn.cursor()

  8. print "Read process had started..."
  9. i=0
  10. num=-1
  11. try:
  12.     while True:
  13.         i+=1
  14.         cur.execute("select num from pgsql_ha_test where id=1;")
  15.         num = cur.fetchone()[0]
  16.         if i % 10000 == 0:
  17.             print time.time()," current num=",num

  18. except psycopg2.Error as e:
  19.     print time.time()," The master has down, last num=",num
  20.     print e.pgerror
  21.     conn.close()

  22. time1= time.time()
  23. connect_success = False
  24. while connect_success == False:
  25.     try:
  26.         conn = psycopg2.connect(url)
  27.         conn.autocommit=True
  28.         connect_success=True
  29.     except psycopg2.Error as e:
  30.         time.sleep(1)

  31. time2= time.time()
  32. print time.time()," connect success after %f second"%(time2-time1)

  33. cur = conn.cursor()
  34. cur.execute("select num from pgsql_ha_test where id=1;")
  35. newnum = cur.fetchone()[0]
  36. print time.time()," current num:",newnum

  37. if newnum num:
  38.     print "NG: Data Lost!"
  39.     exit(1)

  40. cur.close()
  41. conn.close()
  42. print "OK"

3. 杀掉master上的postgres进程,模拟故障触发主从切换

点击(此处)折叠或打开

  1. [root@node73 ~]# ps -ef|grep postgres
  2. root 1228 25775 0 14:09 pts/2 00:00:00 su - postgres
  3. postgres 1229 1228 0 14:09 pts/2 00:00:00 -bash
  4. postgres 3339 31673 17 14:10 ? 00:00:07 postgres: postgres postgres 192.168.1.245(41991) UPDATE waiting for 1/FE556260
  5. postgres 4105 31673 41 14:10 ? 00:00:11 postgres: postgres postgres 192.168.1.245(41994) idle
  6. root 5612 25754 0 14:11 pts/1 00:00:00 grep postgres
  7. postgres 9168 31673 0 13:49 ? 00:00:05 postgres: wal sender process postgres 192.168.1.248(53883) streaming 1/FE556260
  8. postgres 24589 31673 0 13:57 ? 00:00:03 postgres: wal sender process postgres 192.168.1.245(41904) streaming 1/FE5562E8
  9. postgres 31673 1 0 13:45 ? 00:00:00 /opt/PostgreSQL/9.4.5/bin/postgres -D /data/pgsql -c config_file=/data/pgsql//postgresql.conf -p 5432
  10. postgres 31694 31673 0 13:45 ? 00:00:00 postgres: logger process
  11. postgres 31706 31673 0 13:45 ? 00:00:00 postgres: checkpointer process
  12. postgres 31707 31673 0 13:45 ? 00:00:00 postgres: writer process
  13. postgres 31708 31673 0 13:45 ? 00:00:00 postgres: wal writer process
  14. postgres 31709 31673 0 13:45 ? 00:00:00 postgres: autovacuum launcher process
  15. postgres 31710 31673 0 13:45 ? 00:00:00 postgres: stats collector process
  16. [root@n73 ~]# kill -9 31673
  17. [root@node73 ~]# ps -ef|grep postgres
  18. root 1228 25775 0 14:09 pts/2 00:00:00 su - postgres
  19. postgres 1229 1228 0 14:09 pts/2 00:00:00 -bash
  20. postgres 4105 1 38 14:10 ? 00:00:18 postgres: postgres postgres 192.168.1.245(41994) idle
  21. root 7017 25754 0 14:11 pts/1 00:00:00 grep postgres
  22. postgres 31694 1 0 13:45 ? 00:00:00 postgres: logger process

kill -9 后可能会有postgres进程残留,这是PostgreSQL的老问题了,使用普通的kill就不会残留了。

4. 检查2个测试脚本的输出

failover_test.py脚本的测试jiego 从执行更新的会话来看,主从切换后没有丢失任何数据。

点击(此处)折叠或打开

  1. [postgres@node69 pgsql_ha]$ ./failover_test.py
  2. Update process had started,please kill the master...
  3. 1447049434.72  current num= 1000
  4. 1447049437.25 current num= 2000
  5. 1447049439.38 current num= 3000
  6. 1447049441.58 current num= 4000
  7. 1447049443.71 current num= 5000
  8. 1447049445.76 current num= 6000
  9. 1447049447.7 current num= 7000
  10. 1447049449.69 current num= 8000
  11. 1447049451.42 current num= 9000
  12. 1447049453.41 current num= 10000
  13. 1447049455.31 current num= 11000
  14. 1447049457.32 current num= 12000
  15. 1447049459.04 current num= 13000
  16. 1447049461.02 current num= 14000
  17. 1447049463.05 current num= 15000
  18. 1447049464.82 current num= 16000
  19. 1447049466.71 current num= 17000
  20. 1447049469.49 current num= 18000
  21. 1447049471.5 current num= 19000
  22. 1447049473.68 current num= 20000
  23. 1447049475.87 current num= 21000
  24. 1447049478.19 Tcurrent num= 22000
  25. 1447049480.2 current num= 23000
  26. 1447049482.18 current num= 24000
  27. 1447049484.01 current num= 25000
  28. 1447049485.23 The master has down, last num: 25529
  29. server closed the connection unexpectedly
  30. This probably means the server terminated abnormally
  31. before or while processing the request.
  32. 1447049504.15 connect success after 18.918158 second
  33. current num: 25529
  34. OK

failover_test2.py的测试结果是NG的。即从旁路的会话来看,曾经看到过的一次数据更新消失了,即发生了数据丢失。

点击(此处)折叠或打开

  1. [postgres@node69 pgsql_ha]$ ./failover_test2.py
  2. Read process had started...
  3. 1447049449.34 current num= 7828
  4. 1447049453.46 current num= 10040
  5. 1447049457.62 current num= 12162
  6. 1447049461.85 current num= 14456
  7. 1447049465.87 current num= 16492
  8. 1447049469.8 current num= 18165
  9. 1447049473.84 current num= 20056
  10. 1447049477.83 current num= 21867
  11. 1447049481.9 current num= 23860
  12. 1447049485.89 current num= 25530
  13. 1447049501.8 The master has down, last num= 25530
  14. server closed the connection unexpectedly
  15. This probably means the server terminated abnormally
  16. before or while processing the request.
  17. 1447049501.81 connect success after 0.006506 second
  18. 1447049501.81 current num: 25529
  19. NG: Data Lost!

问题的危害

关于这个问题的危害程度,要视情况而定了。
如果看到那次数据更新的会话,仅仅是看看,一点问题没有。如果它是利用看到的数据更新了其它表,那么也没事,因为这个更新操作必然会失败,它的WAL记录不会被传递到slave上。但是如果它把看到那个值缓存下来或者传递到其它数据系统上,那就有可能带来数据不一致了。
那么PostgreSQL为什么要这么实现呢?也许是为了尽快释放锁,也许是别的原因。之后准备发邮件到社区问问。
另外,这个问题发生的概率比较低,测试时以kill -9的方式杀进程出现这个问题的概率要高一些(可能和postgres进程残留有关),实际场景的数据库crash没这么高。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
弹性计算 关系型数据库 数据库
搭建PostgreSQL主从架构
PostgreSQL是一个关系型数据库管理系统(RDBMS),支持NoSQL数据类型(JSON/XML/hstore)。本教程介绍如何在两台CentOS 7操作系统的ECS实例上搭建PostgreSQL主从架构。
|
5月前
|
监控 物联网 关系型数据库
使用PostgreSQL触发器解决物联网设备状态同步问题
在物联网监控系统中,确保设备状态(如在线与离线)的实时性和准确性至关重要。当设备状态因外部因素改变时,需迅速反映到系统内部。因设备状态数据分布在不同表中,直接通过应用同步可能引入复杂性和错误。采用PostgreSQL触发器自动同步状态变化是一种高效方法。首先定义触发函数,在设备状态改变时更新管理模块表;然后创建触发器,在状态字段更新后执行此函数。此外,还需进行充分测试、监控性能并实施优化,以及在触发函数中加入错误处理和日志记录功能。这种方法不仅提高自动化程度,增强数据一致性与实时性,还需注意其对性能的影响并采取优化措施。
|
8月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用合集之如何使用PostgreSQL2.4.1从指定时间戳同步数据
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
8月前
|
SQL 关系型数据库 数据库
实时计算 Flink版产品使用合集之同步PostgreSQL数据时,WAL 日志无限增长,是什么导致的
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
8月前
|
分布式计算 DataWorks 关系型数据库
DataWorks产品使用合集之在使用 DataWorks 数据集成同步 PostgreSQL 数据库中的 Geometry 类型数据如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
104 0
|
8月前
|
分布式计算 关系型数据库 大数据
MaxCompute产品使用合集之怎么才可以将 PostgreSQL 中的 geometry 空间类型字段同步到 MaxCompute 或另一个 PostgreSQL 数据库
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
SQL XML 弹性计算
【ECS生长万物之开源】搭建PostgreSQL主从架构
PostgreSQL被业界誉为最先进的开源数据库,支持NoSQL数据类型(JSON/XML/hstore)。本文档介绍在CentOS 7操作系统的ECS实例上搭建PostgreSQL主从架构的操作步骤。
|
SQL 存储 关系型数据库
PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换|学习笔记
快速学习PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换
PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换|学习笔记
|
运维 网络协议 关系型数据库
PostgreSQl 12主从流复制及归档配置
PostgreSQl 12主从流复制及归档配置
|
XML 存储 关系型数据库
如何将数据从SQLServer同步至AnalyticDB for PostgreSQL
使用DTS,进行Sqlserver -> AnalyticDB for PostgreSQL 数据链路同步,可以很方便的将SQLServer中的分散的业务数据批量/实时导入至AnalyticDB for PostgreSQL, 进行复杂的OLAP查询与分析
3240 0
如何将数据从SQLServer同步至AnalyticDB for PostgreSQL

热门文章

最新文章