methodmissing-scrooge 1.0.4 → 2.0.0
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/README.textile +139 -449
- data/Rakefile +20 -19
- data/VERSION.yml +2 -2
- data/lib/attributes_proxy.rb +121 -0
- data/lib/scrooge.rb +206 -47
- data/rails/init.rb +1 -10
- data/test/helper.rb +88 -0
- data/test/models/mysql_user.rb +4 -0
- data/test/scrooge_test.rb +75 -0
- data/test/setup.rb +3 -0
- metadata +11 -76
- data/assets/config/scrooge.yml.template +0 -27
- data/lib/scrooge/core/string.rb +0 -29
- data/lib/scrooge/core/symbol.rb +0 -21
- data/lib/scrooge/core/thread.rb +0 -26
- data/lib/scrooge/framework/base.rb +0 -315
- data/lib/scrooge/framework/rails.rb +0 -132
- data/lib/scrooge/middleware/tracker.rb +0 -46
- data/lib/scrooge/orm/active_record.rb +0 -159
- data/lib/scrooge/orm/base.rb +0 -102
- data/lib/scrooge/profile.rb +0 -223
- data/lib/scrooge/storage/base.rb +0 -46
- data/lib/scrooge/storage/memory.rb +0 -25
- data/lib/scrooge/strategy/base.rb +0 -74
- data/lib/scrooge/strategy/controller.rb +0 -31
- data/lib/scrooge/strategy/scope.rb +0 -15
- data/lib/scrooge/strategy/stage.rb +0 -77
- data/lib/scrooge/strategy/track.rb +0 -19
- data/lib/scrooge/strategy/track_then_scope.rb +0 -41
- data/lib/scrooge/tracker/app.rb +0 -161
- data/lib/scrooge/tracker/base.rb +0 -66
- data/lib/scrooge/tracker/model.rb +0 -150
- data/lib/scrooge/tracker/resource.rb +0 -181
- data/spec/fixtures/config/scrooge/scopes/1234567891/scope.yml +0 -2
- data/spec/fixtures/config/scrooge.yml +0 -20
- data/spec/helpers/framework/rails/cache.rb +0 -25
- data/spec/spec_helper.rb +0 -55
- data/spec/units/scrooge/core/string_spec.rb +0 -21
- data/spec/units/scrooge/core/symbol_spec.rb +0 -13
- data/spec/units/scrooge/core/thread_spec.rb +0 -15
- data/spec/units/scrooge/framework/base_spec.rb +0 -160
- data/spec/units/scrooge/framework/rails_spec.rb +0 -40
- data/spec/units/scrooge/orm/base_spec.rb +0 -61
- data/spec/units/scrooge/profile_spec.rb +0 -79
- data/spec/units/scrooge/storage/base_spec.rb +0 -35
- data/spec/units/scrooge/storage/memory_spec.rb +0 -20
- data/spec/units/scrooge/strategy/base_spec.rb +0 -62
- data/spec/units/scrooge/strategy/controller_spec.rb +0 -26
- data/spec/units/scrooge/strategy/scope_spec.rb +0 -18
- data/spec/units/scrooge/strategy/stage_spec.rb +0 -35
- data/spec/units/scrooge/strategy/track_spec.rb +0 -19
- data/spec/units/scrooge/strategy/track_then_scope_spec.rb +0 -22
- data/spec/units/scrooge/tracker/app_spec.rb +0 -68
- data/spec/units/scrooge/tracker/base_spec.rb +0 -29
- data/spec/units/scrooge/tracker/model_spec.rb +0 -79
- data/spec/units/scrooge/tracker/resource_spec.rb +0 -115
- data/spec/units/scrooge_spec.rb +0 -13
- data/tasks/scrooge.rake +0 -43
data/README.textile
CHANGED
@@ -1,353 +1,58 @@
|
|
1
1
|
h1. Scrooge
|
2
2
|
|
3
|
-
|
4
|
-
Ruby applications only fetch the database content needed to minimize wire traffic
|
5
|
-
and reduce conversion overheads to native Ruby types.
|
3
|
+
h4. This is a complete rewrite from the initial coverage at "igvita.com":http://www.igvita.com/2009/02/27/activerecord-optimization-with-scrooge/ - read on below
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
Many thanks to Stephen Sykes ( "pennysmalls.com":http://pennysmalls.com ) for his time spent on shaping, implementing and troubleshooting this release.
|
6
|
+
|
7
|
+
An ActiveRecord attribute tracker to ensure production Ruby applications only fetch the database content needed to minimize wire traffic and reduce conversion overheads to native Ruby types.
|
9
8
|
|
10
9
|
h2. Why bother ?
|
11
10
|
|
12
11
|
* Object conversion and moving unnecessary data is both expensive and tax existing infrastructure in high load setups
|
13
12
|
* Manually extracting and scoping SELECT clauses is not sustainable in a clean and painless manner with iterative development, even less so in large projects.
|
14
13
|
|
15
|
-
h2.
|
16
|
-
|
17
|
-
There's 3 basic modes of operation :
|
18
|
-
|
19
|
-
* Track : Track attribute access to dump a representative scope profile.
|
20
|
-
|
21
|
-
* Scope : Scope the process and related resources to a previously persisted scope profile.
|
22
|
-
|
23
|
-
* Track then scope : A multi-stage strategy that tracks attribute access for a given warmup period, aggregates the results
|
24
|
-
across multiple processes and enforce a scoping policy representative of the tracking phase.
|
25
|
-
|
26
|
-
h2. Resources
|
27
|
-
|
28
|
-
A resource is :
|
29
|
-
|
30
|
-
* A controller and action endpoint ( inferred through framework specific routing )
|
31
|
-
* A content type / format - a PDF representation may have different Model attribute requirements than a vanilla ERB view.
|
32
|
-
* Request method - typically popular public facing GET requests
|
33
|
-
* Public or Private as a logged in / authenticated representation likely have different attribute requirements.
|
34
|
-
|
35
|
-
All Model to attribute mappings is tracked on a per Resource basis.Multiple Models per Resource is supported.
|
36
|
-
|
37
|
-
h2. Strategies
|
38
|
-
|
39
|
-
h4. Tracking
|
40
|
-
|
41
|
-
In tracking mode Scrooge installs filters ( either through Rack middleware or framework specific hooks ) that track attribute access on a per Resource basis.
|
42
|
-
|
43
|
-
A Kernel#at_exit callback dumps and timestamps this profile ( or scope ) to eg. *framework_configuration_directory/config/scopes/1234147851/scope.yml*
|
44
|
-
|
45
|
-
This typically works well with functional or integration testing and can yield a substantial birds eye view of attribute
|
46
|
-
use.The accuracy is directly proportional to test coverage and the quality of the test suite.
|
47
|
-
|
48
|
-
Example log output :
|
49
|
-
|
50
|
-
<pre>
|
51
|
-
<code>
|
52
|
-
Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:55:55) [GET]
|
53
|
-
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
54
|
-
Hotel Load (0.3ms) SELECT * FROM `hotels` LIMIT 0, 15
|
55
|
-
Rendering template within layouts/application
|
56
|
-
Rendering hotels/index
|
57
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 491) LIMIT 1
|
58
|
-
[Scrooge] read attribute updated_at
|
59
|
-
Rendered hotels/_hotel (2.7ms)
|
60
|
-
Rendered shared/_header (0.1ms)
|
61
|
-
Rendered shared/_navigation (0.3ms)
|
62
|
-
Missing template hotels/_index_sidebar.erb in view path app/views
|
63
|
-
Rendered shared/_sidebar (0.1ms)
|
64
|
-
Rendered shared/_footer (0.1ms)
|
65
|
-
Completed in 91ms (View: 90, DB: 1) | 200 OK [http://test.host/hotels]
|
66
|
-
SQL (0.3ms) ROLLBACK
|
67
|
-
SQL (0.1ms) BEGIN
|
68
|
-
</code>
|
69
|
-
</pre>
|
70
|
-
|
71
|
-
An example scope / profile, saved to disk :
|
14
|
+
h2. What it does
|
72
15
|
|
73
16
|
<pre>
|
74
17
|
<code>
|
75
|
-
|
76
|
-
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
- code
|
108
|
-
- updated_at
|
109
|
-
- level
|
110
|
-
- id
|
111
|
-
- countries_index_get:
|
112
|
-
:action: index
|
113
|
-
:controller: countries
|
114
|
-
:method: :get
|
115
|
-
:is_public: true
|
116
|
-
:format: "*/*"
|
117
|
-
:models:
|
118
|
-
- Country:
|
119
|
-
- name
|
120
|
-
- created_at
|
121
|
-
- code
|
122
|
-
- updated_at
|
123
|
-
- id
|
124
|
-
- location_id
|
125
|
-
- continent_id
|
126
|
-
- hotels_index_get:
|
127
|
-
:action: index
|
128
|
-
:controller: hotels
|
129
|
-
:method: :get
|
130
|
-
:is_public: true
|
131
|
-
:format: "*/*"
|
132
|
-
:models:
|
133
|
-
- Hotel:
|
134
|
-
- from_price
|
135
|
-
- narrative
|
136
|
-
- star_rating
|
137
|
-
- latitude
|
138
|
-
- created_at
|
139
|
-
- hotel_name
|
140
|
-
- updated_at
|
141
|
-
- important_notes
|
142
|
-
- id
|
143
|
-
- apt
|
144
|
-
- location_id
|
145
|
-
- nearest_tube
|
146
|
-
- longitude
|
147
|
-
- telephone
|
148
|
-
- nearest_rail
|
149
|
-
- location_name
|
150
|
-
- distance
|
151
|
-
- Image:
|
152
|
-
- thumbnail_width
|
153
|
-
- created_at
|
154
|
-
- title
|
155
|
-
- updated_at
|
156
|
-
- url
|
157
|
-
- thumbnail_height
|
158
|
-
- height
|
159
|
-
- thumbnail_url
|
160
|
-
- has_thumbnail
|
161
|
-
- hotel_id
|
162
|
-
- width
|
18
|
+
Processing HotelsController#show (for 127.0.0.1 at 2009-03-12 14:32:45) [GET]
|
19
|
+
Parameters: {"action"=>"show", "id"=>"8699-radisson-hotel-waterfront-cape-town", "controller"=>"hotels"}
|
20
|
+
Hotel Load Scrooged (0.3ms) SELECT `hotels`.id FROM `hotels` WHERE (`hotels`.`id` = 8699)
|
21
|
+
Rendering template within layouts/application
|
22
|
+
Rendering hotels/show
|
23
|
+
Hotel Load (0.2ms) SELECT `hotels`.location_id,`hotels`.hotel_name,`hotels`.location,`hotels`.from_price,`hotels`.star_rating,`hotels`.apt,`hotels`.latitude,`hotels`.longitude,`hotels`.distance,`hotels`.narrative,`hotels`.telephone,`hotels`.important_notes,`hotels`.nearest_tube,`hotels`.nearest_rail,`hotels`.created_at,`hotels`.updated_at FROM `hotels` WHERE (`hotels`.`id` = 8699)
|
24
|
+
Image Load Scrooged (0.2ms) SELECT `images`.id FROM `images` WHERE (`images`.hotel_id = 8699) LIMIT 1
|
25
|
+
Image Load (0.2ms) SELECT `images`.hotel_id,`images`.title,`images`.url,`images`.width,`images`.height,`images`.thumbnail_url,`images`.thumbnail_width,`images`.thumbnail_height,`images`.has_thumbnail,`images`.created_at,`images`.updated_at FROM `images` WHERE (`images`.`id` = 488)
|
26
|
+
Rendered shared/_header (0.0ms)
|
27
|
+
Rendered shared/_navigation (0.2ms)
|
28
|
+
Image Load Scrooged (0.2ms) SELECT `images`.id FROM `images` WHERE (`images`.hotel_id = 8699)
|
29
|
+
CACHE (0.0ms) SELECT `images`.hotel_id,`images`.title,`images`.url,`images`.width,`images`.height,`images`.thumbnail_url,`images`.thumbnail_width,`images`.thumbnail_height,`images`.has_thumbnail,`images`.created_at,`images`.updated_at FROM `images` WHERE (`images`.`id` = 488)
|
30
|
+
Address Columns (44.8ms) SHOW FIELDS FROM `addresses`
|
31
|
+
Address Load Scrooged (0.5ms) SELECT `addresses`.id FROM `addresses` WHERE (`addresses`.hotel_id = 8699) LIMIT 1
|
32
|
+
Rendered hotels/_show_sidebar (49.4ms)
|
33
|
+
Rendered shared/_footer (0.1ms)
|
34
|
+
Completed in 56ms (View: 8, DB: 46) | 200 OK [http://localhost/hotels/8699-radisson-hotel-waterfront-cape-town]
|
35
|
+
|
36
|
+
|
37
|
+
Processing HotelsController#show (for 127.0.0.1 at 2009-03-12 14:32:48) [GET]
|
38
|
+
Parameters: {"action"=>"show", "id"=>"8699-radisson-hotel-waterfront-cape-town", "controller"=>"hotels"}
|
39
|
+
Hotel Load Scrooged (0.3ms) SELECT `hotels`.narrative,`hotels`.from_price,`hotels`.star_rating,`hotels`.hotel_name,`hotels`.id FROM `hotels` WHERE (`hotels`.`id` = 8699)
|
40
|
+
Rendering template within layouts/application
|
41
|
+
Rendering hotels/show
|
42
|
+
Image Load Scrooged (0.3ms) SELECT `images`.url,`images`.id,`images`.height,`images`.width FROM `images` WHERE (`images`.hotel_id = 8699) LIMIT 1
|
43
|
+
Rendered shared/_header (0.0ms)
|
44
|
+
Rendered shared/_navigation (0.2ms)
|
45
|
+
Image Load Scrooged (0.3ms) SELECT `images`.thumbnail_width,`images`.id,`images`.thumbnail_height,`images`.thumbnail_url FROM `images` WHERE (`images`.hotel_id = 8699)
|
46
|
+
Address Load Scrooged (0.2ms) SELECT `addresses`.id FROM `addresses` WHERE (`addresses`.hotel_id = 8699) LIMIT 1
|
47
|
+
Rendered hotels/_show_sidebar (1.3ms)
|
48
|
+
Rendered shared/_footer (0.0ms)
|
49
|
+
Completed in 7ms (View: 5, DB: 1) | 200 OK [http://localhost/hotels/8699-radisson-hotel-waterfront-cape-town]
|
163
50
|
</code>
|
164
|
-
</pre>
|
165
|
-
|
166
|
-
h4. Scope
|
167
|
-
|
168
|
-
A previously persisted scope / profile can be restored from disk and injected to the applicable Resources.Database content retrieved will match that of the given scope timestamp.
|
169
|
-
|
170
|
-
This is typically pushed to production where a hybrid ( track then scope strategy) mode of operation is frowned upon and adjusted for each major release or deployment.
|
51
|
+
</pre>
|
171
52
|
|
172
|
-
|
173
|
-
|
174
|
-
<pre>
|
175
|
-
<code>
|
176
|
-
Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:59:41) [GET]
|
177
|
-
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
178
|
-
Hotel Load (0.4ms) SELECT hotels.narrative, hotels.from_price, hotels.created_at, hotels.latitude, hotels.star_rating, hotels.hotel_name, hotels.updated_at, hotels.important_notes, hotels.apt, hotels.id, hotels.nearest_tube, hotels.location_id, hotels.nearest_rail, hotels.telephone, hotels.longitude, hotels.distance, hotels.location_name FROM `hotels` LIMIT 0, 15
|
179
|
-
Rendering template within layouts/application
|
180
|
-
Rendering hotels/index
|
181
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 491) LIMIT 1
|
182
|
-
Rendered hotels/_hotel (2.8ms)
|
183
|
-
Rendered shared/_header (0.1ms)
|
184
|
-
Rendered shared/_navigation (0.3ms)
|
185
|
-
Missing template hotels/_index_sidebar.erb in view path app/views
|
186
|
-
Rendered shared/_sidebar (0.1ms)
|
187
|
-
Rendered shared/_footer (0.1ms)
|
188
|
-
Completed in 90ms (View: 5, DB: 1) | 200 OK [http://test.host/hotels]
|
189
|
-
SQL (0.1ms) ROLLBACK
|
190
|
-
SQL (0.1ms) BEGIN
|
191
|
-
</code>
|
192
|
-
</pre>
|
193
|
-
|
194
|
-
h4. Track then scope
|
195
|
-
|
196
|
-
Multi-stage and self configuring strategy that tracks attribute access for a given warmup period, synchronize the results across n-1 processes, aggregate the results to be representative of the whole cluster ( or seamless fallback to a single process ), remove the tracking filters and install functionality that scopes database access to that of the tracking phase.
|
197
|
-
|
198
|
-
Recommended for production use.
|
199
|
-
|
200
|
-
Example log output :
|
53
|
+
h2. Suggested Use
|
201
54
|
|
202
|
-
|
203
|
-
<code>
|
204
|
-
Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:00:58) [GET]
|
205
|
-
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
206
|
-
Hotel Load (0.5ms) SELECT * FROM `hotels` LIMIT 0, 15
|
207
|
-
Hotel Columns (7.7ms) SHOW FIELDS FROM `hotels`
|
208
|
-
SQL (3.9ms) SELECT count(*) AS count_all FROM `hotels`
|
209
|
-
Rendering template within layouts/application
|
210
|
-
Rendering hotels/index
|
211
|
-
Image Load (0.5ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11381) LIMIT 1
|
212
|
-
Image Columns (3.6ms) SHOW FIELDS FROM `images`
|
213
|
-
Rendered hotels/_hotel (200.2ms)
|
214
|
-
Image Load (0.4ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11382) LIMIT 1
|
215
|
-
Rendered hotels/_hotel (2.4ms)
|
216
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1
|
217
|
-
Rendered hotels/_hotel (1.8ms)
|
218
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12693) LIMIT 1
|
219
|
-
Rendered hotels/_hotel (1.7ms)
|
220
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12738) LIMIT 1
|
221
|
-
Rendered hotels/_hotel (1.6ms)
|
222
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12886) LIMIT 1
|
223
|
-
Rendered hotels/_hotel (1.9ms)
|
224
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13007) LIMIT 1
|
225
|
-
Rendered hotels/_hotel (1.8ms)
|
226
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13074) LIMIT 1
|
227
|
-
Rendered hotels/_hotel (1.5ms)
|
228
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13077) LIMIT 1
|
229
|
-
Rendered hotels/_hotel (1.6ms)
|
230
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13078) LIMIT 1
|
231
|
-
Rendered hotels/_hotel (1.8ms)
|
232
|
-
Image Load (0.3ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13079) LIMIT 1
|
233
|
-
Rendered hotels/_hotel (2.4ms)
|
234
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13080) LIMIT 1
|
235
|
-
Rendered hotels/_hotel (1.8ms)
|
236
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13082) LIMIT 1
|
237
|
-
Rendered hotels/_hotel (1.5ms)
|
238
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13085) LIMIT 1
|
239
|
-
Rendered hotels/_hotel (1.8ms)
|
240
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13105) LIMIT 1
|
241
|
-
Rendered hotels/_hotel (1.6ms)
|
242
|
-
Rendered shared/_header (0.4ms)
|
243
|
-
Rendered shared/_navigation (0.8ms)
|
244
|
-
Missing template hotels/_index_sidebar.erb in view path app/views
|
245
|
-
Rendered shared/_sidebar (0.4ms)
|
246
|
-
Rendered shared/_footer (0.3ms)
|
247
|
-
Completed in 270ms (View: 243, DB: 20) | 200 OK [http://localhost/hotels]
|
248
|
-
[Scrooge] Execute stage :synchronize ...
|
249
|
-
[Scrooge] Uninstalling tracking middleware ...
|
250
|
-
[Scrooge] Stop tracking ...
|
251
|
-
[Scrooge] Synchronize results with other processes ...
|
252
|
-
Cache write: 17619400_63223_756033
|
253
|
-
Cache read: scrooge_tracker_aggregation
|
254
|
-
Cache write: scrooge_tracker_aggregation
|
255
|
-
[Scrooge] Execute stage :aggregate ...
|
256
|
-
[Scrooge] Aggregate results from other processes ...
|
257
|
-
|
258
|
-
|
259
|
-
Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:01:37) [GET]
|
260
|
-
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
261
|
-
Hotel Load (0.5ms) SELECT * FROM `hotels` LIMIT 0, 15
|
262
|
-
SQL (0.2ms) SELECT count(*) AS count_all FROM `hotels`
|
263
|
-
Rendering template within layouts/application
|
264
|
-
Rendering hotels/index
|
265
|
-
Image Load (0.3ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11381) LIMIT 1
|
266
|
-
Rendered hotels/_hotel (2.0ms)
|
267
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11382) LIMIT 1
|
268
|
-
Rendered hotels/_hotel (1.8ms)
|
269
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1
|
270
|
-
Rendered hotels/_hotel (1.7ms)
|
271
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12693) LIMIT 1
|
272
|
-
Rendered hotels/_hotel (1.6ms)
|
273
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12738) LIMIT 1
|
274
|
-
Rendered hotels/_hotel (1.5ms)
|
275
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12886) LIMIT 1
|
276
|
-
Rendered hotels/_hotel (1.8ms)
|
277
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13007) LIMIT 1
|
278
|
-
Rendered hotels/_hotel (1.8ms)
|
279
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13074) LIMIT 1
|
280
|
-
Rendered hotels/_hotel (1.4ms)
|
281
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13077) LIMIT 1
|
282
|
-
Rendered hotels/_hotel (1.6ms)
|
283
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13078) LIMIT 1
|
284
|
-
Rendered hotels/_hotel (1.6ms)
|
285
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13079) LIMIT 1
|
286
|
-
Rendered hotels/_hotel (1.8ms)
|
287
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13080) LIMIT 1
|
288
|
-
Rendered hotels/_hotel (1.7ms)
|
289
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13082) LIMIT 1
|
290
|
-
Rendered hotels/_hotel (1.5ms)
|
291
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13085) LIMIT 1
|
292
|
-
Rendered hotels/_hotel (1.7ms)
|
293
|
-
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13105) LIMIT 1
|
294
|
-
Rendered hotels/_hotel (81.1ms)
|
295
|
-
Rendered shared/_header (0.1ms)
|
296
|
-
Rendered shared/_navigation (0.3ms)
|
297
|
-
Missing template hotels/_index_sidebar.erb in view path app/views
|
298
|
-
Rendered shared/_sidebar (0.1ms)
|
299
|
-
Rendered shared/_footer (0.1ms)
|
300
|
-
Completed in 113ms (View: 107, DB: 4) | 200 OK [http://localhost/hotels]
|
301
|
-
Cache read: scrooge_tracker_aggregation
|
302
|
-
Cache read: 17619400_63223_756033
|
303
|
-
[Scrooge] Execute stage :scope ...
|
304
|
-
[Scrooge] Scope ...
|
305
|
-
|
306
|
-
|
307
|
-
Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:01:53) [GET]
|
308
|
-
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
309
|
-
Hotel Load (0.7ms) SELECT hotels.narrative, hotels.from_price, hotels.created_at, hotels.latitude, hotels.star_rating, hotels.hotel_name, hotels.updated_at, hotels.important_notes, hotels.apt, hotels.id, hotels.nearest_tube, hotels.location_id, hotels.nearest_rail, hotels.telephone, hotels.longitude, hotels.distance, hotels.location_name FROM `hotels` LIMIT 0, 15
|
310
|
-
SQL (0.2ms) SELECT count(*) AS count_all FROM `hotels`
|
311
|
-
Rendering template within layouts/application
|
312
|
-
Rendering hotels/index
|
313
|
-
Image Load (0.3ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 11381) LIMIT 1
|
314
|
-
Rendered hotels/_hotel (2.0ms)
|
315
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 11382) LIMIT 1
|
316
|
-
Rendered hotels/_hotel (1.7ms)
|
317
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1
|
318
|
-
Rendered hotels/_hotel (1.7ms)
|
319
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 12693) LIMIT 1
|
320
|
-
Rendered hotels/_hotel (1.7ms)
|
321
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 12738) LIMIT 1
|
322
|
-
Rendered hotels/_hotel (1.5ms)
|
323
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 12886) LIMIT 1
|
324
|
-
Rendered hotels/_hotel (1.7ms)
|
325
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13007) LIMIT 1
|
326
|
-
Rendered hotels/_hotel (1.8ms)
|
327
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13074) LIMIT 1
|
328
|
-
Rendered hotels/_hotel (1.4ms)
|
329
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13077) LIMIT 1
|
330
|
-
Rendered hotels/_hotel (1.5ms)
|
331
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13078) LIMIT 1
|
332
|
-
Rendered hotels/_hotel (1.8ms)
|
333
|
-
Image Load (0.3ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13079) LIMIT 1
|
334
|
-
Rendered hotels/_hotel (1.9ms)
|
335
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13080) LIMIT 1
|
336
|
-
Rendered hotels/_hotel (1.7ms)
|
337
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13082) LIMIT 1
|
338
|
-
Rendered hotels/_hotel (1.5ms)
|
339
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13085) LIMIT 1
|
340
|
-
Rendered hotels/_hotel (1.7ms)
|
341
|
-
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 13105) LIMIT 1
|
342
|
-
Rendered hotels/_hotel (1.7ms)
|
343
|
-
Rendered shared/_header (0.1ms)
|
344
|
-
Rendered shared/_navigation (0.2ms)
|
345
|
-
Missing template hotels/_index_sidebar.erb in view path app/views
|
346
|
-
Rendered shared/_sidebar (0.0ms)
|
347
|
-
Rendered shared/_footer (0.0ms)
|
348
|
-
Completed in 34ms (View: 27, DB: 4) | 200 OK [http://localhost/hotels]
|
349
|
-
</code>
|
350
|
-
</pre>
|
55
|
+
Install, and you're off to the races!
|
351
56
|
|
352
57
|
h2. Installation
|
353
58
|
|
@@ -359,177 +64,162 @@ h4. From Git
|
|
359
64
|
|
360
65
|
git pull git://github.com/methodmissing/scrooge.git
|
361
66
|
|
362
|
-
|
363
67
|
h4. As a Gem
|
364
68
|
|
365
69
|
sudo gem install methodmissing-scrooge -s http://gems.github.com
|
366
70
|
|
367
|
-
h2.
|
71
|
+
h2. Stability
|
368
72
|
|
369
|
-
|
73
|
+
The whole ActiveRecord test suite passes with scrooge, except for 9 failures related to callsite augmentation (note the SQL reload snippets below).Thoughts on handling or circumventing this much appreciated.
|
370
74
|
|
371
75
|
<pre>
|
372
76
|
<code>
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
on_missing_attribute: :reload # or :raise
|
389
|
-
logged_in_session: :user_id # session key that represents the logged in user
|
390
|
-
enabled: true
|
391
|
-
test:
|
392
|
-
orm: :active_record
|
393
|
-
storage: :memory
|
394
|
-
strategy: :track
|
395
|
-
warmup: 600 # warmup / track for 10 minutes
|
396
|
-
scope:
|
397
|
-
on_missing_attribute: :reload # or :raise
|
398
|
-
logged_in_session: :user_id # session key that represents the logged in user
|
399
|
-
enabled: true
|
77
|
+
2) Failure:
|
78
|
+
test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once(EagerAssociationTest)
|
79
|
+
[/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/test/cases/../../lib/active_record/test_case.rb:31:in `assert_queries'
|
80
|
+
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/test/cases/associations/eager_test.rb:139:in `test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once'
|
81
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.3.1/lib/active_support/testing/setup_and_teardown.rb:57:in `__send__'
|
82
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.3.1/lib/active_support/testing/setup_and_teardown.rb:57:in `run']:
|
83
|
+
5 instead of 3 queries were executed.
|
84
|
+
Queries:
|
85
|
+
SELECT `posts`.id,`posts`.type FROM `posts` WHERE (`posts`.`id` = 1)
|
86
|
+
SELECT `posts`.author_id,`posts`.title,`posts`.body,`posts`.comments_count,`posts`.taggings_count FROM `posts` WHERE (`posts`.`id` = 1)
|
87
|
+
SELECT `authors`.name,`authors`.id FROM `authors` WHERE (`authors`.`id` = 1)
|
88
|
+
SELECT `authors`.author_address_id,`authors`.author_address_extra_id FROM `authors` WHERE (`authors`.`id` = 1)
|
89
|
+
SELECT `author_addresses`.id FROM `author_addresses` WHERE (`author_addresses`.`id` = 1) .
|
90
|
+
<3> expected but was
|
91
|
+
<5>.
|
400
92
|
</code>
|
401
93
|
</pre>
|
402
94
|
|
403
|
-
|
95
|
+
To run tests in your environment :
|
404
96
|
|
405
|
-
|
97
|
+
* Configure to run the ActiveRecord test suite as per the "docs":http://github.com/rails/rails/blob/8a17fd1a65ab8e2fa6b36d79603fde0e6ffd083f/activerecord/RUNNING_UNIT_TESTS
|
98
|
+
* 'rake test' from within the scrooge root directory
|
99
|
+
* It'll attempt to find the path to the ActiveRecord test cases through rubygems
|
100
|
+
* Known to work with both 2.2.2, 2.3.0 and the upcoming 2.3.1
|
101
|
+
|
102
|
+
h2. Initial Benchmarks
|
103
|
+
|
104
|
+
Passenger, Rails 2.2.2, remote DB :
|
406
105
|
|
407
106
|
<pre>
|
408
107
|
<code>
|
409
|
-
|
410
|
-
|
108
|
+
Without scrooge:
|
109
|
+
|
110
|
+
Concurrency Level: 1
|
111
|
+
Time taken for tests: 68.279156 seconds
|
112
|
+
Complete requests: 150
|
113
|
+
Failed requests: 0
|
114
|
+
Write errors: 0
|
115
|
+
Total transferred: 13741201 bytes
|
116
|
+
HTML transferred: 13679100 bytes
|
117
|
+
Requests per second: 2.20 [#/sec] (mean)
|
118
|
+
Time per request: 455.194 [ms] (mean)
|
119
|
+
Time per request: 455.194 [ms] (mean, across all concurrent requests)
|
120
|
+
Transfer rate: 196.53 [Kbytes/sec] received
|
121
|
+
|
122
|
+
With scrooge:
|
123
|
+
|
124
|
+
Concurrency Level: 1
|
125
|
+
Time taken for tests: 58.162039 seconds
|
126
|
+
Complete requests: 150
|
127
|
+
Failed requests: 0
|
128
|
+
Write errors: 0
|
129
|
+
Total transferred: 13747200 bytes
|
130
|
+
HTML transferred: 13685100 bytes
|
131
|
+
Requests per second: 2.58 [#/sec] (mean)
|
132
|
+
Time per request: 387.747 [ms] (mean)
|
133
|
+
Time per request: 387.747 [ms] (mean, across all concurrent requests)
|
134
|
+
Transfer rate: 230.82 [Kbytes/sec] received
|
411
135
|
</code>
|
136
|
+
</pre>
|
137
|
+
|
138
|
+
h2. How it works
|
412
139
|
|
413
|
-
h4.
|
140
|
+
h4. Callsites
|
414
141
|
|
415
|
-
|
142
|
+
Ruby allows introspection of the call tree through
|
416
143
|
|
417
144
|
<pre>
|
418
145
|
<code>
|
419
|
-
|
420
|
-
</
|
421
|
-
</
|
422
|
-
|
423
|
-
h4. Verbose
|
146
|
+
Kernel#caller
|
147
|
+
</code>
|
148
|
+
</pre>
|
424
149
|
|
425
|
-
|
150
|
+
Scrooge analyzes the last 10 calltree elements that triggered
|
426
151
|
|
427
152
|
<pre>
|
428
153
|
<code>
|
429
|
-
|
154
|
+
ActiveRecord::Base.find_by_sql
|
155
|
+
</code>
|
430
156
|
</pre>
|
431
|
-
</code>
|
432
157
|
|
433
|
-
|
158
|
+
Lets refer to that as a callsite, or signature.
|
434
159
|
|
435
|
-
|
436
|
-
|
437
|
-
h4. Warmup
|
438
|
-
|
439
|
-
The designated warmup period for the :track_then_scope strategy, given in seconds.Typically 600 to 3600.
|
160
|
+
Thus given SQL such as
|
440
161
|
|
441
162
|
<pre>
|
442
163
|
<code>
|
443
|
-
|
164
|
+
"SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1"
|
165
|
+
</code>
|
444
166
|
</pre>
|
445
|
-
</code>
|
446
167
|
|
447
|
-
|
168
|
+
Called from our application helper
|
448
169
|
|
449
|
-
|
170
|
+
<pre>
|
171
|
+
<code>
|
172
|
+
["/Users/lourens/projects/superbreak_app/vendor/plugins/scrooge/rails/../lib/scrooge.rb:27:in `find_by_sql'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:1557:in `find_every'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:1514:in `find_initial'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:613:in `find'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:60:in `find'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:67:in `first'", "/Users/lourens/projects/superbreak_app/app/helpers/application_helper.rb:60:in `hotel_image'", "/Users/lourens/projects/superbreak_app/app/views/hotels/_hotel.html.erb:4:in `_run_erb_app47views47hotels47_hotel46html46erb_locals_hotel_hotel_counter_object'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable.rb:36:in `send'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable.rb:36:in `render'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable_partial.rb:20:in `render'"]
|
173
|
+
</code>
|
174
|
+
</pre>
|
450
175
|
|
176
|
+
We can generate a unique callsite identifier with the following calculation :
|
451
177
|
<pre>
|
452
178
|
<code>
|
453
|
-
|
179
|
+
(The above calltree << "SELECT * FROM `images` ).hash " # cut off conditions etc.
|
180
|
+
</code>
|
454
181
|
</pre>
|
455
|
-
</code>
|
456
182
|
|
457
|
-
|
183
|
+
Callsites are tracked on a per model ( table name ) basis.
|
458
184
|
|
459
|
-
h4.
|
185
|
+
h4. Scope
|
460
186
|
|
461
|
-
|
187
|
+
Only SQL statements that meet the following criteria is considered for optimization :
|
462
188
|
|
463
|
-
|
464
|
-
<code>
|
465
|
-
on_missing_attribute: :reload # or :raise
|
466
|
-
</pre>
|
467
|
-
</code>
|
189
|
+
* A SELECT statement
|
468
190
|
|
469
|
-
|
191
|
+
* Not an INNER JOIN
|
470
192
|
|
471
|
-
|
193
|
+
* The Model has a primary key defined
|
472
194
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
</pre>
|
477
|
-
</code>
|
195
|
+
h4. How it tracks
|
196
|
+
|
197
|
+
The ActiveRecord attributes Hash is replaced with a proxy that automatically augments the callsite with any attributes referenced through the Hash lookup keys.
|
478
198
|
|
479
|
-
h4.
|
199
|
+
h4. Storage
|
480
200
|
|
481
|
-
|
201
|
+
There's a slight memory hit for each model as the callsites is stored as a class level Hash, which is relatively lightweight and looks like this :
|
482
202
|
|
483
203
|
<pre>
|
484
204
|
<code>
|
485
|
-
|
486
|
-
</pre>
|
205
|
+
{-113952497=>#<Set: {"User", "Password"}>}
|
487
206
|
</code>
|
207
|
+
</pre>
|
488
208
|
|
489
|
-
|
209
|
+
h4. Tracking and scoping ?
|
490
210
|
|
491
|
-
|
211
|
+
The tracking and scoping phases is superseded by this implementation - none of those hindrances anymore.
|
492
212
|
|
493
|
-
|
494
|
-
<code>
|
495
|
-
methodmissing:superbreak_app lourens$ rake scrooge:list
|
496
|
-
(in /Users/lourens/projects/superbreak_app)
|
497
|
-
- 1234735663
|
498
|
-
- 1234735722
|
499
|
-
- 1234735744
|
500
|
-
- 1234735790
|
501
|
-
- 1234738880
|
502
|
-
methodmissing:superbreak_app lourens$ rake scope=1234735790 scrooge:inspect
|
503
|
-
(in /Users/lourens/projects/superbreak_app)
|
504
|
-
#<GET :hotels/show (*/*)
|
505
|
-
- #<Hotel :important_notes, :location_id>
|
506
|
-
- #<Address :line1, :created_at, :line2, :postcode, :updated_at, :country_id, :county, :location_id, :town, :hotel_id>
|
507
|
-
|
508
|
-
#<GET :countries/index (*/*)
|
509
|
-
- #<Country :name, :created_at, :code, :updated_at, :id, :location_id, :continent_id>
|
510
|
-
|
511
|
-
#<GET :locations/index (*/*)
|
512
|
-
- #<Location :name, :created_at, :code, :updated_at, :level, :id>
|
513
|
-
|
514
|
-
#<GET :hotels/index (*/*)
|
515
|
-
- #<Image :created_at, :thumbnail_width, :title, :updated_at, :url, :thumbnail_height, :height, :thumbnail_url, :has_thumbnail, :width, :hotel_id>
|
516
|
-
- #<Hotel :narrative, :from_price, :created_at, :latitude, :star_rating, :hotel_name, :updated_at, :important_notes, :apt, :id, :nearest_tube, :location_id, :nearest_rail, :telephone, :longitude, :distance, :location_name>
|
517
|
-
</code>
|
518
|
-
</pre>
|
213
|
+
h2. Todo
|
519
214
|
|
520
|
-
|
215
|
+
* Deeper coverage for Scrooge::AttributesProxy ( pending conversion to a subclass of Hash instead )
|
521
216
|
|
522
|
-
|
217
|
+
* Extract Scrooge::Callsite
|
523
218
|
|
524
|
-
|
219
|
+
* Track both columns AND association invocations off Scrooge::Callsite
|
525
220
|
|
526
|
-
|
221
|
+
* Have invoking Model#attributes not associate all columns with the callsite
|
527
222
|
|
528
|
-
*
|
529
|
-
<code>
|
530
|
-
<pre>
|
531
|
-
ActiveRecord::StatementInvalid (Mysql::Error: Unknown column 'orders.brochures_count' in 'field list': SELECT orders.brochures_count, orders.id FROM `orders` WHERE (`orders`.`id` = 1688139) LIMIT 1)
|
532
|
-
</code>
|
533
|
-
</pre>
|
223
|
+
* Avoid possible missing attribute exceptions for destroyed objects
|
534
224
|
|
535
|
-
|
225
|
+
(c) 2009 Lourens Naudé (methodmissing) and Stephen Sykes (sdsykes)
|