2013-05-06 17 views
24

Ryan Batesさんは、プッシュ通知について議論するときにPostgresののLISTEN/NOTIFY機能について言及していますが、レールアプリでLISTEN/NOTIFYを実装する方法についてヒントを見つけることができませんでした。postgres LISTEN/NOTIFY rails

ここにpgアダプタ内のwait_for_notify機能のドキュメントですが、正確にはそれが設計されているかどうかわかりません。

pgアダプタのconnection変数に直接タップする必要がありますか?

+0

私が正しく理解すれば、DBが情報をレールにプッシュするようにします。レールに何らかのリスナーがあれば、plperlを使ってこのリスナーに接続し、情報を配信することができます。 –

+0

私は、そのリスナーとして動作するようにレールを設定する方法のサンプルコードを見つけようとしています。私はPostgresのLISTEN/NOTIFY能力のためにplperlが必要ではないと思っていますが、私はあなたが考えることができるものを試そうとしています –

+0

私は心にハックを持っていました。 http://sequel.rubyforge.org/rdoc/files/doc/postgresql_rdoc.htmlまたはhttps://github.com/taotetek/listen_notify_poller/blob/master/example.rb –

答えて

46

wait_for_notifyメソッドで適切な場所を探していますが、ActiveRecordはAPIを使用していないようですので、基礎となるPG :: Connectionオブジェクト(またはその1つ)を取得する必要がありますあなたがマルチスレッド設定を実行している場合)、ActiveRecordがPostgresと通信するために使用しています。

接続が完了したら、必要な文をLISTEN文で実行し、ブロック(および任意のタイムアウト期間)をwait_for_notifyに渡します。これは現在のスレッドをブロックし、タイムアウトに達するか、NOTIFYが発生するまでPostgresの接続を独占します(例えば、Webリクエストの中でこれをしたくない場合)。別のプロセスがあなたが聞いているチャネルの1つにNOTIFYを発行すると、そのブロックは3つの引数、つまり通知されたチャネル、NOTIFYを引き起こしたPostgresバックエンドのPID、およびNOTIFYを伴うペイロード(もしあれば)。

私はかなりしばらくのActiveRecordを使用していないので、これを行うためのクリーンな方法があるかもしれませんが、これは4.0.0.beta1で無事に動作するようです:の例えば

# Be sure to check out a connection, so we stay thread-safe. 
ActiveRecord::Base.connection_pool.with_connection do |connection| 
    # connection is the ActiveRecord::ConnectionAdapters::PostgreSQLAdapter object 
    conn = connection.instance_variable_get(:@connection) 
    # conn is the underlying PG::Connection object, and exposes #wait_for_notify 

    begin 
    conn.async_exec "LISTEN channel1" 
    conn.async_exec "LISTEN channel2" 

    # This will block until a NOTIFY is issued on one of these two channels. 
    conn.wait_for_notify do |channel, pid, payload| 
     puts "Received a NOTIFY on channel #{channel}" 
     puts "from PG backend #{pid}" 
     puts "saying #{payload}" 
    end 

    # Note that you'll need to call wait_for_notify again if you want to pick 
    # up further notifications. This time, bail out if we don't get a 
    # notification within half a second. 
    conn.wait_for_notify(0.5) do |channel, pid, payload| 
     puts "Received a second NOTIFY on channel #{channel}" 
     puts "from PG backend #{pid}" 
     puts "saying #{payload}" 
    end 
    ensure 
    # Don't want the connection to still be listening once we return 
    # it to the pool - could result in weird behavior for the next 
    # thread to check it out. 
    conn.async_exec "UNLISTEN *" 
    end 
end 

より一般的な使用方法については、Sequel's implementationを参照してください。

を追加して編集する:ここでは何が起こっているのかについての別の説明があります。これはバックグラウンドの背後にある正確な実装ではないかもしれませんが、動作を十分に説明しているようです。

Postgresは接続ごとに通知のリストを保持します。接続を使用してLISTEN channel_nameを実行すると、そのチャンネルの通知をこの接続のリストにプッシュする必要があります(複数の接続で同じチャンネルを聞くことができるので、1つの通知が多くのリストにプッシュされることがあります) 。同時に多数のチャネルに接続することができ、そのいずれかへの通知はすべて同じリストにプッシュされます。

wait_for_notifyは、接続のリストから最も古い通知をポップし、その情報をブロックに渡します。または、リストが空の場合は、通知が利用可能になるまでスリープし、同じことを(またはタイムアウトまで到達した場合、nilを返します)。 wait_for_notifyは1つの通知しか処理しないので、複数の通知を処理する場合は、繰り返し呼び出す必要があります。あなたUNLISTEN channel_nameまたはUNLISTEN *、Postgresが使用している接続のリストにそれらの通知をプッシュ停止しますが、すでにそのリストにプッシュされているものがそこに滞在し、それが次に呼び出されたときwait_for_notifyはまだそれらを返します

。これにより、wait_for_notifyの後に、しかしUNLISTENの前に蓄積された通知が、別のスレッドがその接続をチェックアウトしたときに引き続き存在する問題が発生する可能性があります。その場合、UNLISTENの後に、短いタイムアウトでnilを返すまでwait_for_notifyを呼び出すとよいでしょう。しかし、多くの異なる目的のためにLISTENNOTIFYを頻繁に使用していない限り、おそらくそれは心配する価値はありません。

上記のSequelの実装に対するより良いリンクを追加しました。私はそれを見ることをお勧めします。それはかなり簡単です。

+0

を見てください。明確にするために: 1/2秒でレスポンスを受け取ることはありません。リスニングを止めますか? –

+0

指定したタイムアウトまでに応答を受信しない場合、wait_for_notifyはnilを返し、ブロックは呼び出されませんが、channel1とchannel2のLISTENはUNLISTENまで有効です。つまり、あなたのコードは何でも実行できます。また、wait_for_notifyを再度実行すると、まだchannel1とchannel2を聴いています。 wait_for_notifyでブロックしていない間にNOTIFYが発生した場合は、wait_for_notifyを再度実行すると直ちにそれを取得しますが、それについてはわかりません。 – PreciousBodilyFluids

+0

実際にメッセージを取得するには、 'wait_for_notify'をポーリングする必要がありますか? –