0

フィールドに入力してすべてのドキュメントを変更したい大きなコレクションがあります。

簡単な例は、各投稿のコメント数をキャッシュすることです:

class Post
  field :comment_count, type: Integer
  has_many :comments
end
class Comment
  belongs_to :post
end

次のようなものでシリアルで実行できます。

Post.all.each do |p|
  p.udpate_attribute :comment_count, p.comments.count
end

ただし、実行に 24 時間かかります (大規模なコレクション)。mongo の map/reduce をこれに使用できるかどうか疑問に思っていましたか? しかし、私はまだ素晴らしい例を見たことがありません。

コメント コレクションをマッピングし、削減された結果を投稿コレクションに保存することを想像します。私は正しい軌道に乗っていますか?

4

1 に答える 1

0

MongoDB の map/reduce を使用してデータの移行を「支援」できますが、残念ながら完全にサーバー側の移行を行うために使用することはできません。あなたは正しい軌道に乗っています。基本的な考え方は次のとおりです。

  1. 各コメントを emit(post_id, {comment_count: 1}) にマップ ---> {_id: post_id, value: {comment_count: 1}}
  2. reduce to value {comment_count: N} N は count-sum ---> {_id: post_id, value: {comment_count: N}}
  3. 出力オプション {reduce: 'posts'} を指定して、map/reduce comment_counts の結果を posts コレクションに戻します。

ある程度大規模な調査を行った結果、それに近づくことができることがわかりましたが、完全なサーバー側の移行を妨げる問題があります。reduce の結果は {_id: KEY, value: MAP_REDUCE_VALUE} の形になります。今のところ、この形状に固執しています。これを回避する方法はないようです。したがって、reduce の入力としてこの形状の外側にある完全な元のドキュメントを取得することも (実際には、この形状の外側にあるデータを失うことになります)、reduce の結果としてこの形状の外側にあるドキュメントを更新することもできません。したがって、投稿コレクションの「最終」更新は、クライアントを介してプログラムで行う必要があります。これを修正することは、適切な変更要求になるようです。

以下は、MongoDB の map/reduce を Ruby で使用してすべての comment_counts を計算する実際の例です。次に、map_reduce_results コレクションをプログラムで使用して、posts コレクションの comment_count を更新します。reduce 関数は、out: {reduce: 'posts'} を使用しようとする試みから取り除かれます。

少し実験して私の答えを確認するか、必要に応じて、完全に機能しないサーバー側の試行をリクエストに応じて投稿し、固定モデルを完成させることができます。これが Ruby での MongoDB の map/reduce を理解するのに役立つことを願っています。

テスト/ユニット/コメント_test.rb

require 'test_helper'

class CommentTest < ActiveSupport::TestCase
  def setup
    @map_reduce_results_name = 'map_reduce_results'
    delete_all
  end

  def delete_all
    Post.delete_all
    Comment.delete_all
    Mongoid.database.drop_collection(@map_reduce_results_name)
  end

  def dump(title = nil)
    yield
    puts title
    Post.all.to_a.each do |post|
      puts "#{post.to_json} #{post.comments.collect(&:text).to_json}"
    end
  end

  def generate
    (2+rand(2)).times do |p|
      post = Post.create(text: 'post_' + p.to_s)
      comments = (2+rand(3)).times.collect do |c|
        Comment.create(text: "post_#{p} comment_#{c}")
      end
      post.comments = comments
    end
  end

  def generate_and_migrate(title = nil)
    dump(title + ' generate:') { generate }
    dump(title + ' migrate:') { yield }
  end

  test "map reduce migration" do
    generate_and_migrate('programmatic') do
      Post.all.each do |p|
        p.update_attribute :comment_count, p.comments.count
      end
    end
    delete_all
    generate_and_migrate('map/reduce') do
      map = "function() { emit( this.post_id, {comment_count: 1} ); }"
      reduce = <<-EOF
        function(key, values) {
          var result = {comment_count: 0};
          values.forEach(function(value) { result.comment_count += value.comment_count; });
          return result;
        }
      EOF
      out = @map_reduce_results_name #{reduce: 'posts'}
      result_coll = Comment.collection.map_reduce(map, reduce, out: out)
      puts "#{@map_reduce_results_name}:"
      result_coll.find.each do |doc|
        p doc
        Post.find(doc['_id']).update_attribute :comment_count, doc['value']['comment_count'].to_i
      end
    end
  end
end

テスト出力 (JSON と Ruby inspect が混在して申し訳ありません)

Run options: --name=test_map_reduce_migration

# Running tests:

programmatic generate:
{"_id":"4fcae3bde4d30b21e2000001","comment_count":null,"text":"post_0"} ["post_0 comment_0","post_0 comment_1","post_0 comment_2"]
{"_id":"4fcae3bde4d30b21e2000005","comment_count":null,"text":"post_1"} ["post_1 comment_1","post_1 comment_0","post_1 comment_2","post_1 comment_3"]
{"_id":"4fcae3bde4d30b21e200000a","comment_count":null,"text":"post_2"} ["post_2 comment_1","post_2 comment_3","post_2 comment_0","post_2 comment_2"]
programmatic migrate:
{"_id":"4fcae3bde4d30b21e2000001","comment_count":3,"text":"post_0"} ["post_0 comment_0","post_0 comment_1","post_0 comment_2"]
{"_id":"4fcae3bde4d30b21e2000005","comment_count":4,"text":"post_1"} ["post_1 comment_1","post_1 comment_0","post_1 comment_2","post_1 comment_3"]
{"_id":"4fcae3bde4d30b21e200000a","comment_count":4,"text":"post_2"} ["post_2 comment_1","post_2 comment_3","post_2 comment_0","post_2 comment_2"]
map/reduce generate:
{"_id":"4fcae3bee4d30b21e200000f","comment_count":null,"text":"post_0"} ["post_0 comment_0","post_0 comment_1"]
{"_id":"4fcae3bee4d30b21e2000012","comment_count":null,"text":"post_1"} ["post_1 comment_2","post_1 comment_0","post_1 comment_1"]
{"_id":"4fcae3bee4d30b21e2000016","comment_count":null,"text":"post_2"} ["post_2 comment_0","post_2 comment_1","post_2 comment_2","post_2 comment_3"]
map_reduce_results:
{"_id"=>BSON::ObjectId('4fcae3bee4d30b21e200000f'), "value"=>{"comment_count"=>2.0}}
{"_id"=>BSON::ObjectId('4fcae3bee4d30b21e2000012'), "value"=>{"comment_count"=>3.0}}
{"_id"=>BSON::ObjectId('4fcae3bee4d30b21e2000016'), "value"=>{"comment_count"=>4.0}}
map/reduce migrate:
{"_id":"4fcae3bee4d30b21e200000f","comment_count":2,"text":"post_0"} ["post_0 comment_0","post_0 comment_1"]
{"_id":"4fcae3bee4d30b21e2000012","comment_count":3,"text":"post_1"} ["post_1 comment_2","post_1 comment_0","post_1 comment_1"]
{"_id":"4fcae3bee4d30b21e2000016","comment_count":4,"text":"post_2"} ["post_2 comment_0","post_2 comment_1","post_2 comment_2","post_2 comment_3"]
.

Finished tests in 0.072870s, 13.7231 tests/s, 0.0000 assertions/s.

1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
于 2012-06-03T04:37:50.830 に答える