块级(ctid)扫描在IoT(物联网)极限写和消费读并存场景的应用

标签

PostgreSQL , 块扫描 , 行号扫描 , ctid , tid scan , IoT , 物联网 , 极限写入 , 实时消费 , 实时读 , 堆表 , heap , 时序


背景

在物联网有一个非常普遍的数据需求,就是数据的写入,另一个普遍的需求则是数据的消费(按时序读取),以及流式计算。

关于流式计算,请参考

《(流式、lambda、触发器)实时处理大比拼 - 物联网(IoT)\金融,时序处理最佳实践》

《流计算风云再起 - PostgreSQL携PipelineDB力挺IoT》

《"物联网"流式处理应用 - 用PostgreSQL实时处理(万亿每天)》

接下来我们谈一谈极限写入和消费。

写入

从数据存储结构来看,PostgreSQL的HEAP存储是非常适合高速写入的,追加式写入。以下文章中已得到高速写入的验证。

《PostgreSQL 如何潇洒的处理每天上百TB的数据增量》

块(时序列)索引

BRIN索引,也被称为块索引,是针对数据块元数据建立的索引(例如某个自增长字段,物理存储和字段的值存在很好的线性相关性,那么每个块的数据区间就具有非常强的独立性),BRIN索引非常小,对写入性能的影响可以忽略。

BRIN适合物理存储和字段的值存在很好的线性相关性的字段,例如时序字段。

或者使用cluster或order 重排后,适合对应字段。

消费

消费是指异步的读取数据,处理数据的过程,例如IoT场景,数据的写入延迟要求非常低,所以要求写入吞吐特别大。

而处理方面,则通过消费机制,进行处理。

那么如何消费呢?

通常可以根据索引进行消费,比如前面提到的BRIN索引,对写入吞吐的影响小,同时支持=,以及范围的检索。如果有时序字段的话,BRIN是非常好的选择。

然而并非所有的数据写入场景都有时序字段(当然用户可以添加一个时间字段来解决这个问题)。当没有时序字段时,如何消费效率最高呢?

块扫描

块扫描是很好的选择,前面提到了数据存储是HEAP,追加形式。

PostgreSQL提供了一种tid scan的扫描方法,告诉数据库你要搜索哪个数据块的哪条记录。

select * from tbl where ctid='(100,99)';

这条SQL指查询100号数据块的第100条记录。

这种扫描效率非常之高,可以配合HEAP存储,在消费(读取记录)时使用。

评估块记录数

PostgreSQL暂时没有提供返回整个数据块的所有记录的接口,只能返回某个数据块的某一条记录,所以如果我们需要读取某个数据块的记录,需要枚举该数据块的所有行。

如何评估一个数据块有多少条记录,或者最多有多少条记录?

PAGE layout

https://www.postgresql.org/docs/10/static/storage-page-layout.html

HeapTupleHeaderData Layout

Field Type Length Description
t_xmin TransactionId 4 bytes
t_xmax TransactionId 4 bytes delete XID stamp
t_cid CommandId 4 bytes insert and/or delete CID stamp (overlays with t_xvac)
t_xvac TransactionId 4 bytes XID for VACUUM operation moving a row version
t_ctid ItemPointerData 6 bytes current TID of this or newer row version
t_infomask2 uint16 2 bytes number of attributes, plus various flag bits
t_infomask uint16 2 bytes various flag bits
t_hoff uint8 1 byte offset to user data

Overall Page Layout

Item Description
PageHeaderData 24 bytes long. Contains general information about the page, including free space pointers.
ItemIdData Array of (offset,length) pairs pointing to the actual items. 4 bytes per item.
Free space The unallocated space. New item pointers are allocated from the start of this area, new items from the end.
Items The actual items themselves.
Special space Index access method specific data. Different methods store different data. Empty in ordinary tables.

单页最大记录数估算

最大记录数=block_size/(ctid+tuple head)=block_size/(4+27);

postgres=# select current_setting('block_size');
 current_setting
-----------------
 32768
(1 row)  

postgres=# select current_setting('block_size')::int/31;
 ?column?
----------
     1057
(1 row)

如果需要评估更精确的行数,可以加上字段的固定长度,变长字段的头(4BYTE)。

例子

生成指定block TID的函数

create or replace function gen_tids(blkid int) returns tid[] as $$
select array(
  SELECT ('('||blkid||',' || s.i || ')')::tid
    FROM generate_series(0,current_setting('block_size')::int/31) AS s(i)
)  ;
$$ language sql strict immutable;

读取某个数据块的记录

postgres=# create table test(id int);
CREATE TABLE
postgres=# insert into test select generate_series(1,10000);
INSERT 0 10000  

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from test where ctid = any
(
  array
  (
    SELECT ('(0,' || s.i || ')')::tid
      FROM generate_series(0, current_setting('block_size')::int/31) AS s(i)
  )
);
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Tid Scan on postgres.test  (cost=25.03..40.12 rows=10 width=4) (actual time=0.592..0.795 rows=909 loops=1)
   Output: test.id
   TID Cond: (test.ctid = ANY ($0))
   Buffers: shared hit=1057
   InitPlan 1 (returns $0)
     ->  Function Scan on pg_catalog.generate_series s  (cost=0.01..25.01 rows=1000 width=6) (actual time=0.087..0.429 rows=1058 loops=1)
           Output: ((('(0,'::text || (s.i)::text) || ')'::text))::tid
           Function Call: generate_series(0, ((current_setting('block_size'::text))::integer / 31))
 Planning time: 0.106 ms
 Execution time: 0.881 ms
(10 rows)
postgres=# explain (analyze,verbose,timing,costs,buffers) select * from test where ctid = any(gen_tids(1));  

 Tid Scan on postgres.test  (cost=1.32..1598.90 rows=1058 width=4) (actual time=0.026..0.235 rows=909 loops=1)
   Output: id
   TID Cond: (test.ctid = ANY ('{"(1,0)","(1,1)","(1,2)","(1,3)","(1,4)","(1,5)","(1,6)","(1,7)","(1,8)","(1,9)","(1,10)","(1,11)","(1,12)","(1,13)","(1,14)","(1,15)","(1,16)","(1,17)","(1,18)","(1,19)","(1,20)","(1,21)","(1,22)","(1,23)
","(1,24)","(1,25)"
....
   Buffers: shared hit=1057
 Planning time: 1.084 ms
 Execution time: 0.294 ms
(6 rows)
postgres=# select ctid,* from test where ctid = any(gen_tids(11));
  ctid  |  id
--------+-------
 (11,1) | 10000
(1 row)

postgres=# select ctid,* from test where ctid = any(gen_tids(9));
  ctid   |  id
---------+------
 (9,1)   | 8182
 (9,2)   | 8183
 (9,3)   | 8184
 (9,4)   | 8185
 (9,5)   | 8186
 (9,6)   | 8187
 ...
 (9,904) | 9085
 (9,905) | 9086
 (9,906) | 9087
 (9,907) | 9088
 (9,908) | 9089
 (9,909) | 9090
(909 rows)

扩展场景

如果数据没有更新,删除;那么CTID还可以作为索引来使用,例如全文检索(ES),可以在建立索引时使用ctid来指向数据库中的记录,而不需要另外再建一个PK,也能大幅度提升写入性能。

参考

https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/

https://www.postgresql.org/message-id/flat/be64327d326568a3be7fde1891ed34ff.squirrel%40sq.gransy.com#be64327d326568a3be7fde1891ed34ff.squirrel@sq.gransy.com

时间: 2024-11-01 16:29:37

块级(ctid)扫描在IoT(物联网)极限写和消费读并存场景的应用的相关文章

流计算风云再起 - PostgreSQL携PipelineDB力挺IoT(物联网), 大幅提升性能和开发效率

标签 PostgreSQL , pipelinedb , 流计算 , patch , bug , libcheck , zeromq , kafka , kinesis , IoT , 物联网 背景 pipelinedb是基于PostgreSQL的一个流式计算数据库,纯C代码,效率极高(32c机器,单机日处理流水达到了250.56亿条).同时它具备了PostgreSQL强大的功能基础,正在掀起一场流计算数据库制霸的腥风血雨. 在物联网(IoT)有非常广泛的应用场景,越来越多的用户开始从其他的流计

流计算风云再起 - PostgreSQL携PipelineDB力挺IoT(物联网)

标签 PostgreSQL , pipelinedb , 流计算 , patch , bug , libcheck , zeromq , kafka , kinesis , IoT , 物联网 背景 pipelinedb是基于PostgreSQL的一个流式计算数据库,纯C代码,效率极高(32c机器,单机日处理流水达到了250.56亿条).同时它具备了PostgreSQL强大的功能基础,正在掀起一场流计算数据库制霸的腥风血雨. 在物联网(IoT)有非常广泛的应用场景,越来越多的用户开始从其他的流计

PostgreSQL 透明加密(TDE,FDE) - 块级加密

PostgreSQL 透明加密(TDE,FDE) - 块级加密 作者 digoal 日期 2016-10-31 标签 PostgreSQL , 透明加密 , TDE , FDE , 块级加密 背景 在数据库应用中,为了提高数据的安全性,可以选择很多中安全加固的方法. 例如, 1. 可以对敏感字段进行加密,可以使用服务端加密,也可以使用数据库端加密,例如pgcrypto加密插件. 服务端加解密相对来说比较安全,因为在数据库端存储的是加密后的数据,只要服务端的秘钥保护好,基本上数据都是安全的. 但是

[HTML/CSS]盒子模型,块级元素和行内元素

目录 概述 盒子模型 块级元素 行内元素 可变元素 总结 概述 在div+css中,了解块级元素和行内元素还是非常有必要的,比如:对行内元素使用width属性就会失效.虽然自己不是做前端的,但是,在项目中,曾经也弄过从前端布局,也吃过这方面的亏.今天,群里有朋友问起这个,就趁着学习一下,也算是查漏补缺吧,虽然,谈不上精通,但是了解,还是很有必要的. 盒子模型 css盒子模型分为两种,一种是遵循w3c标准的标准盒子模型,另外一种就是IE盒子模型. 标准盒子模型 IE盒子模型 通过上面两张图可以看出

CSS网页制作教程:display属性行内元素和块级元素

文章简介:内联(行内)元素.块级元素区别. A:行内就是在一行内的元素,只能放在行内:块级元素,就是一个四方块,可以放在页面上任何地方. B:说白了,行内元素就好像一个单词:块级元素就好像一个段落,如果不另加定义的话,它将独立一行出现.C:一般的块级元素诸如段落<p>.标题<h1><h2>....列表,<ul><ol><li> .表格<table>.表单<form>.DIV<div>和BODY<

CSS常见内联元素和块级元素

[块元素(block element) ] * address - 地址 * blockquote - 块引用 * center - 举中对齐块 * dir - 目录列表 * div - 常用块级容易,也是css layout的主要标签 * dl - 定义列表 * fieldset - form控制组 * form - 交互表单 * h1 - 大标题 * h2 - 副标题 * h3 - 3级标题 * h4 - 4级标题 * h5 - 5级标题 * h6 - 6级标题 * hr - 水平分隔线 *

HTML5在a标签内放置块级元素示例代码

对比起XHTML来说,HTML5通过更简单的元素引起了一系列的思考,坦诚地讲,这真的是急需的简化.这些简化之一就是能够通过<a>标签 包装像div,h标签(h1...h6),和段落标记P 这些块级元素.你没看错:用一个A标签包裹块级元素(译者注: 在HTML5以前,a标签属于行内元素,而div,h1,p 等为块级元素,如果强行包裹,可能会被强行截断为多个a标签.当然,都是浏览器的事啦.). 示例代码:   复制代码 代码如下: <body> <a href="/ab

JavaScript的作用域和块级作用域概念理解

  作用域永远都是任何一门编程语言中的重中之重,因为它控制着变量与参数的可见性与生命周期.讲到这里,首先理解两个概念:块级作用域与函数作用域. 什么是块级作用域呢? 任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域. 函数作用域就好理解了(*^__^*) ,定义在函数中的参数和变量在函数外部是不可见的. 大多数类C语言都拥有块级作用域,JS却没有.请看下文demo: //C语言 #include void main() { int

【原】如何实现IE6下块级元素的内容自动收缩

近期在做提示层组件的开发,遇到了一个IE6常见的bug....想出了几个解决的办法,挺有意思的,这里分享给大家. 由于IE6浏览器中,display:inline-block只能触发IE的haslayout属性使得元素具有布局属性,当对div元素使用display:inline-block,div元素仍然为块状布局而占据一行. 那么如何在IE6下使得块级元素的内容自动收缩呢?开发圆角小提示层模块,让它来解答这个问题. 结构和样式: /** * @name : prompt * @explain