如何使用大型json文档加速这个长时间运行的postgres查询?

时间:2014-07-18 21:38:45

标签: json postgresql heroku

我们将不同的市场上市产品(ebay,亚马逊)汇总到一个应用程序中。列表表格包含某些共享列:

sku,标题,价格,数量,状态等......,所有列表都是通用的......

我们利用postgres 9.3 json列并在listing表中创建了一个listing_data列来保存所有原始列表json数据,而不是创建单独的多态表来存储特定于市场的列。来自同一列表中的市场api。

显示一个帐户的所有易趣物品列表的网页会像这样进行分页查询,我们使用select来限制某些列并从嵌套的json值中深入获取一些值:

SELECT
    id,
    sku,
    title,
    price,
    quantity,
    state,
    listing_data->>'listing_type' as listing_type,
    listing_data->>'listing_duration' as listing_duration,
    json_extract_path(listing_data, 'listing_details', 'end_time') as end_time,
    listing_data->>'start_price' as start_price,
    listing_data->>'buy_it_now_price' as buy_it_now_price,
    listing_data->>'reserve_price' as reserve_price,
    json_extract_path(listing_data, 'variations', 'variations') as ebay_variations
FROM "listings"
WHERE
    "listings"."channel_id" = $1 AND
    ("listings"."state" NOT IN ('deleted', 'archived', 'importing')) AND
    "listings"."state" IN ('online')
ORDER BY created_at DESC
LIMIT 25 OFFSET 0

问题是我们看到此查询有时需要超过30秒并且在heroku上超时。我们正在使用7Gig的postgres内存进行Heroku Ika postgres计划。我们在实践中发现的是客户倾向于放置大量的HTML(甚至包括嵌入式二进制视频和Flash应用程序!),只需ebay描述可以达到500K。

这是一个解释分析输出的示例,类似于上面的select语句:

Limit  (cost=0.11..58.72 rows=25 width=205) (actual time=998.693..1005.286 rows=25 loops=1)
  ->  Index Scan Backward using listings_manager_new on listings  (cost=0.11..121084.58 rows=51651 width=205) (actual time=998.691..1005.273 rows=25 loops=1)
        Index Cond: ((channel_id = xyz) AND ((state)::text = 'online'::text) AND ((type)::text = 'ListingRegular'::text))
Total runtime: 1005.330 ms

我认为这意味着postgres正在使用索引,但仍然有很高的成本?我已经读过9.3将json存储为文本blob,因此从大型json文档中提取json数据值很昂贵,即使我们忽略了description键,也需要解析整个json文档。我们没有基于json数据进行过滤,我希望json解析成本仅与分页限制的25个结果相关联,但我不确定。

我已经阅读了一些其他的stackoverflow和博客,它们表明行的大小或“宽”表会影响性能,因为postgres查询超过8k页,而较大的行需要更多的磁盘IO才能通过几页。我不知道这是否仅适用于顺序扫描,或者也适用于使用索引。

可能可能将json列从主列表中移出并且与仅包含json的单独表有1-1 has one关联,但是这样做然后需要在查询上加入。

在我做任何事情之前,我认为我会联系并就如何分析我们的瓶颈以及可能是加速此查询的解决方案获得一些其他意见或建议。

1 个答案:

答案 0 :(得分:1)

Postgresql使用一种名为TOAST的技术来存储大型属性,否则这些大型属性太大而无法存储在页面中。这些属性存储在一行,在一个从它们所属的行引用的单独文件中。

您的JSON属性存储在一个列中,因此如果描述字段与您说的一样大,则很可能使用TOAST为许多此类行存储整个JSON数据。

如果查询完全引用此列,则需要读入整个列值,这会导致大量I / O.如果在WHERE子句中引用该列,则会产生最大的影响,但对于您显示的示例查询,情况似乎并非如此。但即使它只出现在SELECT子句中,也意味着TOAST数据必须读入所有匹配的行。

我的建议是:

  • 如果你真的不需要大量的描述字段,那么在存储到数据库之前将其过滤掉
  • 如果确实需要,请考虑将JSON数据拆分为两个字段,一列中频繁访问的字段,另一列中的字段较多,频率较低。
  • 将一个较不常用的字段放在一个单独的表中可能是个好主意。
  • 避免在WHERE子句中使用JSON字段 - 尝试使用其他列中的数据限制查询
  • 上面的内容可能会让您重新考虑原始的decstructure不使用继承的表结构,特别是如果JSON结构的组件在WHERE子句中有用。