pgq 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ TODO
2
+ *.gem
3
+ lib/serv
4
+ README2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pgq (0.1)
5
+ activerecord (>= 2.3.2)
6
+ activesupport (>= 2.3.2)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activemodel (3.2.3)
12
+ activesupport (= 3.2.3)
13
+ builder (~> 3.0.0)
14
+ activerecord (3.2.3)
15
+ activemodel (= 3.2.3)
16
+ activesupport (= 3.2.3)
17
+ arel (~> 3.0.2)
18
+ tzinfo (~> 0.3.29)
19
+ activesupport (3.2.3)
20
+ i18n (~> 0.6)
21
+ multi_json (~> 1.0)
22
+ arel (3.0.2)
23
+ builder (3.0.0)
24
+ diff-lcs (1.1.3)
25
+ i18n (0.6.0)
26
+ multi_json (1.3.4)
27
+ rake (0.9.2.2)
28
+ rspec (2.10.0)
29
+ rspec-core (~> 2.10.0)
30
+ rspec-expectations (~> 2.10.0)
31
+ rspec-mocks (~> 2.10.0)
32
+ rspec-core (2.10.0)
33
+ rspec-expectations (2.10.0)
34
+ diff-lcs (~> 1.1.3)
35
+ rspec-mocks (2.10.1)
36
+ tzinfo (0.3.33)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ pgq!
43
+ rake
44
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Makarchev K
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,207 @@
1
+ Pgq Rails
2
+ =========
3
+
4
+ Queues system for AR/Rails based on PgQ Skytools for PostgreSQL, like Resque on Redis. Rails 3! only tested.
5
+
6
+ About PgQ
7
+ * http://wiki.postgresql.org/wiki/SkyTools#PgQ
8
+
9
+
10
+ Install
11
+ -------
12
+
13
+ Install skytools:
14
+ Ubuntu 11.10:
15
+
16
+ # apt-get install postgresql-server postgresql-client
17
+ # apt-get install skytools
18
+
19
+ Gemfile:
20
+
21
+ ```ruby
22
+ gem 'pgq'
23
+ ```
24
+
25
+ Create ticker configs:
26
+
27
+ $ rails generate pgq:config
28
+ edit config: config/pgq_development.ini
29
+
30
+
31
+ Install pgq to database (if test database recreates all the time, should reinstall pgq each time):
32
+
33
+ $ rake pgq:install
34
+ or execute
35
+ $ pgqadm config/pgq_development.ini install
36
+
37
+
38
+
39
+ Run ticker daemon (ticker needs on production database, or development if we test the process of consuming):
40
+ Daemon run once, bind to the database. (If worker not consuming check that daemon started).
41
+
42
+ $ rake pgq:ticker:start (stop)
43
+ or execute
44
+ $ pgqadm config/pgq_development.ini ticker -d
45
+
46
+
47
+ Last, add to config/application.rb
48
+
49
+ config.autoload_paths += %W( #{config.root}/app/models/pgq )
50
+
51
+
52
+ Create Pgq consumer
53
+ -------------------
54
+
55
+ $ rails generate pgq:add my
56
+
57
+
58
+ This creates file app/models/pgq/pgq_my.rb and migration
59
+
60
+ $ rake db:migrate
61
+
62
+
63
+ ```ruby
64
+ class PgqMy < Pgq::Consumer
65
+
66
+ def some_method1(a, b, c)
67
+ logger.info "async call some_method1 with #{[a, b, c].inspect}"
68
+ end
69
+
70
+ def some_method2(x)
71
+ logger.info "async called some_method2 with #{x.inspect}"
72
+ end
73
+
74
+ end
75
+ ```
76
+
77
+ Insert event into queue like this:
78
+
79
+ PgqMy.some_method1(1, 2, 3)
80
+
81
+ or
82
+
83
+ PgqMy.add_event(:some_method2, some_x)
84
+
85
+ or
86
+
87
+ PgqMy.enqueue(:some_method1, 1, 2, 3)
88
+
89
+
90
+ Workers
91
+ -------
92
+ Start worker for queue:
93
+
94
+ $ rake pgq:worker QUEUES="my"
95
+ $ rake pgq:worker QUEUES="my,mailer,other"
96
+ $ rake pgq:worker QUEUES="all" RAILS_ENV=production
97
+
98
+
99
+
100
+ Also can consume manual, or write [bin_script](http://github.com/kostya/bin_script) like this:
101
+ ```ruby
102
+ class PgqRunnerScript < BinScript
103
+
104
+ self.enable_locking = false
105
+
106
+ required :q, "queues separated by ','"
107
+ required :w, "watch file"
108
+
109
+ def do!
110
+ worker = Pgq::Worker.new(:logger => self.logger, :queues => params(:q),
111
+ :watch_file => params(:w) || "./tmp/stop_all.txt")
112
+ worker.run
113
+ end
114
+
115
+ end
116
+ ```
117
+
118
+ and run:
119
+
120
+ $ ./bin/pgq_runner.rb -q my -l ./log/pgq_my.log
121
+ $ ./bin/pgq_runner.rb -q all -l ./log/pgq_all.log # this will consume all queues from config/queues_list.yml
122
+
123
+
124
+
125
+
126
+ ### Admin interface
127
+ Admins interface is possible like Resque, but not realized yet.
128
+ For queues info, run in console:
129
+
130
+ > Pgq::Consumer.database.pgq_get_consumer_info
131
+
132
+
133
+ ### Failed events
134
+ When any raise happens in consumer, its produce failed event, which can be retry, or delete.
135
+
136
+ Retry manual:
137
+
138
+ Pgq::Consumer.resend_failed_events(queue_name)
139
+
140
+ Delete manual:
141
+
142
+ Pgq::Consumer.clear_failed_events(queue_name)
143
+
144
+
145
+ ### Divide events between workers, for one consumer class
146
+ create more queues: my_2, my_3
147
+
148
+ ```ruby
149
+ class PgqMy < Pgq::Consumer
150
+
151
+ QUEUES = %w(my my_2 my_3).freeze
152
+
153
+ def self.next_queue_name
154
+ QUEUES.choice
155
+ end
156
+
157
+ end
158
+ ```
159
+ And run 3 workers: for my, my_2, my_3
160
+
161
+
162
+
163
+
164
+ ### Consume group of events extracted from pgq(batch):
165
+ Usually batch size is ~500 events.
166
+
167
+ ```ruby
168
+ class PgqMy < Pgq::ConsumerGroup
169
+
170
+ # {'type' => [events]}
171
+ def perform_group(events_hash)
172
+ raise "realize me"
173
+ end
174
+
175
+ end
176
+ ```
177
+
178
+
179
+
180
+ ### Options
181
+ create initializer with:
182
+
183
+ ```ruby
184
+ class Pgq::Consumer
185
+
186
+ # specify database with pgq queues
187
+ def self.database
188
+ SomeDatabase
189
+ end
190
+
191
+ # specify coder
192
+ def self.coder
193
+ Pgq::Marshal64Coder
194
+ end
195
+
196
+ end
197
+ ```
198
+
199
+ ### Proxy method to consumer
200
+ Usefull in specs
201
+
202
+ ``` ruby
203
+ PgqMy.proxy(:some_method1)
204
+ ```
205
+
206
+ When code call PgqMy.some_method1(a,b,c) this would be convert into PgqMy.new.some_method1(a,b,c)
207
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ task :default => :spec
9
+ RSpec::Core::RakeTask.new(:spec)
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/lib/pgq'
@@ -0,0 +1,40 @@
1
+ module Pgq
2
+ class AddGenerator < Rails::Generators::Base
3
+ include Rails::Generators::Migration
4
+ source_root File.expand_path("../add_templates", __FILE__)
5
+ argument :queue_name, :type => :string
6
+
7
+ def self.next_migration_number(path)
8
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
9
+ end
10
+
11
+ def add_to_queues_list
12
+ filename = Rails.root + 'config/queues_list.yml'
13
+ if File.exists?(filename)
14
+ x = YAML.load_file(filename)
15
+ x << name
16
+ File.open(filename, 'w'){|f| f.write(YAML.dump(x))}
17
+ else
18
+ x = [name]
19
+ File.open(filename, 'w'){|f| f.write(YAML.dump(x))}
20
+ end
21
+ end
22
+
23
+ def add_files
24
+ template "pgq_class.rb", "app/models/pgq/pgq_#{name}.rb"
25
+ template "spec.rb", "spec/models/pgq/pgq_#{name}_spec.rb"
26
+ migration_template "migration.rb", "db/migrate/create_#{name}_queue.rb"
27
+ end
28
+
29
+ private
30
+
31
+ def name
32
+ queue_name.underscore
33
+ end
34
+
35
+ def name_c
36
+ name.classify
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,9 @@
1
+ class Create<%= name_c %>Queue < ActiveRecord::Migration
2
+ def self.up
3
+ Pgq::Consumer.add_queue :<%= name %>
4
+ end
5
+
6
+ def self.down
7
+ Pgq::Consumer.remove_queue :<%= name %>
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class Pgq<%= name_c %> < Pgq::Consumer
2
+
3
+ # to insert event: Pgq<%= name_c %>.my_event(1, 2, 3)
4
+
5
+ # async execute
6
+ def my_event(a, b, c)
7
+ logger.info "async call my_event with #{[a, b, c].inspect}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Pgq<%= name_c %> do
4
+ before(:each) do
5
+ @consumer = Pgq<%= name_c %>.new
6
+ end
7
+
8
+ end
@@ -0,0 +1,18 @@
1
+ module Pgq
2
+ class ConfigGenerator < Rails::Generators::Base
3
+ source_root File.expand_path("../config_templates", __FILE__)
4
+
5
+ def copy_configs
6
+ self.env_name = 'development'
7
+ template "pgq.rb", "config/pgq_development.ini"
8
+ self.env_name = 'test'
9
+ template "pgq.rb", "config/pgq_test.ini"
10
+ self.env_name = 'production'
11
+ template "pgq.rb", "config/pgq_production.ini"
12
+ end
13
+
14
+ private
15
+ attr_accessor :env_name
16
+
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ [pgqadm]
2
+ job_name = pgqadm_<%= env_name %>
3
+ db = dbname=dbname user=user password=password host=127.0.0.1 port=5432
4
+
5
+ # how often to run maintenance [seconds]
6
+ maint_delay = 600
7
+
8
+ # how often to check for activity [seconds]
9
+ loop_delay = 0.1
10
+
11
+ logfile = log/%(job_name)s.log
12
+ pidfile = tmp/%(job_name)s.pid
data/lib/pgq.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+
4
+ module Pgq
5
+ end
6
+
7
+ Dir[File.dirname(__FILE__) + "/pgq/*.rb"].each{|f| require f }
8
+
9
+ ActiveRecord::Base.extend(Pgq::Api) if defined?(ActiveRecord::Base)
data/lib/pgq/api.rb ADDED
@@ -0,0 +1,88 @@
1
+ module Pgq::Api
2
+ # should mixin to class, which have connection
3
+
4
+ def pgq_create_queue(queue_name)
5
+ connection.select_value(sanitize_sql_array ["SELECT pgq.create_queue(?)", queue_name]).to_i
6
+ end
7
+
8
+ def pgq_drop_queue(queue_name)
9
+ connection.select_value(sanitize_sql_array ["SELECT pgq.drop_queue(?)", queue_name]).to_i
10
+ end
11
+
12
+ def pgq_register_consumer(queue_name, consumer_id)
13
+ connection.select_value(sanitize_sql_array ["SELECT pgq.register_consumer(?, ?)", queue_name, consumer_id]).to_i
14
+ end
15
+
16
+ def pgq_unregister_consumer(queue_name, consumer_id)
17
+ connection.select_value(sanitize_sql_array ["SELECT pgq.unregister_consumer(?, ?)", queue_name, consumer_id]).to_i
18
+ end
19
+
20
+ def pgq_add_queue(queue_name, consumer_name)
21
+ pgq_create_queue(queue_name.to_s)
22
+ pgq_register_consumer(queue_name.to_s, consumer_name.to_s)
23
+ end
24
+
25
+ def pgq_remove_queue(queue_name, consumer_name)
26
+ pgq_unregister_consumer(queue_name.to_s, consumer_name.to_s)
27
+ pgq_drop_queue(queue_name.to_s)
28
+ end
29
+
30
+ def pgq_insert_event(queue_name, ev_type, ev_data, ev_extra1 = nil, ev_extra2 = nil, ev_extra3 = nil, ev_extra4 = nil)
31
+ result = connection.select_value(sanitize_sql_array ["SELECT pgq.insert_event(?, ?, ?, ?, ?, ?, ?)",
32
+ queue_name, ev_type, ev_data, ev_extra1, ev_extra2, ev_extra3, ev_extra4])
33
+ result ? result.to_i : nil
34
+ end
35
+
36
+ def pgq_next_batch(queue_name, consumer_id)
37
+ result = connection.select_value(sanitize_sql_array ["SELECT pgq.next_batch(?, ?)", queue_name, consumer_id])
38
+ result ? result.to_i : nil
39
+ end
40
+
41
+ def pgq_get_batch_events(batch_id)
42
+ connection.select_all(sanitize_sql_array ["SELECT * FROM pgq.get_batch_events(?)", batch_id])
43
+ end
44
+
45
+ def pgq_event_failed(batch_id, event_id, reason)
46
+ connection.select_value(sanitize_sql_array ["SELECT pgq.event_failed(?, ?, ?)", batch_id, event_id, reason]).to_i
47
+ end
48
+
49
+ def pgq_event_retry(batch_id, event_id, retry_seconds)
50
+ connection.select_value(sanitize_sql_array ["SELECT pgq.event_retry(?, ?, ?)", batch_id, event_id, retry_seconds]).to_i
51
+ end
52
+
53
+ def pgq_finish_batch(batch_id)
54
+ connection.select_value(sanitize_sql_array ["SELECT pgq.finish_batch(?)", batch_id])
55
+ end
56
+
57
+ def pgq_get_queue_info(queue_name)
58
+ connection.select_value(sanitize_sql_array ["SELECT pgq.get_queue_info(?)", queue_name])
59
+ end
60
+
61
+ def pgq_failed_event_retry(queue_name, consumer, event_id)
62
+ connection.select_value(sanitize_sql_array ["SELECT * FROM pgq.failed_event_retry(?, ?, ?)", queue_name, consumer, event_id])
63
+ end
64
+
65
+ def pgq_failed_event_delete(queue_name, consumer, event_id)
66
+ connection.select_value(sanitize_sql_array ["SELECT * FROM pgq.failed_event_delete(?, ?, ?)", queue_name, consumer, event_id])
67
+ end
68
+
69
+ def pgq_failed_events_count(queue_name, consumer)
70
+ res = connection.select_value(sanitize_sql_array ["SELECT * FROM pgq.failed_event_count(?, ?)", queue_name, consumer])
71
+ res ? res.to_i : nil
72
+ end
73
+
74
+ def pgq_failed_event_list queue_name, consumer, limit = nil, offset = nil, order = 'desc'
75
+ order = (order.to_s == 'desc') ? order : 'asc'
76
+ connection.select_all(sanitize_sql_array ["SELECT * FROM pgq.failed_event_list(?, ?, ?, ?) order by ev_id #{order}", queue_name, consumer, limit.to_i, offset.to_i])
77
+ end
78
+
79
+ # queue lag in seconds
80
+ def pgq_queue_lag(queue_name)
81
+ connection.select_value(sanitize_sql_array ["SELECT Max(EXTRACT(epoch FROM lag)) FROM pgq.get_consumer_info() where queue_name = ?", queue_name]).to_f
82
+ end
83
+
84
+ def pgq_get_consumer_info
85
+ connection.select_all("SELECT * FROM pgq.get_consumer_info()")
86
+ end
87
+
88
+ end