6

1,000 バッチでサーバーから約 20,000 データセットをフェッチします。各データセットはJSON オブジェクトです。これを永続化すると、約 350 MB の圧縮されていないプレーンテキストが作成されます。

1GBのメモリ制限があります。したがって、1,000 個の JSON オブジェクトごとに配列として生の JSONファイルに追加モードで書き込みます。

結果は、集約する必要がある 20 個の JSON 配列を含むファイルです。メタデータを追加したいので、とにかくそれらに触れる必要があります。一般に、Ruby Yajl パーサーは次のようにこれを可能にします。

raw_file = File.new(path_to_raw_file, 'r')
json_file = File.new(path_to_json_file, 'w')

datasets = []
parser = Yajl::Parser.new
parser.on_parse_complete = Proc.new { |o| datasets += o }

parser.parse(datasets)

hash = { date: Time.now, datasets: datasets }
Yajl::Encoder.encode(hash, json_file)

このソリューションのどこに問題がありますか? 問題は、まだ JSON 全体がメモリに解析されていることです。これは避けなければなりません。

基本的に必要なのは、IO オブジェクトから JSON を解析し、同時に別の IO オブジェクトにエンコードするソリューションです。

Yajl がこれを提供していると思いましたが、方法が見つからず、その API からヒントも得られなかったので、そうではないと思います。これをサポートする JSON パーサー ライブラリはありますか? 他の解決策はありますか?


私が考えることができる唯一の解決策は、IO.seek機能を使用することです。すべてのデータセット配列を次々に書き込み、すべての配列の後[...][...][...]、先頭に戻って で上書き][,、配列を手動で効果的に接続します。

4

3 に答える 3

5

データベースから一度に 1 つのレコードを取得し、必要に応じて処理し、JSON に変換してから、末尾/区切りのカンマを付けて出力できないのはなぜですか?

のみを含むファイルで開始し、[すべての JSON 文字列を追加した場合、最後のエントリにカンマを追加せず、代わりに終了を使用すると]、ハッシュの JSON 配列が得られ、一度に 1 行分を処理します。

少し遅くなりますが (おそらく)、システムには影響しません。また、ブロッキング/ページングを使用して一度に適切な数のレコードを取得すると、DB I/O は非常に高速になります。

たとえば、次の Sequel サンプル コードと、行を JSON として抽出し、より大きな JSON 構造を構築するコードを組み合わせたものを次に示します。

require 'json'
require 'sequel'

DB = Sequel.sqlite # memory database

DB.create_table :items do
  primary_key :id
  String :name
  Float :price
end

items = DB[:items] # Create a dataset

# Populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)

add_comma = false

puts '['
items.order(:price).each do |item|
  puts ',' if add_comma
  add_comma ||= true
  print JSON[item]
end
puts "\n]"

どの出力:

[
{"id":2,"name":"def","price":3.714714089426208},
{"id":3,"name":"ghi","price":27.0179624376119},
{"id":1,"name":"abc","price":52.51248221170203}
]

注文が「価格」順になっていることに注意してください。

検証は簡単です:

require 'json'
require 'pp'

pp JSON[<<EOT]
[
{"id":2,"name":"def","price":3.714714089426208},
{"id":3,"name":"ghi","price":27.0179624376119},
{"id":1,"name":"abc","price":52.51248221170203}
]
EOT

結果は次のとおりです。

[{"id"=>2, "name"=>"def", "price"=>3.714714089426208},
 {"id"=>3, "name"=>"ghi", "price"=>27.0179624376119},
 {"id"=>1, "name"=>"abc", "price"=>52.51248221170203}]

これにより、JSON が検証され、元のデータが回復可能であることが示されます。データベースから取得した各行は、構築する JSON 構造全体の最小の「バイトサイズ」の部分である必要があります。

それに基づいて、データベース内の着信 JSON を読み取り、操作し、JSON ファイルとして出力する方法は次のとおりです。

require 'json'
require 'sequel'

DB = Sequel.sqlite # memory database

DB.create_table :items do
  primary_key :id
  String :json
end

items = DB[:items] # Create a dataset

# Populate the table
items.insert(:json => JSON[:name => 'abc', :price => rand * 100])
items.insert(:json => JSON[:name => 'def', :price => rand * 100])
items.insert(:json => JSON[:name => 'ghi', :price => rand * 100])
items.insert(:json => JSON[:name => 'jkl', :price => rand * 100])
items.insert(:json => JSON[:name => 'mno', :price => rand * 100])
items.insert(:json => JSON[:name => 'pqr', :price => rand * 100])
items.insert(:json => JSON[:name => 'stu', :price => rand * 100])
items.insert(:json => JSON[:name => 'vwx', :price => rand * 100])
items.insert(:json => JSON[:name => 'yz_', :price => rand * 100])

add_comma = false

puts '['
items.each do |item|
  puts ',' if add_comma
  add_comma ||= true
  print JSON[
    JSON[
      item[:json]
    ].merge('foo' => 'bar', 'time' => Time.now.to_f)
  ]
end
puts "\n]"

生成するもの:

[
{"name":"abc","price":3.268814929005337,"foo":"bar","time":1379688093.124606},
{"name":"def","price":13.871147312377719,"foo":"bar","time":1379688093.124664},
{"name":"ghi","price":52.720984131655676,"foo":"bar","time":1379688093.124702},
{"name":"jkl","price":53.21477190840114,"foo":"bar","time":1379688093.124732},
{"name":"mno","price":40.99364022416619,"foo":"bar","time":1379688093.124758},
{"name":"pqr","price":5.918738444452265,"foo":"bar","time":1379688093.124803},
{"name":"stu","price":45.09391752439902,"foo":"bar","time":1379688093.124831},
{"name":"vwx","price":63.08947792357426,"foo":"bar","time":1379688093.124862},
{"name":"yz_","price":94.04921035056373,"foo":"bar","time":1379688093.124894}
]

タイムスタンプを追加して、各行が個別に処理されていることを確認できるようにし、の処理速度を把握できるようにしました。確かに、これは小さなメモリ内データベースであり、コンテンツへのネットワーク I/O はありませんが、適切な DB ホスト上のデータベースへのスイッチを介した通常のネットワーク接続も非常に高速です。ORM に DB をチャンクで読み取るように指示すると、DBM はより大きなブロックを返してパケットをより効率的に満たすことができるため、処理を高速化できます。必要なチャンクのサイズは、ネットワーク、ホスト、およびレコードのサイズによって異なるため、実験して決定する必要があります。

エンタープライズ規模のデータベースを扱う場合、特にハードウェア リソースが限られている場合、元の設計は適切ではありません。何年にもわたって、20,000 行のテーブルが非常に小さいように見える BIG データベースを解析する方法を学びました。最近では VM スライスが一般的であり、それらをクランチに使用しているため、往年の PC であることが多く、メモリ フットプリントが小さく、小さなドライブを備えた単一の CPU です。それらを打ち負かすことはできません。そうしないとボトルネックになるため、データを可能な限り最小のアトミックピースに分割する必要があります。

DB 設計についての口論: JSON をデータベースに格納することは、疑わしい方法です。最近の DBM は行の JSON、YAML、および XML 表現を吐き出すことができますが、保存された JSON、YAML、または XML 文字列内を DBM に強制的に検索させることは、処理速度に大きな影響を与えるため、同等のルックアップ データも持っていない限り、絶対に避けてください。別々のフィールドに索引付けされているため、検索が可能な限り高速になります。データが別々のフィールドで利用できる場合、古き良きデータベースクエリを実行し、DBM または選択したスクリプト言語を調整し、メッセージ化されたデータを出力することがはるかに簡単になります。

于 2013-09-19T19:35:08.100 に答える
1

JSON::StreamまたはYajl::FFI gemsを介して可能です。ただし、独自のコールバックを作成する必要があります。それを行う方法に関するいくつかのヒントは、ここここにあります。

同様の問題に直面して、独自のコールバックを作成する必要をなくす json-streamer gem を作成しました。後でメモリから削除して、各オブジェクトを1つずつ生成します。その後、意図したとおりにこれらを別の IO オブジェクトに渡すことができます。

于 2016-05-29T00:39:19.723 に答える