sql - 优化 Postgres 表中的存储空间、查询速度、JSON 列数据
问题描述
考虑下表,该表记录了属于不同类别的不同公司的不同产品的价格变化。
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 值)映射到类别名称 - 100 个类别
(电子、时尚、健康、运动、玩具、书籍)
industry_id
- 一个单独的表将 id(唯一的 bigint 值)映射到行业名称 - 一个类别中的数千个行业
(诺基亚、苹果、微软、PeterEngland、Rubik、Nivia、中远)
product_id
- 一个单独的表格将 id(唯一的 bigint 值)映射到产品名称 - 一个行业中的数百万种产品
time
(unix time as 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}
以多种方式查询该表以分析产品价格在一天/周/月作为小时/天/周/月范围内变化的趋势,作为图表。趋势可能基于否。的产品,正在修改的独特产品。
例如谷歌样本趋势
按原样(as string
)存储 JSON 会占用更多存储空间。因此,我尝试在 json 中存储键值,并在单独的表中使用递增的序列 ID,并使用这些 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}
对于大约 20M 的数据集,它减少了大约 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"}
将这些十进制整数存储为十六进制整数也是如此。
- 进一步节省存储空间?(因为在视觉上它似乎被压缩了)
- JSON是保留key-value的数据类型,还是存储为字符串?
- 使数据压缩?
- 提高读取性能?
- 或者无论如何它可以改进?(索引,或任何?)
在普通的 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
在选择列中。
解决方案
如果您还提供一些您通常查询的示例,这将有所帮助。有不同的方法来优化索引/如何将数据写入磁盘,这在很大程度上取决于您正在运行的查询类型(更具体地说,您的 where 子句中有什么)?如果您使用查看 JSON 的 where 子句,您应该考虑将它们分成列,或者在 JSON 本身上构建索引。
听起来您的担忧之一是存储。因为 TimescaleDB 和 PostgreSQL 是关系型的,所以它们确实比可能具有更好压缩特性的列式存储占用更多的存储空间。您也可以考虑使用 ZFS 之类的东西来压缩内容。
推荐阅读
- javascript - 当用户点击左侧详细信息中的产品时如何创建将显示在右侧
- servicestack - ServiceStack:“Matching = ...”规则的解释
- c# - 我怎样才能有一个什么都不做的 LINQ where 子句?
- css - 为什么顺风 css 类名中有反斜杠?
- json - 匹配两个Json响应时忽略一个键
- tex - 如何在多行闭括号内居中数学表达式?
- django-rest-framework - Django Rest Framework 文档嵌套序列化程序
- batch-file - 在for循环中获取目录的最后一部分,使用cd,并在一行中
- c++ - DLL 导出类:抽象基类和 pimpl?
- amazon-web-services - AWS:有没有办法暂停/取消暂停 Elastic LoadBalancers 的流量?