优化Postgres表中的存储空间,查询速度和JSON列数据

时间:2018-10-24 10:59:02

标签: sql postgresql query-performance postgres-9.6 timescaledb

请考虑下表,该表记录了属于不同类别的不同公司的不同产品的价格变化。

     Column    |  Type  | Modifiers
-----------------+--------+-----------
 category_id   | bigint | not null
 product_id    | bigint | not null
 industry_id   | bigint | not null
 time          | bigint | not null
 price         | bigint | not null
 product_info  | json   | not null

Indexes:
    "price_change_pk" PRIMARY KEY, btree (category_id, product_id, price, "time")

Foreign-key constraints:
    "orders_industry_id" FOREIGN KEY (industry_id) REFERENCES industry_info(industry_id)
    "orders_product_id" FOREIGN KEY (product_id) REFERENCES device_info(product_id)
    "orders_category_id" FOREIGN KEY (categoy_id) REFERENCES category_info(category_id)

为清楚起见,列值将为:

category_id-一个单独的表将具有映射到类别名称的ID(唯一的bigint值)-类别的100s

(电子,时尚,健康,运动,玩具,书籍)

industry_id-一个单独的表将具有映射到行业名称的id(唯一的bigint值)-一个类别中有数千个行业

(诺基亚,苹果,微软,PeterEngland,Rubik,Nivia,中远)

product_id-一个单独的表将具有映射到产品名称的id(唯一的bigint值)-一个行业中数以百万计的产品

time(unix时间为bigint)-修改价格的时间,

price-几千个不同的值-(200、10000、14999、30599、450)

product_info-包含产品额外信息的json(键/值对的数量可能会有所不同)

{seller:"ABC Assured", discount:10, model:XYZ, EMIoption:true, EMIvalue:12, festival_offer:28, market_stat:comingsoon}

以多种方式查询该表,以图表的形式分析日/周/月,小时/天/周/月范围内的产品价格变化趋势。趋势可能基于否。产品,正在修改的独特产品。

例如Google Sample Trend

按原样存储JSON(如string)会占用更多存储空间。因此,我尝试将键值和递增的序列ID存储在json中的单独表中,并使用这些ID。

喜欢

Keys (citext, bigint)
seller - 1
discount - 2
model - 3
EMIoption - 4
EMIvalue - 5
festival_offer - 6
...
...
currency - 25

Values (citext, bigint)
ABC Assured - 1
10 - 2
XYZ - 3
true - 4
12 - 5
28 - 6
comingsoon - 7
...
...
ZYX - 106
rupees - 107
american dollars - 108
canadian dollars - 109
Prime seller - 110

{seller:"ABC Assured", discount:10, model:XYZ, EMIoption:true, EMIvalue:12, festival_offer:28, market_stat:comingsoon, curreny: rupees}

成为

{"1":1, "2":2", "3":3, "4":4, "5":5, "6":6, "7":7, "25":107}


{seller:"Prime seller", discount:10, model:XYZ, EMIoption:true, EMIvalue:12, festival_offer:28, market_stat:comingsoon, curreny: "canadian dollars"}

成为

{"1":110, "2":2", "3":3, "4":4, "5":5, "6":6, "7":7, "25":109}


对于大约2000万个数据集,它减少了约1.5GB。

增加键值基数,增加序列号。所以我尝试将十进制存储为十六进制。

{"1":1, "2":2", "3":3, "4":4, "5":5, "6":6, "7":7, "25":107}

成为

{"1":1, "2":2", "3":3, "4":4, "5":5, "6":6, "7":7, "19":"6B"}


{"1":110, "2":2", "3":106, "4":4, "5":5, "6":6, "7":7, "25":109}

成为

{"1":, "2":2", "3":"6A", "4":4, "5":5, "6":6, "7":7, "19":"6D"}


将这些十进制整数存储为十六进制整数也是如此。

  1. 进一步节省存储空间? (因为在视觉上似乎已压缩)
  2. JSON是否保留键值的数据类型,或者将它们存储为字符串?
  3. 要压缩数据吗?
  4. 提高阅读性能?
  5. 还是无论如何都可以改进? (编制索引,还是其他?)

在普通的psql应用程序中,查询需要几分钟才能完成。由于它符合时间序列数据,因此我们使用TimescaleDB扩展,它的分片机制可以提高查询的执行速度,但我们需要几秒钟的结果。

查询样本: 要查看将给定类别中的所有产品每天按月分组的价格更改为500次的次数。

select count(*), to_char(date_trunc('day', to_timestamp(time/1000) at time zone 'Asia/Kolkata'), 'YYYY/MM/DD') as unit, price 
from price_change 
where category_id = 1000000010 and time between 1514745000000 and 1517423400000 
  and price = 500 
group by price, unit;

要查看在过去10个月的每个月中,给定类别中所有产品的价格更改为(100,200,300,400,500,600,700,800,900,1000)中的任何一个的次数。

select count(*), to_char(date_trunc('month', to_timestamp(time/1000) at time zone 'Asia/Kolkata'), 'YYYY/MM/DD') as unit, price 
from price_change 
where category_id = 1000000010 and time between  1514745000000 and 1517423400000  
   and price in (100,200,300,400,500,600,700,800,900,1000) group by price, unit;

要选择在给定时间范围内在给定类别中价格已更改的产品详细信息

select product_id, product_name, price, to_char(date_trunc('day', to_timestamp(time/1000) at time zone 'Asia/Kolkata'), 'YYYY/MM/DD') as timestamp 
from price_change 
  join products using product_id 
where price_change.category_id = 1000000010 
  and price_change.time between 1514745000000 and 1517423400000;

要在给定的时间范围内选择给定类别中价格已更改的行业和产品ID详细信息

select industry_id, product_id, price 
from price_change 
  join industries using industry_id 
where price_change.category_id = 1000000010 
  and price_change.time between 1514745000000 and 1517423400000;

要在特定类别的折扣为10%的时间范围内选择产品价格变化的详细信息

select product_id, product_name, price, to_char(date_trunc('day', to_timestamp(time/1000) at time zone 'Asia/Kolkata'), 'YYYY/MM/DD') as timestamp 
from price_change 
  join products using product_id 
where price_change.category_id = 1000000010 
  and price_change.time between 1514745000000 and 1517423400000
  and product_info->>'discount'=10;

要选择特定卖家在特定类别销售的时间范围内的产品价格更改明细

select product_id, product_name, price, to_char(date_trunc('day', to_timestamp(time/1000) at time zone 'Asia/Kolkata'), 'YYYY/MM/DD') as timestamp 
from price_change 
  join products using product_id 
where price_change.category_id = 1000000010 
  and price_change.time between 1514745000000 and 1517423400000
  and product_info->>'seller'='ABC Assured';

在大多数情况下,查询的选择列中将不包含category_id

1 个答案:

答案 0 :(得分:0)

如果您还提供一些有关通常查询内容的示例,则将有所帮助。有多种优化索引的方法/如何将数据写入磁盘的方式很大程度上取决于您正在运行的查询类型(更具体地说,where子句中的查询)?如果您使用的是用于JSON的where子句,则应考虑将其分解为列,或在JSON本身上建立索引。

听起来您的关注点之一是存储。因为TimescaleDB和PostgreSQL是关系型的,所以它们占用的存储空间比可能具有更好压缩特性的列式存储空间要多。您可以考虑使用ZFS之类的东西来压缩东西。