阅读更多

4顶
0踩

数据库

原创新闻 Postgres数据库缓存的深度指南

2016-07-04 16:13 by 副主编 mengyidan1988 评论(1) 有8378人浏览
引用

原文:Understanding caching in Postgres - An in-depth guide
作者:Madusudanan.B.N
译者:仲培艺,关注数据库领域,纠错、寻求报道或者投稿请致邮:zhongpy@csdn.net。

缓存

缓存对于数据库的系统性能有着重要意义。

本文虽以Postgres为例,但也可对应到其他数据库系统。

一、高速缓冲存储器是什么以及为什么需要它
不同的计算机部件有着不同的运行速度。人类在理解数字的规模上面,与计算机有着极大的差距。

对此,通过下面这张表格(截取自《文字间的无限空间》The Infinite Space Between Words),会有一个更直观的认识。

表中数据是从人类角度加以估算。



在数据库系统中,技术人员最关注的往往是硬盘的I/O问题。

与新的固态硬盘(SSD)相比,磁盘在随机I/O方面的性能较差。

大多数OLTP的工作负载都属于随机I/O,因此获取硬盘中信息时,速度就会极其缓慢。

为了克服这一劣势,Postgres利用随机存取存储器(RAM)来缓存数据,而这极大地提高了性能。即使对比SSD,RAM速度上也要快上很多。

上面是缓存处理器(cache)的普适原则,几乎适用于所有的数据库系统。

二、理解术语
在进一步拓展知识之前,先了解一些特定术语。

首先,可借助一篇优秀的文章展开阅读。

每当你读完一篇的时候,就接着换一篇难度更深一点的文章继续。这个过程中,要特别留意有关堆内存元组的部分。

另外,还可以看相关的官方文档,但这类相对难理解些。

不管内容是什么,Postgres都有一个抽象化存储,即计算机编程语言脚本(page,8kb)。

下幅图给出了一个简要介绍。



这一抽象概念是下文里将要重点解决的问题。

三、缓存的对象
Postgre缓存的内容如下:
  • 表格数据

指表格的具体内容。
  • 索引

索引亦存储至8k大小的块,即和表格数据存储位置一致,接着看下面的存储区域。
  • 查询执行计划

看一条查询执行计划时,有一个计划阶段,主要用来挑选最适用于查询的计划。Postgres还可以储存这个计划,而这基于的是每一次会话,而一旦会话结束,存储的计划就会被删除。这导致其优化、分析变得棘手了起来,但从总体上来看这一点不是那么重要,除非处于执行中的查询非常复杂,并且/包含大量重复查询。

文档从细节着手对这些内容做出了很好的解释。我们可以通过查询pg_prepared_statements来找出缓存了哪些东西。这里要注意的是,在结束的会话间无法获取缓存,有且仅对正在进行的会话可见。

下文将进一步研究表格数据和索引缓存的细节问题。
四、内存区
Postgres具有多个配置参数,则需要理解参数指代的含义。

对于缓存来说,最重要的配置当属shared_buffers。这在Postgres源代码内部,又称为NBuffers,用以存储共享数据至内存。

Shared_buffers也仅仅是由8kb大小的块所形成的数组。每个脚本内部都包含元数据,如前所言,在此处起到区分的作用。Progres在查看硬盘数据之前,会先查找shared_buffers内的脚本,若此处有hit参与,则其将在这一环节返回数据,从而避免硬盘的I/O问题。

五、LRU/Clock清理缓存算法
关于存储器内数据的缓存和清理的装置,受控制于Clock清理缓存算法。

因其被用以处理OLTP工作负载,故而几乎所有的信息传输都是在内存下进行的。
下面从细节处谈一谈每一项操作。

缓冲区分配

Postgres是一个基于过程的系统,换言之,每一次连接自身都含有一个本地操作系统进程,这源于Postgres的root进程(原称postmaster)。

若一个进程要求一个LRU高速缓冲存储器下的脚本(一旦该脚本经过一个经典的SQL查询而获得,则进程结束),它要求在缓冲区分配。若block已存于高速缓冲存储器,则其将被锁定并返回。而这个锁定过程也将提升下面将要讨论到的使用计数。而如果使用计数显示为0,则脚本将被解除锁定。

只有当所有缓冲区或slots都包含一个脚本之后,才会进行到缓存区清理环节。

缓存区清理

计算机学科学的一个经典问题就是确定哪些脚本应当从内存中删除,并写入硬盘。

由于对之前的运行没有记录,简单一个LRU(Least Recently Used,最近不常使用算法)在实际中很难很好地工作。

而Postgres保存了脚本使用计数的运行轨迹,因此如果一个脚本的使用计数是0,则其将从内存中被删除并写入硬盘。此外,在遇到脏页的情况下,也将被写入硬盘(详情见下文)。

暂且抛下具体细节不看的话,缓存算法本身几乎不需要调整,而且其智能程度远超于人的想象。

六、脏页和高速缓冲存储器失效
一直到这里我们都在讨论选择查询的问题,那DML查询呢?简单来说,二者写入相同的脚本。若为内存,则直接写入,否则先从内存中提取出来再写入。

这就是产生“脏页”这个概念的环节,也就是说,一个脚本已被修改还未写入硬盘的情况。
在我们继续之前,还有许多功课和研究要做,这里特别强调关于WAL日志和内部事件(checkpoints)。

WAL是一个重做日志,主要用以保存系统内部发生一切的轨迹。这是通过将所有的变化分别录入一个WAL日志来实现。Checkpointer就是一个在一个time setting的控制下,定时将所谓的脏页写入磁盘的进程。之所以如此操作,是因为考虑到数据库崩溃时从头开始重复所有操作的需要。

这是从内存中删除脚本最常见的方法,而在经典案例中LRU算法清理几乎没有出现过。

七、理解高速缓冲存储器(cache)
Explain是理解计算机内部正在进行写什么的好方法。这个方法甚至能够告诉你有多少数据块是来自磁盘的,又有多少是来自shared_buffers,即内存的。

例如下面这个查询计划:
performance_test=# explain (analyze,buffers) select * from users order by userid limit 10;

  Limit  (cost=0.42..1.93 rows=10 width=219) (actual time=32.099..81.529 rows=10 loops=1)
    Buffers: shared read=13
    ->  Index Scan using users_userid_idx on users  (cost=0.42..150979.46 rows=1000000 width=219) (actual time=32.096..81.513 rows=10 loops=1)
          Buffers: shared read=13
  Planning time: 0.153 ms
  Execution time: 81.575 ms
 (6 rows)

共享读取指的是其取自磁盘且不被缓存。若该查询再次运行,且缓存配置正确的话(我们将在下面讨论),其将以共享hit的形式呈现。
performance_test=# explain (analyze,buffers) select * from users order by userid limit 10;

  Limit  (cost=0.42..1.93 rows=10 width=219) (actual time=0.030..0.052 rows=10 loops=1)
    Buffers: shared hit=13
    ->  Index Scan using users_userid_idx on users  (cost=0.42..150979.46 rows=1000000 width=219) (actual time=0.028..0.044 rows=10 loops=1)
          Buffers: shared hit=13
  Planning time: 0.117 ms
  Execution time: 0.085 ms
 (6 rows)

通过这个方法,很容易从查询的角度来获知缓存的量,而无需通过OS/Postgres的Internals来完成。

八、全面扫描案例
全面扫描,在没有索引且Postgres必须从磁盘中提取所有数据的情况下,对于这类高速缓冲存储器来说归属问题区域。

由于单次扫描能清楚存储器内的所有数据,故而其操作方法有所不同。它选用一组共计256K.B大小的缓冲区,而非常规的LRU/Clock清理缓存算法。下面的查询展示了它的操作方法。
performance_test=# explain (analyze,buffers) select count(*) from users;

  Aggregate  (cost=48214.95..48214.96 rows=1 width=0) (actual time=3874.445..3874.445 rows=1 loops=1)
    Buffers: shared read=35715
    ->  Seq Scan on users  (cost=0.00..45714.96 rows=999996 width=0) (actual time=6.024..3526.606 rows=1000000 loops=1)
          Buffers: shared read=35715
  Planning time: 0.114 ms
  Execution time: 3874.509 ms

再次执行上述查询。
performance_test=# explain (analyze,buffers) select count(*) from users;

  Aggregate  (cost=48214.95..48214.96 rows=1 width=0) (actual time=426.385..426.385 rows=1 loops=1)
    Buffers: shared hit=32 read=35683
    ->  Seq Scan on users  (cost=0.00..45714.96 rows=999996 width=0) (actual time=0.036..285.363 rows=1000000 loops=1)
          Buffers: shared hit=32 read=35683
  Planning time: 0.048 ms
  Execution time: 426.431 ms

从中我们可以清楚地看到32个块清楚地转入了内存,即32*8= 256 KB。这在src、backend、storage、buffer和README中都有所说明/解释/体现。

九、内存流和OS缓存
Postgres是一个跨平台数据库,其缓存很大程度上依赖于操作系统。

实际上shared_buffers是在复制OS的操作。下面给出一张典型的数据在Postgres内部的流向图。



起初的确令人困惑,由于缓存是由OS和Postgres二者同时管理的,但这样操作也是有原因的。

谈及操作系统,高速缓冲存储器则需要一个独立的post,网上也有很多可供利用的资源。

要记住OS缓存数据是处于和上面提到的一样的原因,即为什么我们需要一个高速缓冲存储器那一部分内容。

我们可以把I/O分为两类,即读和写。简言之,数据从磁盘流入内存叫做读;数据由内存流入磁盘称为写。

1.读

参考上面的流程图,可以发现写指的是数据从磁盘到OS存储器再到shared_buffers的转移过程。

有时,OS存储器和shared_buffers可以控制相同的脚本,而这可能导致空间浪费,但这里要记住,OS存储器所用的是简单的LRU算法而非数据库优化的clock sweep。一旦脚本页的共享缓冲功能减弱,OS存储器内则无法读取,且若有备份,也很容易被删除。

在实际情况下,并不存在太多被同时储存于各内存区域的脚本页。这也是建议仔细规划shared_buffers大小的原因之一。过分硬性的规定将有损其性能,例如让其承载最大份额的内存或是给其分配的过少。

针对其优化问题,下文将详解。

2.写

写是由内存到磁盘的数据转移。这个环节正是脏页产生之处。一个脚本一旦被标记为脏页,则将被传输至OS存储器,随后则被写入磁盘。在这个环节,OS在输入通信量的基础上享有更大的调度输入输出的自由。

如上文所说,若OS存储器容量较小,则其不能对写入进行重新排序,也不能对输入输出进行优化。而这对于高工作负载的写来说是非常重要的。因此,OS存储器的大小也很重要。

十、初始配置
对于很多数据库系统来说,并不存在直接适用的万能配置。配有基础配置的PostgreSQL ships经过优化增强了其兼容性而非性能。

依据应用程序、工作负载来优化配置,是数据库管理员、开发者的责任。然而Postgres有一个很好的文档用以指示从何处开始。

一旦确立了默认、启动配置,即可通过负载、性能测试查看其操作情况。

要记住对初始配置的调整是为了更好的获取而不是性能的提高。通常情况下,最好能检测并选择更适宜其工作负载的配置。

十一、随时优化
对于不可检测的东西,自然也无法优化。

有了postgres,就有了两种可供检测的方法。

1.操作系统

对于在哪个平台上Postgres可以最好地工作这个问题的答案,并未得到普遍的共识,所以此处假设适用的是Linux家庭版操作系统。但这个想法类似。

首先,有一个叫做Io top的工具,可以用它来检测磁盘的输入输出。和top相似的是,它在检测磁盘输入输出时迟早会派上用场。这是只需要运行iotop指令来检测读取、写入。



上图清楚地展示了在负载下Postgres是如何运行的,即哪些是针对磁盘的,又有哪些是来自RAM的,这些数据可通过产生的负载来获取。

直接来自Postgres
通常情况下,最好监测直接来源于Postgres的数据,而不是经历了OS路径传输的数据。

此外,通常只有在确认Postgres本身存在错误的条件下,才会执行OS层面的监测,但这种情况一般不常见。

伴随Postgres,有一些可供支配的工具,可以用来检测内存的性能。

默认是SQL explain。它能给出比其它数据库系统要更多的信息,但在理解上的难度也要相对高一些。故而需要通过练习来加以熟悉。不要错过那几个有用的flags,因为如前所见,他们将被特别缓存。

进入下面的链接来深入了解explain的相关内容:
更多explain详情关注网站 postgresguide.com
Explain visualizer

查询日志是用以了解系统内部所发生的事件的另一途径。

我们无需事无巨细地全部记录,只需要记录特定时间间隔间的查询,或者运用log_min_duration_statement参数以采用慢速查询日志。

这也可以帮助你利用慢速查询自动记录执行计划。它能够让你在无需手动运行explain的情况下进行调试。

上面提到的方案都很好,但是缺乏巩固的理念。

这是Postgres内部建立的模块,但是在默认情况下会产生缺陷。

可以通过创造extension pg_stat_statements来使之工作。

一旦其开始运行,经过一定量的查询之后,即可以展开如下查询。
SELECT
    (total_time / 1000 / 60)::decimal as total_minutes,
    (total_time/calls)::decimal as average_time,
    query
  FROM pg_stat_statements
  ORDER BY 1 DESC
  LIMIT 100;

给出大量查询所消耗的时长及其平均值的细节信息。

这个方法的缺陷在于它消耗了部分性能,故而在生产系统中不常推荐这个方法。

如果想要获得更深入的了解,还有两个可以直接深入shared_buffers和OS 存储器内部的模块。

值得注意的是,explain(analyze,buffers)只显示出自shared_buffers的工具,不包括出自OS存储器的。

PG缓冲存储器

这帮助我们实时掌握共享缓冲区内的数据。收集来自shared_buffers的数据,并存至pg_buffercache内部方便查看。

下面给出一个查询样例,它列出了前一百的表格和存储的脚本的数量。
SELECT c.relname,count(*) AS buffers
    FROM pg_class c INNER JOIN pg_buffercache b
    ON b.relfilenode=c.relfilenode INNER JOIN pg_database d
    ON (b.reldatabase=d.oid AND 
    d.datname=current_database())
    GROUP BY c.relname ORDER BY 2 DESC LIMIT 100;

  • PG fincore


这是一个外部模块,负责提供OS如何存储脚本的信息。这一层级较低且影响力较强。

Pg预加载模块(数据缓存预加载模块)
这是一个内置模块,它确实能够负载数据到shared_buffers或OS存储器或为二者共有。如果觉得内存预热是个问题的话,便可以借此来进行很好的调试。

虽然还有其它可以用以了解Postgres存储器的方法,但我这里已经列出了最为普遍以及最为便于使用的几项了。有了这些工具,就不会再出现由于内存问题导致的数据库运行缓慢。

参考文献
  • 大小: 24.6 KB
  • 大小: 5.4 KB
  • 大小: 8.8 KB
  • 大小: 24.2 KB
4
0
评论 共 1 条 请登录后发表评论
1 楼 fjjiaboming 2016-07-06 09:34

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • oracle读书笔记

    oracle读书笔记1、 层次名完整性 2、 外键不建议使用简短列名 3、 名字都使用单数形式 4、 简洁性(相对,注意有时不应该为简洁性牺牲清晰性) 5、 对象名词典,这个词典应该强制实施公司的命名标准并保证名字选择和缩写的一致性

  • oracle查询语句 switch,ORACLE SQL语句中的“SWITCH语句”函数DECODE

    下面这个例子介绍ORACLE SQL Functions对DECODE函数的用法,引自官方文档 SELECT product_id, DECODE (warehouse_id, 1, 'Southlake', 2, 'San Francisco', 3, 'N...

  • oracle学习篇(二)

    oracle学习篇(二)

  • 常用oracle语句

    1、 select MACHINE from v$session   //可以查看到连接到oracle服务上客户端计算机名字 select sys_context('USERENV','IP_ADDRESS')  from dual;  //获取oracle客户段ip地址 select count(*) from v$session where username is not null;

  • oracle的switch+case语句吗,2.7 switch 语句中的 case 范围

    2.7 switch 语句中的 case 范围在标准 C 中,switch 语句中的 case 标签只能有一个关联值。Solaris Studio C 允许使用某些编译器中使用的扩展(称为 case 范围)。case 范围指定要与单个 case 标签关联的值范围。case 范围语法为:case low ... high :case 范围的行为就好像为从 low 到 high(包含边界值)的给定范围...

  • 《收获,不止Oracle》读书笔记之第三章逻辑体系世界

    《收获,不止Oracle》读书笔记之第三章逻辑体系世界

  • Oracle 11g基础简介(《Oracle从入门到精通》读书笔记1)

    ...... 4. 启动数据库:启动顺序-启动实例、加载数据库、打开数据库(连接) startup nomount|mount|open|force|resetrict [pfile=filename] nomount:只启动实例、不加载数据库 mount:启动实例、数据库,但保持数据库为关闭状态(不可连接) open:默认启动选项,启动实例、数据库并打开数据库连接; force:强制关闭当前正在运行的实例再启动,也就是重启; resetrict:指定以受限制的会话方式启动数据库; pfile:指定实例启动

  • oracle 读书笔记

    <br />使用逻辑操作符<br />  查询工资高于500 或者岗位 <br /> <br />数据分组  --max  min  avg  sum  count<br /> <br />如何显示所有员工中最高工资和最低工资<br />select max(sal) ,min(sal) from emp;<br /> <br />如何显示所有员工中最高工资和最低工资的名字和工资<br />select ename ,sal from emp where sal=(select max(sal) from

  • Oracle函数

    Oracle函数 函数的分类: 单行函数:对每行输入值进行计算,得到相应的计算结果,返回给用户,也就是说,每行作为一个输入参数,经过函数计算得到每行的计算结果。比如length 多行函数:对多行输入值进行计算,得到多行对应的单个结果。比如max,min dual为oracle本身自带的虚表,主要用于测试使用。 sql函数的使用--单行函数分类 单行函数分为

  • oracle数据库读书报告,Oracle Concepts 11g 读书笔记(五)

    ++[Oracle Database Instance]+++[Introduction to the Oracle Database Instance]A database instance is a set of memory structures that manage database files. A database is a set of physical files on disk...

  • oracle数据库读书报告,Oracle 10g 读书笔记之数据库常用文件

    查询文件位置:select name, value from v$parameter where name like一、参数文件和服务器参数文件参数文件(Parameter File)通常称为初始文件(init file),或 init.ora 文件。默认名称为 init.ora 文本文件。SPFILE 生成 PFILE:create pfile='/tmp/initorcl.ora' from ...

  • oracle的switch+case语句吗,oracle中case及其类似用法

    在现实数据时,我们为了更直观的显示表内信息,通常会将同类或某个范围内的值归为一类,使用统一的‘新值’取代‘原值’,那么这时候,我们要用到case语句,下面看看case的几种用法吧。1.简单case语句简单case语句的用法与C#中switch case的用法很类似,case捕获定制,then后返回‘新值’,通常也可以利用该语法判定指定字段是否有指定值得存在,下面通过例子看看它的用法:1 selec...

  • 读书笔记 《Oracle性能诊断艺术》

    Troubleshooting Oracle Performance Christian Antognini   可以从 http://top.antognini.ch 下载代码   一、性能问题 二、关键概念 选择性和基数 游标的生命周期 解析的过程   三、找出性能问题  1、跟踪数据库调用 2、剖析PL/SQL代码   四、系统和对象统计信息 1、使用dbms

  • Oracle CASE WHEN 用法介绍

    转自http://www.cnblogs.com/eshizhan/archive/2012/04/06/2435493.html 1. CASE WHEN 表达式有两种形式 --简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END --Case搜索函数 CASE...

  • ORACLE SQL语句中的“SWITCH语句”函数DECODE

    下面这个例子介绍ORACLE SQL Functions对DECODE函数的用法,引自官方文档 SELECT product_id,        DECODE (warehouse_id, 1, 'Southlake',                              2, 'San Francisco',                              3, '

  • oracle case.when group by

    数据库 t 表 b 表内容 Id Name 胜负 1 张三 胜 2 李四 胜 3 张三 胜 4 张三 负 5 王五 胜 6

  • 《Oracle编程艺术》学习笔记(18)-REDO和UNDO

    REDOOracle维护着两类重做日志文件:在线(online)重做日志文件和归档(archived)重做日志文件。 在线重做日志用于在出现电源故障(实例终止)时“修正”数据文件,而归档重做日志用于在出现硬盘故障时或者误操作删除数据时,配合数据文件备份“修正”数据文件。 为什么需要重做日志文件-&amp;gt;http://blog.csdn.net/fw0124/article/details/68...

  • oracle数据库学习感想,学习oracle数据库感想

    要想学好Oracle数据库,首先要学习ORACLE的体系结构,现在你不需要深入理解它们,但要分清几个关键的概念:instance&amp;database, memory structure,process&amp;those files,such as data file, control file,init parameter file etc以及database ,tablespace,da...

  • 《Oracle PLSQL实例精解》读书笔记

    《Oracle PLSQL实例精解》读书笔记导读第一章 PL/SQL概念第二章 PL/SQL语言基础第三章 在PL/SQL中的SQL第四章 条件控制:IF语句第五章 条件控制:CASE语句第六章 迭代控制:第一部分第七章 迭代控制:第二部分第十九章 过程第二十章 函数 导读 以下是一部分读书笔记,内容相对零散,希望在用到时会有一点提示作用。 第一章 PL/SQL概念 PL/SQL是Oracle...

Global site tag (gtag.js) - Google Analytics