使用V8和PostgreSQL进行Javascript序列化和性能优化

27

我一直在尝试使用PostgreSQL和PL/V8,它将V8 JavaScript引擎嵌入到PostgreSQL中。使用此方法,我可以在数据库内查询JSON数据,这相当不错。

基本方法如下:

CREATE or REPLACE FUNCTION 
  json_string(data json, key text) RETURNS TEXT AS $$
  var data = JSON.parse(data); 
  return data[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;

SELECT id, data FROM things WHERE json_string(data,'name') LIKE 'Z%';
使用V8,我可以将JSON数据解析成JS,然后返回一个字段,我可以将其用作常规的pg查询表达式。
但是,在大型数据集上,性能可能会成为问题,因为对于每一行都需要解析数据。解析器很快,但绝对是该过程中最慢的部分,并且每次都必须发生。
我正在尝试解决的问题(最终得到实际问题)是是否有一种方法可以缓存或预处理JSON...甚至在表格中存储JSON的二进制表示形式,可以被V8自动用作JS对象,这可能会更好。我已经研究过使用messagepack或protobuf等替代格式,但我认为它们不一定比本地JSON解析器快。
PG具有blob和二进制类型,因此数据可以以二进制形式存储,然后我们只需要一种方式将其转换为V8。

应该是 return data[key];,对吧? - JMM
听起来你真正想要的是Pg能够以高效的v8兼容形式本地存储json字段,而不需要解析。目前,Pg并不支持使用最新的json字段实现这一点。如果这就是你想要的,那么你需要做的第一件事就是研究v8,看看是否可以找到对json进行高性能二进制序列化和反序列化的支持,因为如果没有这个功能,你将无法实现你的需求。 - Craig Ringer
2
如果性能是一个问题,为什么不仅将JSON用于通信,并将数据保存到常规表中呢? - Gerardo Lima
5个回答

12

Postgres支持对任意函数调用建立索引。以下索引应该可以解决问题:

CREATE INDEX json_idx ON things (json_string(field,'name'));

这绝对可行,我正在使用它,但为每个字段添加索引可能会有问题,提高非索引字段的性能将使支持特定查询成为可能。 - Toby Hede
1
@TobyHede,您会很高兴知道,正在进行工作以统一hstore和json支持,添加支持json表示、测试、索引等的hstore类型。希望在9.4版本中实现。 - Craig Ringer

7
简而言之,Pg的新json支持目前似乎没有直接存储json的方法,除了序列化的json文本。(这看起来可能会在9.4版本中改变)
您似乎想要存储一个预解析的形式,即v8如何在内存中表示json的序列化表示形式,但当前不支持。甚至不清楚v8是否提供任何类型的二进制序列化/反序列化json结构。如果它不是原生的,就需要添加代码到Pg中以生成这样的表示,并将其转换回v8 json数据结构。
它也不一定更快:
- 如果json以v8特定的二进制形式存储,则每次返回正常的json表示给客户端的查询都必须对其进行格式化,从而产生CPU成本。 - json的二进制序列化版本与直接在内存中存储v8 json数据结构并不相同。您无法将涉及任何指针图形的数据结构直接写入磁盘,必须进行序列化。这种序列化和反序列化具有成本,而且它甚至可能比解析json文本表示形式更快。这在很大程度上取决于v8如何在内存中表示JavaScript对象。 - 由于大多数json都是文本和小数字,因此二进制序列化表示形式很容易变得更大。由于存储大小直接影响表扫描的速度,从TOAST获取值的时间,TOAST值所需的解压缩时间,索引大小等等,您可能会得到更慢的查询和更大的表。
我很想知道是否可能实现您所描述的优化,并且它是否真正是一种优化方法。
为了在进行表扫描时获得所需的好处,我想您真正需要的是可以遍历而无需解析并将其转换为可能是malloc()的javascript对象图形的格式。您希望能够为字段提供路径表达式,并直接从已读取到Pg读取缓冲区或共享缓冲区中的序列化形式中抓取它。这将是一个非常有趣的设计项目,但我会惊讶如果v8中存在类似的东西。
您真正需要做的是研究现有基于json的对象数据库如何快速搜索任意json路径以及它们的磁盘表示形式,然后回报pgsql-hackers。也许可以从已经解决此问题的人们那里学到一些东西 - 假设他们已经解决了这个问题。
与此同时,我想要关注其他答案正在做什么:绕过慢点并找到其他完成所需任务的方法。您也可以尝试优化json解析器,但这取决于使用的是v8还是其他解析器,如果已经超过了收益递减点,那么优化可能已经无济于事。

我想这是速度和灵活的数据表示之间需要权衡的领域之一。


谢谢您的评论。我也遇到了同样的问题,现在我意识到存储“JSON的二进制版本”并不能一定使得事情更快。 - Saurabh Nanda

1

我对这个没有任何经验,但它引起了我的好奇心,所以我做了一些阅读。

仅限JSON

以下是一个类似的东西(未经测试)。它并没有解决你关于存储JSON二进制表示的问题,而是尝试一次性解析你正在检查的所有行的所有JSON,希望通过减少为每一行单独处理的处理开销来提高性能。如果成功了,我认为它可能会导致更高的内存消耗。

CREATE TYPE...set_of_records() 的内容是从wiki上的示例中改编而来,其中提到“您还可以返回具有JSON数组的记录。” 我想它真正意味着“对象数组”。

DB记录中的id值是否嵌入在JSON中?

版本#1

CREATE TYPE rec AS (id integer, data text, name text);

CREATE FUNCTION set_of_records() RETURNS SETOF rec AS
$$

  var records = plv8.execute( "SELECT id, data FROM things" );

  var data = [];


  // Use for loop instead if better performance

  records.forEach( function ( rec, i, arr ) {

    data.push( rec.data );

  } );

  data = "[" + data.join( "," ) + "]";

  data = JSON.parse( data );


  records.forEach( function ( rec, i, arr ) {

    rec.name = data[ i ].name;

  } );


  return records;

$$
LANGUAGE plv8;


SELECT id, data FROM set_of_records() WHERE name LIKE 'Z%'

版本 #2

这个版本使用Postgres来聚合/连接一些值,以减少在JS中进行的处理。

CREATE TYPE rec AS (id integer, data text, name text);

CREATE FUNCTION set_of_records() RETURNS SETOF rec AS
$$

  var cols = plv8.execute(

    "SELECT" +

    "array_agg( id ORDER BY id ) AS id," +

    "string_agg( data, ',' ORDER BY id ) AS data" +

    "FROM things"

  )[0];


  cols.data = JSON.parse( "[" + cols.data + "]" );


  var records = cols.id;


  // Use for loop if better performance

  records.forEach( function ( id, i, arr ) {

    arr[ i ] = {

      id : id,

      data : cols.data[ i ],

      name : cols.data[ i ].name

    };

  } );


  return records;

$$
LANGUAGE plv8;


SELECT id, data FROM set_of_records() WHERE name LIKE 'Z%'

hstore

如果将JSON数据在写入时复制到hstore列(或者如果性能足够好,将JSON转换为hstore在选择时),并在WHERE中使用hstore,那么它的性能如何比较?例如:

SELECT id, data FROM things WHERE hstore_data -> name LIKE 'Z%'

我从这里听说了hstore: http://lwn.net/Articles/497069/

文章提到了一些其他有趣的事情:

PL/v8让您...在特定的JSON元素上创建表达式索引并保存它们,为您提供存储搜索索引,就像CouchDB的“视图”一样。

它没有详细说明,我不太清楚它指的是什么。

有一个归属于“jberkus”的评论说:

我们讨论过也要有二进制JSON类型,但是没有协议来传输二进制值(BSON根本不是标准,并且存在一些严重的缺陷),似乎没有任何意义。

如果您有兴趣为PostgreSQL开发二进制JSON支持,我们很乐意提供帮助...


hstore非常棒,但不像json那样具有任意深度。 - Toby Hede
“创建表达式索引”基本上就是我正在做的事情,你可以在一个函数上创建索引(详见@limscoder的回答)。 - Toby Hede
@Toby "hstore还是很棒的,但是……" 是啊,太糟糕了,不确定这是否会对你造成影响。 "你可以在函数上创建索引。" 是的,我还没有使用过Postgres,所以直到看到limscoder的答案之前,我不知道你可以这样做。这非常流畅。起初我没有马上想到,但后来发现那是我链接的文章所讨论的内容。一开始我以为它在讲述PL/V8特定的某些东西。 - JMM
你认为一次性解析JSON有什么好处吗?可能不用说,但是(假设我建议的设置基本上有效),我认为set_of_records()可以被参数化以限制查询并动态选择从JSON中提取哪些元素。 - JMM

1

但在这种情况下,JSON将如何存储?在PG9.2中,JSON数据类型实际上只是一个带有验证的文本字段,因此为了与V8一起使用,您需要解析数据。 - Toby Hede
问题实际上在于如何将 JSON 存储为本地/二进制的“预解析”格式? - Toby Hede
我更倾向于将可索引属性存储在某些内部数据结构中,然后后续的搜索不会直接操作JSON,而是操作索引内容(可能是非规范化的)。http://wiki.postgresql.org/wiki/XML_Support 但不要指望我已经全部阅读了它 :)你看过XML数据类型吗?http://www.postgresql.org/docs/current/static/datatype-xml.html不确定它在幕后是如何实现的,但我想到的是将JSON内容映射到SQL数据库中(但上面没有提到)。 - Jason Dwyer

0

我不知道这里是否有用,但我发现了这个:pg-to-json-serializer。它提到了以下功能:

解析JSON字符串并从中填充postgreSQL记录/数组

我不知道它是否比你迄今为止所做的带来任何性能优势,而且我甚至不理解他们的示例。

只是觉得值得一提。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接