Columnstore中的4秒内环游世界(第1部分)

在本文中,我将考虑提高报告速度。 所谓报告,是指对使用聚合函数的数据库的任何查询。 另外,我将涉及与在人力和机器报表的生产和支持上花费的资源有关的问题。

在示例中,我将使用包含52,608,000条记录的数据集。

以不难分析的储备为例,我将证明即使是一台性能较弱的计算机也可以轻松地成为分析“体面”数据量的良好工具。

建立了不复杂的实验后,我们将看到常规表不适合用于分析查询。

如果读者可以轻松地理解缩写OLTP和OLAP,则直接进入Columnstore部分可能很有意义

两种处理数据的方法


在这里,我将简要介绍一下,因为 Internet上关于此主题的信息已绰绰有余。

因此,在最高级别上,只有两种处理数据的方法:OLTP和OLAP。

OLTP-可以转换为即时交易处理。 实际上,我们谈论的是处理少量数据的短交易的在线处理。 例如,记录,更新或删除订单。 在绝大多数情况下,订单是非常少量的数据,在处理过程中,您不必担心现代RDBMS施加的长锁。

OLAP-可以一次转换为大量交易的分析处理。 任何报告都使用这种特定方法,因为在大多数情况下,该报告会生成某些部分的汇总汇总数据。

每种方法都有自己的技术。 例如,对于OLTP,它是PostgreSQL,对于OLAP,它是Microsoft SQL Server Analysis Services。 尽管PostgresSQL使用一种众所周知的格式将数据存储在表中,但为OLAP发明了几种不同的格式。 这些是多维表,存储有键值对和我最喜欢的列存储区。 有关后者的详细信息,请参见下文。

为什么需要两种方法?


值得注意的是,任何数据仓库迟早都会面临两种类型的负载:频繁读取(当然也要写入和更新)非常少量的数据,而很少读取但却非常大量的数据。 实际上,这是例如票房和负责人的活动。 收银台全天工作,用少量数据填充存储,而到了一天结束时,如果业务进展顺利,累积的存储量将达到可观的规模。 反过来,经理在一天结束时想知道每天的票房收入。

因此,在OLTP中,我们具有表和索引。 这两个工具非常适合记录所有细节的票房活动。 索引提供了对先前记录的订单的快速搜索,因此更改订单很容易。 但是为了满足领导者的需求,我们需要考虑每天累积的全部数据量。 另外,通常,经理不需要所有订单的所有详细信息。 他真正需要知道的是票房一般能赚多少钱。 售票处在哪里,何时有午休时间,由谁工作等等都无关紧要。 那时就存在OLAP,因此系统可以在很短的时间内回答这个问题-该公司从整体上赚了多少,而无需顺序读取每个订单及其所有详细信息。 OLAP可以使用与OLTP相同的表和索引吗? 答案是否定的,至少不应该这样。 首先,因为OLAP不需要表中记录的所有详细信息。 通过以除二维表以外的其他格式存储数据来解决此问题。 其次,所分析的信息通常散布在不同的表中,这需要它们的多个关联,包括自联接类型的关联。 为了解决此问题,通常,他们开发一种特殊的数据库架构。 该方案针对OLAP负载以及针对OLTP负载的正常归一化方案进行了优化。

OLAP使用OLTP方案时会发生什么


实际上,我介绍了这一部分,是为了使本文清楚地满足我对此类材料格式的要求,即 问题,解决方案,结论。

我们列出了使用OLTP方案进行数据分析的许多缺点。

  • 索引太多。

    通常,您必须创建特殊索引来支持报告。 这些索引实现了OLAP数据存储方案。 应用程序的OLTP部分不使用它们,而是在它们上施加负载,需要不断的支持并占用磁盘空间。
  • 读取的数据量超出要求。
  • 缺乏清晰的数据方案。

    事实是,报告以单一形式提交的信息通常分散在不同的表中。 此类信息需要不断进行转换。 最简单的示例是收入金额,其中包括现金和非现金货币。 另一个引人注目的示例是数据层次结构。 因为 应用程序开发是渐进的,并不总是知道将来需要什么,可以将含义相同的层次结构存储在不同的表中。 而且,虽然在OLAP中积极使用了即时获取功能,但这些功能却略有不同。
  • 查询过于复杂。

    因为 OLTP方案不同于OLAP,它需要一个高度相关的软件层​​,以使OLTP数据方案具有正确的格式。
  • 支持,调试和开发的复杂性。

    通常,我们可以说代码库越复杂,将其维持在健康状态就越困难。 这是一个公理。
  • 测试范围的复杂性。

    由于有关如何使数据库充满所有测试脚本的讨论,很多副本都被破坏了,但是最好说,使用更简单的数据方案可以使测试所涉及的任务简化很多次。
  • 无休止的性能调试。

    用户很有可能会下令数据库服务器“沉重”的报告。 随着时间的推移,这种可能性会增加。 应当注意,OLAP也容易出现此问题,但是与OLTP不同,此问题中的OLAP资源要高得多。

列存储

本文将重点介绍列存储的存储格式,但没有底层细节。 上面提到的其他格式也应引起注意,但这是另一篇文章的主题。

实际上,列存储格式已经知道30年了,但是直到最近才在RDBMS中实现。 columnstore的本质是数据不是存储在行中,而是存储在列中。 即 在一页上(所有已知的8 Kb),服务器仅记录一个字段的数据。 因此,表中的每个字段依次出现。 这是必要的,这样您就不必阅读其他信息。 假设有一个包含10个字段的表和一个在SELECT语句中仅指定一个字段的查询。 如果它是以基于行的格式保存的常规表,则服务器将被强制读取所有10个字段,但同时仅返回一个字段。 事实证明,服务器读取的信息比必需的多9倍。 Columnstore完全解决了这个问题,因为 存储格式仅允许您读取一个有序字段。 这一切都是因为RDBMS中的存储单元是一页。 即 服务器始终写入和读取至少一页。 唯一的问题是上面有多少个字段。

Columnstore如何真正提供帮助


要回答这个问题,必须有确切的数字。 让我们得到它们。 但是什么数字可以给出准确的图像呢?

  1. 磁盘空间量。
  2. 查询性能。
  3. 容错能力。
  4. 易于实施。
  5. 开发人员必须具备哪些新技能才能使用新结构。

磁碟空间


让我们创建一个简单的表,用数据填充它并检查它占用了多少空间。

create foreign table cstore_table ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) server cstore_server options(compression 'pglz'); 

如您所见,我创建了一个外部表。 事实是PostgreSQL没有内置的列存储支持。 但是PostgreSQL有一个强大的扩展系统。 其中之一使创建列存储表成为可能。 文章末尾的链接。

  • pglz-告诉扩展名应该使用PostgreSQL中的内置算法压缩数据;
  • trd-交易时间;
  • 运维-分析部分或测量;
  • m1,m2,m3,m4,m5-数字指示器或度量;

让我们插入“数量可观”的数据量,看看它在磁盘上占用了多少空间。 同时,我们检查插件的性能。 因为 我将实验放在家用笔记本电脑上,数据量有点自然。 此外,这甚至很好,我将使用运行来宾OS Fedora 30的HDD。OS主机-Windows 10家庭版。 处理器Intel Core7。来宾OS收到4 GB RAM。 PostgreSQL版本-x86_64-pc-linux-gnu上的PostgreSQL 10.10,由gcc(GCC)9.1.1 20190503(Red Hat 9.1.1-1)编译,64位。 我将尝试一个具有记录数52608 000的数据集。

 explain (analyze) insert into cstore_table select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 1095) as d; 

实施计划如下
插入cstore_table(成本= 0.01..24902714242540.01行= 1000000000000000宽度= 150)(实际时间= 119560.456..119560.456行= 0循环= 1)
---->嵌套循环(成本= 0.01..24902714242540.01行= 1000000000000000宽度= 150)(实际时间= 1.823..22339.976行= 52608000循环= 1)
---------->对generate_series d进行功能扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.151..2.198行= 1096循环= 1)
---------->实现(成本= 0.01..27284555030.01行= 1000000000000宽度= 16)(实际时间= 0.002..3.196行= 48000循环= 1096)
---------------->嵌套循环(成本= 0.01..17401742530.01行= 1000000000000宽度= 16)(实际时间= 1.461..15.072行= 48000循环= 1)
---------------------->函数对generate_series进行扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 1.159..2.007行= 4000循环= 1)
---------------------->实现(成本= 0.01..26312333.01行= 1,000,000,000宽度= 12)(实际时间= 0.000..0.001行= 12个循环= 4000)
---------------------------->嵌套循环(成本= 0.01..16429520.01行= 1,000,000,000宽度= 12)(实际时间= 0.257 ..0.485行= 12个循环= 1)
---------------------------------->在generate_series wh上进行功能扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.046..0.049行= 3个循环= 1)
---------------------------------->实现(成本= 0.01..28917.01行= 1,000,000宽度= 8) (实际时间= 0.070..0.139行= 4个循环= 3)
--------------------------------------->嵌套循环(成本= 0.01..20010.01行= 1000000宽度= 8)(实际时间= 0.173..0.366行= 4循环= 1)
------------------------------------------->在generate_series op(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.076..0.079行= 2个循环= 1)
--------------------------------------------->对generate_series进行功能扫描组织(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.043..0.047行= 2个循环= 2)
计划时间:0.439毫秒
执行时间:119692.051 ms
总交货时间-1.994867517分钟

数据集创建时间-22.339976秒

插入时间-1.620341333分钟

我没有使用PostgreSQL函数评估占用的磁盘空间。 不知道为什么,但是显示为0。也许这是外部表的标准行为。 用于此文件管理器。 因此,占用的磁盘空间量为226.2 Mb。 要评估多少,让我们将其与常规表进行比较。

 explain (analyze) create table rbstore_table as select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 1095) as d; 

实施计划如下
嵌套循环(成本= 0.01..22402714242540.01行= 1000000000000000宽度= 44)(实际时间= 0.585..23781.942行= 52608000循环= 1)
--->在generate_series d上进行功能扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.091..2.130行= 1096循环= 1)
--->实现(成本= 0.01..27284555030.01行= 1000000000000宽度= 16)(实际时间= 0.001..3.574行= 48000循环= 1096)
---------->嵌套循环(成本= 0.01..17401742530.01行= 1000000000000宽度= 16)(实际时间= 0.489..14.044行= 48000循环= 1)
---------------->函数对generate_series进行扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.477..1.352行= 4000循环= 1 )
---------------->实现(成本= 0.01..26312333.01行= 1000000000宽度= 12)(实际时间= 0.000..0.001行= 12循环= 4000)
---------------------->嵌套循环(成本= 0.01..16429520.01行= 1,000,000,000宽度= 12)(实际时间= 0.010..0.019行= 12循环= 1)
---------------------------->函数对generate_series wh扫描(成本= 0.00..10.00行= 1000宽度= 4)(实际时间= 0.003..0.003行= 3个循环= 1)
---------------------------->实现(成本= 0.01..28917.01行= 1,000,000宽度= 8)(实际时间= 0.002。 .0.004行= 4个循环= 3)
---------------------------------->嵌套循环(成本= 0.01..20010.01行= 1,000,000宽度= 8 )(实际时间= 0.006..0.009行= 4个循环= 1)
---------------------------------------->对generate_series op进行功能扫描(成本= 0.00 ..10.00行= 1000宽度= 4)(实际时间= 0.002..0.002行= 2循环= 1)
---------------------------------------->对generate_series组织进行功能扫描(成本= 0.00 ..10.00行= 1000宽度= 4)(实际时间= 0.001..0.001行= 2循环= 2)
计划时间:0.569毫秒
执行时间:378883.989 ms
我们对执行该计划所花费的时间不感兴趣,因为 在现实生活中,不应该使用此类插件。 我们对这个表占用多少磁盘空间感兴趣。 满足了系统功能的要求后,我收到了3.75 GB。

因此,cstore_table-226 MB,rbstore_table-3.75 GB。 16.99倍的差异是惊人的,但主要由于数据的分布,不太可能在生产中获得相同的差异。 通常,这种差异将较小,约为5倍。

但是,等等,没有人将基于行的格式的原始数据用于分析目的。 例如,他们尝试使用索引数据进行报告。 而且因为 “原始”数据将始终是,您需要将大小与索引大小进行比较。 让我们创建至少一个索引。 使其成为日期字段和操作类型的索引-trd + op。

因此,我仅对两个字段建立了索引,索引占用了1583 MB,比cstore_table大得多。 但是,通常,OLAP加载需要多个索引。 在这里应该适当注意,cstore_table不需要其他索引。 该表用作涵盖所有查询的索引。

综上所述,可以得出一个简单的结论-使用列存储表,可以减少使用的磁盘空间量。

查询效果


为了评估性能,让我们运行一个查询,该查询返回特定月份的特定操作类型的摘要数据。

 explain (analyze, costs, buffers) select sum(m1) from cstore_table where trd = '2011-01-01' and op = 1; 

实施计划如下
总计(成本= 793602.69..793602.70行= 1宽度= 32)(实际时间= 79.708..79.708行= 1循环= 1)
-缓冲区:共享命中= 44226
--->在cstore_table上进行外部扫描(成本= 0.00..793544.70行= 23197宽度= 5)(实际时间= 23.209..76.628行= 24000循环= 1)
--------过滤器:((trd ='2011-01-01'::日期)AND(op = 1))
--------被筛选器删除的行:26000
-------- CStore文件:/ var / lib / pgsql / 10 / data / cstore_fdw / 14028/16417
-------- CStore文件大小:120818897
--------缓冲区:共享匹配= 44226
计划时间:0.165毫秒
执行时间:79.887毫秒


 explain (analyze, costs, buffers) select sum(m1) from rbstore_table where trd = '2011-01-01' and op = 1; 

实施计划如下
总计(成本= 40053.80..40053.81行= 1宽度= 8)(实际时间= 389.183..389.183行= 1循环= 1)
-缓冲区:共享读取= 545
--->使用rbstore_table上的trd_op_ix进行索引扫描(成本= 0.56..39996.70行= 22841宽度= 4)(实际时间= 55.955..385.283行= 24000循环= 1)
--------索引条件:((trd ='2011-01-01 00:00:00'::不带时区的时间戳)AND(op = 1))
--------缓冲区:共享读取= 545
计划时间:112.175毫秒
执行时间:389.219 ms
389.219毫秒和79.887毫秒。 在这里,我们看到即使在相对少量的列存储数据上,表也比基于行的表上的索引快得多。

让我们更改请求,尝试获取整个2011年的销售量。

 explain (analyze, costs, buffers) select sum(m1) from cstore_table where trd between '2011-01-01' and '2011-12-31' and op = 1; 

实施计划如下
总计(成本= 946625.58..946625.59行= 1宽度= 32)(实际时间= 3123.604..3123.604行= 1循环= 1)
-缓冲区:共享命中= 44226
--->在cstore_table上进行外部扫描(成本= 0.00..925064.70行= 8624349宽度= 5)(实际时间= 21.728..2100.665行= 8760000循环= 1)
--------过滤器:((trd> ='2011-01-01'::日期)AND(trd <='2011-12-31'::日期)AND(op = 1))
--------被过滤器删除的行:8760000
-------- CStore文件:/ var / lib / pgsql / 10 /数据/ cstore_fdw / 14028/16411
-------- CStore文件大小:120818897
--------缓冲区:共享匹配= 44226
计划时间:0.212毫秒
执行时间:3123.960 ms


 explain (analyze, costs, buffers) select sum(m1) from rbstore_table where trd between '2011-01-01' and '2011-12-31' and op = 1; 

实施计划如下
完成汇总(成本= 885214.33..885214.34行= 1宽度= 8)(实际时间= 98512.560..98512.560行= 1循环= 1)
-缓冲区:共享命中= 2565读取= 489099
--->收集(成本= 885214.12..885214.33行= 2宽度= 8)(实际时间= 98427.034..98523.194行= 3个循环= 1)
--------计划的工人人数:2
--------发动的工人:2
--------缓冲区:共享命中= 2565读取= 489099
--------->部分总计(成本= 884214.12..884214.13行= 1宽度= 8)(实际时间= 97907.608..97907.608行= 1循环= 3)
--------------缓冲区:共享命中= 2565读取= 489099
--------------->在rbstore_table上进行并行Seq扫描(成本= 0.00..875264.00行= 3580047宽度= 4)(实际时间= 40820.004..97405.250行= 2920000循环= 3)
---------------------过滤器:((trd> ='2011-01-01 00:00:00'::不带时区的时间戳)AND(trd <='2011-12-31 00:00:00'::不带时区的时间戳)AND(op = 1))
--------------------筛选器删除的行:14616000
--------------------缓冲区:共享命中= 2565读取= 489099
计划时间:7.899毫秒
执行时间:98523.278 ms
98523.278毫秒和3123.960毫秒。 也许部分索引会对我们有所帮助,但是最好不要冒险,并建立一个合适的基于row_的结构来存储现成的值。

手动汇总


手动聚合的合适结构可以是包含预计算值的基于行的常规表。 例如,它可能包含与2011年有关的记录,其操作类型等于1,而在字段m1,m2,m3,m4和m5中,将为这些分析部分精确存储汇总值。 因此,有了足够的集合和索引集,分析查询将获得空前的性能。 有趣的是,Microsoft SQL Server Analysis Services有一个特殊的向导,可让您配置预先计算的值的数量和深度。

该解决方案具有以下优点:

  • 实时分析。

    请不要混淆“实时分析”一词。 在这里,我们谈论的事实是,在大多数情况下,单位的增量会在可接受的时间内发生。

    实际上,这个加号是有争议的,但是我们不要谈论它。 事实仍然存在。 该解决方案的架构使单元几乎始终保持“新鲜”。
  • 完全独立于数据量。

    这是一个非常严重的优点。 无论处理了多少数据,迟早都会处理它们,并接收汇总。
  • 相对复杂。

    为了获得实时分析和数据量独立性,该解决方案必须使用高级技术,例如DBMS级别的多线程和手动锁定管理。
  • 难度测试。

    在这里,我们讨论的是单元测试和手动测试。 我认为读者不应该解释识别多线程错误并不是一件容易的事。
  • 磁盘空间需求增加。


columnstore的实际使用


在这里,我们必须再次深入理论并更详细地分析什么是分析数据的问题。

以企业的平均负责人为准。 通常,他/她担心两个全球性问题:“目前情况如何?” 和“最近发生了什么变化?”。

要回答“目前情况如何”这个问题,我们绝对不需要历史数据。 即 不管一个月前情况如何。

为了保持对脉冲的了解,经常会问这个问题。 这种数据分析称为可操作的。

要回答“最近发生了什么变化”的问题,我们需要准确的历史数据。 而且,通常,分析以相同的时间间隔进行。 例如,将一个月与一个月,年份等进行比较。 当然,系统不应限制用户比较任意时间段的能力,但必须将这种情况视为罕见,因为 将关闭的年份与未关闭的一半进行比较没有任何意义。 比较分析的一个显着特征是,它不需要操作那么频繁。 我们将这种分析称为历史。

显然,运营分析应该迅速进行。 因此,对性能提出了很高的要求。 在进行历史分析时,不能提出这样的要求。 尽管历史分析的表现应该保持在很高的水平。 至少使分析系统本身保持竞争力。

因此,根据两种类型的分析,我们可以区分两种类型的分析数据:运营数据和历史数据。 从用户的角度来看,应该不会注意到他当前正在处理哪些数据。

出于这些考虑,在数据库服务器中出现了将表拆分为单独的部分的可能性。

关于列存储,可以混合使用基于行和列存储格式的部分。 众所周知,操作分析数据经常更改,这会阻止它们以列存储格式存储。 鉴于操作数据不会发生太多的事实,可以将它们以基于行的格式存储。

历史数据不变。 这些数据很多,因此columnstore格式更适合它们。 回想一下,在列存储源上的粗体查询性能要比在基于行的源上高。

让我们来看一个以上所有示例。

在下面,我创建主仓库表,并将操作和历史分析的各个部分附加到该表上。

 create table warehouse ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) partition by range(trd); create foreign table historycal_data ( trd date, org int, op int, it int, wh int, m1 numeric(32, 2), m2 numeric(32, 2), m3 numeric(32, 2), m4 numeric(32, 2), m5 numeric(32, 2) ) server cstore_server options(compression 'pglz'); insert into historycal_data select '2010-01-01'::date + make_interval(days => d) as trd , op , org , wh , it , 100 as m1 , 100 as m2 , 100 as m3 , 100 as m4 , 100 as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, (1095 - 31)) as d; analyze historycal_data; create table operational_data as select ('2012-12-01'::date + make_interval(days => d))::date as trd , op , org , wh , it , 100::numeric(32, 2) as m1 , 100::numeric(32, 2) as m2 , 100::numeric(32, 2) as m3 , 100::numeric(32, 2) as m4 , 100::numeric(32, 2) as m5 from generate_series(0, 1) as op cross join generate_series(1, 2) as org cross join generate_series(1, 3) as wh cross join generate_series(1, 4000) as it cross join generate_series(0, 30) as d; create index trd_op_ix on operational_data (trd, op); analyze operational_data; alter table warehouse attach partition operational_data for values from ('2012-12-01') to ('2112-01-01'); alter table warehouse attach partition historycal_data for values from ('2010-01-01') to ('2012-12-01'); 

一切准备就绪。 让我们尝试订购一些报告。 让我们从订购当月某一天的数据开始。

 explain (analyze, costs, buffers) select sum(m1) from warehouse where trd = '2012-12-01' and op = 1; 

总计(成本= 15203.37..15203.38行= 1宽度= 32)(实际时间= 17.320..17.320行= 1循环= 1)
-缓冲区:共享命中= 3读取= 515
--->追加(成本= 532.59..15140.89行= 24991宽度= 5)(实际时间= 1.924..13.838行= 24000循环= 1)
-------缓冲区:共享命中= 3读取= 515
--------->对operational_data进行位图堆扫描(成本= 532.59..15140.89行= 24991宽度= 5)(实际时间= 1.924..11.992行= 24000循环= 1)
---------------重新检查条件:((trd ='2012-12-01'::日期)AND(op = 1))
---------------堆块:精确= 449
---------------缓冲区:共享命中= 3读取= 515
---------------->在trd_op_ix上进行位图索引扫描(成本= 0.00..526.34行= 24991宽度= 0)(实际时间= 1.877..1.877行= 24000循环= 1 )
---------------------索引条件:((trd ='2012-12-01'::日期)AND(op = 1))
---------------------缓冲区:共享命中= 2读取= 67
计划时间:0.388毫秒
执行时间:100.941 ms
现在,我们将订购2012年全年的数据,其中交易数量为8,784,000。

 explain (analyze, costs, buffers) select sum(m1) from warehouse where trd between '2012-01-01' and '2012-12-31' and op = 1; 
总计(成本= 960685.82..960685.83行= 1宽度= 32)(实际时间= 4124.681..4124.681行= 1循环= 1)
-缓冲区:共享命中= 45591读取= 11282
--->追加(成本= 0.00..938846.60行= 8735687宽度= 5)(实际时间= 66.581..3036.394行= 8784000循环= 1)
---------缓冲区:共享命中= 45591读取= 11282
---------->对historycal_data进行外部扫描(成本= 0.00..898899.60行= 7994117宽度= 5)(实际时间= 66.579..2193.801行= 8040000循环= 1)
---------------过滤器:((trd> ='2012-01-01'::日期)AND(trd <='2012-12-31'::日期)AND (op = 1))
---------------被过滤器删除的行:8040000
--------------- CStore文件:/ var / lib / pgsql / 10 / data / cstore_fdw / 14028/16448
--------------- CStore文件大小:117401470
---------------缓冲区:共享匹配= 42966
---------->对operational_data进行Seq扫描(成本= 0.00..39947.00行= 741570宽度= 5)(实际时间= 0.019..284.824行= 744000循环= 1)
---------------过滤器:((trd> ='2012-01-01'::日期)AND(trd <='2012-12-31'::日期)AND (op = 1))
---------------筛选器删除的行:744000
---------------缓冲区:共享命中= 2625读取= 11282
计划时间:0.256毫秒
执行时间:4125.239 ms
最后,让我们看看如果用户希望(例如,在没有恶意的情况下)订购系统中所有交易的报告,其中有52 608 000。

 explain (analyze, costs, buffers) select sum(m1) from warehouse 

总计(成本= 672940.20..672940.21行= 1宽度= 32)(实际时间= 15907.886..15907.886行= 1循环= 1)
-缓冲区:共享命中= 17075读取= 11154
--->追加(成本= 0.00..541420.20行= 52608000宽度= 5)(实际时间= 0.192..9115.144行= 52608000循环= 1)
---------缓冲区:共享命中= 17075读取= 11154
---------->对historycal_data进行外部扫描(成本= 0.00..512633.20行= 51120000宽度= 5)(实际时间= 0.191..5376.449行= 51120000循环= 1)
--------------- CStore文件:/ var / lib / pgsql / 10 / data / cstore_fdw / 14028/16448
--------------- CStore文件大小:117401470
---------------缓冲区:共享匹配= 14322
---------->对operational_data进行Seq扫描(成本= 0.00..28787.00行= 1488000宽度= 5)(实际时间= 0.032..246.978行= 1488000循环= 1)
---------------缓冲区:共享命中= 2753读取= 11154
计划时间:0.157毫秒
执行时间:15908.096 ms
请注意,我仍然在写文章,好像什么也没发生。 我什至不必重新启动功能不强的HDD和4 GB RAM的笔记本电脑。 尽管资源消耗问题需要更仔细的研究。

容错能力


在撰写本文时,部分地对容错进行了测试。 我的笔记本电脑还活着,而且,除了通常的情况外,我总体上没有发现它的工作速度有任何下降。

让读者原谅我没有详细解决容错问题,但是我可以说所涉及的扩展具有容错能力-可以备份。

易于实施


事实证明,在创建以列存储格式存储数据的表时,除了压缩算法外没有其他选项。 压缩本身是绝对必要的。

格式本身具有一定的结构。 通过设置适当的参数,可以加快分析查询的速度,或者调整信息的压缩程度。

如上所示,创建列存储表根本没有麻烦。 该扩展可以使用40种PostgreSQL数据类型。 网络研讨会讨论了PostgreSQL支持的所有类型。

开发人员必须具备哪些新技能才能使用新结构


SQL开发人员不需要任何特殊技能即可将查询写入列存储表。 这样的表在所有查询中都是可见的,就像常规的基于行的表一样。 尽管这并不排除查询优化的需要。

结论


在本文中,我展示了具有列存储存储格式的表如何有用。 这样可以节省磁盘空间和高性能的分析查询。 使用表格的简便性自动降低了创建完整的分析数据仓库的成本,因为 它的使用不需要开发复杂的,难以调试的算法。 测试得以简化。

尽管上述实验激发了人们的乐观情绪,但许多问题尚未解决。 例如,当列存储表联接其他表时,将生成什么查询计划。 我希望在下一部分继续这项工作。 多少部分将取决于cstore_fdw在或多或少的实际数据上的行为。

链接到其他材料


简短回顾cstore_fdw

github上的cstore_fdw

路线图cstore_fdw

Source: https://habr.com/ru/post/zh-CN472396/


All Articles