glowworm 0.3.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.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +6 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +129 -0
  6. data/LICENSE +19 -0
  7. data/README.md +326 -0
  8. data/Rakefile +29 -0
  9. data/bin/basic_server_tester +311 -0
  10. data/bin/em_server/config.ru +2 -0
  11. data/bin/em_server/em_server.rb +19 -0
  12. data/bin/em_server_tester +68 -0
  13. data/bin/glowworm +90 -0
  14. data/bin/load_tester +84 -0
  15. data/ci_jobs/glowworm-continuous-deploy-next-staging/run.sh +10 -0
  16. data/ci_jobs/glowworm-integrations/run.sh +15 -0
  17. data/ci_jobs/glowworm-performance/run.sh +2 -0
  18. data/ci_jobs/glowworm-robustness/run.sh +2 -0
  19. data/ci_jobs/glowworm-units/run.sh +13 -0
  20. data/ci_jobs/setup.sh +119 -0
  21. data/example/example_server.ecology +6 -0
  22. data/example/example_server.rb +32 -0
  23. data/glowworm.gemspec +54 -0
  24. data/lib/glowworm.rb +501 -0
  25. data/lib/glowworm/em.rb +8 -0
  26. data/lib/glowworm/no_bg.rb +2 -0
  27. data/lib/glowworm/version.rb +3 -0
  28. data/server/Gemfile +27 -0
  29. data/server/Gemfile.lock +87 -0
  30. data/server/PROTOCOL +39 -0
  31. data/server/check_mk_checks/check_glowworm_server +43 -0
  32. data/server/db_migrations/20111004214649_change_feature_accounts_to_string.rb +60 -0
  33. data/server/db_migrations/20111028104546_add_value_to_account_set_features.rb +12 -0
  34. data/server/db_migrations/20120217090636_add_fully_active_flag_to_features.rb +15 -0
  35. data/server/example_test_data.rb +66 -0
  36. data/server/glowworm_server.ecology.erb +16 -0
  37. data/server/glowworm_server.rb +226 -0
  38. data/server/run/server.sh +7 -0
  39. data/server/server_test.rb +72 -0
  40. data/server/version.rb +3 -0
  41. data/test/integration/basic_server_test.rb +90 -0
  42. data/test/integration/create_sqlite_data.rb +196 -0
  43. data/test/integration/em_server_test.rb +68 -0
  44. data/test/integration/gemfile_for_specific_glowworm_version +17 -0
  45. data/test/integration/gemfile_for_specific_glowworm_version.lock +55 -0
  46. data/test/integration/integration_test_helper.rb +153 -0
  47. data/test/integration/load_test.rb +59 -0
  48. data/test/integration/nginx.conf +23 -0
  49. data/test/integration/server_test.ecology.erb +6 -0
  50. data/test/test_helper.rb +47 -0
  51. data/test/units/em_test.rb +41 -0
  52. data/test/units/feature_flag_test.rb +297 -0
  53. data/test/units/no_bg_test.rb +40 -0
  54. data/test/units/request_test.rb +51 -0
  55. metadata +410 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OThhNTlmZjhmMzZmOTRjZmQxMjYwZTZlMzJmNGVmNmEyN2RjODZmYw==
5
+ data.tar.gz: !binary |-
6
+ YTUyZWViZTIwNWY4MmNlNDFkZDgzNzg3MTYzMzkxNGExZmU2MTRiNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjkwNDg4ZDYxMDAwMWEwYzU1NzQ1YmZiODgzNmExOTUxZThmYjdhZWUzNTk1
10
+ MmJlMDgxMjIxMWJkNTJiYTg2MTQzOTBjYmM4MjlmZWU2OTQ1MDMwY2RmYzkz
11
+ MWViNDA1ODg3ZGZhYTk0MGQ0YTRjYmQ4ZTYzYzMwZWUxNzg2NDc=
12
+ data.tar.gz: !binary |-
13
+ YjEyYjU3YzU3MTc1ZmY4MjdkMDQ5MWRjMWJmY2RiNTgwZmRjZDQxN2E1ZGVj
14
+ OWQ4YjQ4ZDg1MGUyYjI3MTdiNmU1OTQyODI2ZWZjYjVkZmRiZTcwNTM0NzE5
15
+ NDkxNTcwMjViNmQ5OTk0ODA3MjE3ZjIzM2RkMDJmMzNkZjAyZGU=
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ *~
3
+ test_data.sqlite
4
+ results
5
+ .yardoc/*
6
+ doc/*
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private --protected
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+ source "http://gems.sv2"
3
+
4
+ # Specify your gem's dependencies in your gemspec
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,129 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ glowworm (0.2.6)
5
+ ecology (~> 0.0.18)
6
+ httparty
7
+ multi_json
8
+ termite (~> 0.0.13)
9
+ trollop
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ remote: http://gems.sv2/
14
+ specs:
15
+ addressable (2.3.2)
16
+ ansi (1.4.3)
17
+ cassandra (0.12.2ooyala2)
18
+ json
19
+ rake
20
+ simple_uuid (~> 0.2.0)
21
+ thrift_client (>= 0.7.0, < 0.9)
22
+ cookiejar (0.3.0)
23
+ daemons (1.1.9)
24
+ ecology (0.0.18)
25
+ erubis
26
+ multi_json
27
+ em-http-request (1.0.3)
28
+ addressable (>= 2.2.3)
29
+ cookiejar
30
+ em-socksify
31
+ eventmachine (>= 1.0.0.beta.4)
32
+ http_parser.rb (>= 0.5.3)
33
+ em-net-http (0.3.7)
34
+ addressable
35
+ em-http-request (>= 0.2.10)
36
+ eventmachine (>= 0.12.10)
37
+ rake
38
+ em-resolv-replace (1.1.3)
39
+ em-socksify (0.2.1)
40
+ eventmachine (>= 1.0.0.beta.4)
41
+ em-synchrony (1.0.2)
42
+ eventmachine (>= 1.0.0.beta.1)
43
+ erubis (2.7.0)
44
+ eventmachine (1.0.0)
45
+ ffi (1.1.5)
46
+ ffi-rzmq (0.9.6)
47
+ ffi
48
+ http_parser.rb (0.5.3)
49
+ httparty (0.11.0)
50
+ multi_json (~> 1.0)
51
+ multi_xml (>= 0.5.2)
52
+ json (1.7.5)
53
+ metaclass (0.0.1)
54
+ minitap (0.3.5)
55
+ minitest
56
+ tapout (>= 0.3.0)
57
+ minitest (3.5.0)
58
+ mocha (0.12.5)
59
+ metaclass (~> 0.0.1)
60
+ multi_json (1.10.1)
61
+ multi_xml (0.5.5)
62
+ mysql (2.8.1)
63
+ nodule (0.0.34)
64
+ cassandra (= 0.12.2ooyala2)
65
+ ffi-rzmq
66
+ rainbow
67
+ rack (1.4.1)
68
+ rack-fiber_pool (0.9.2)
69
+ rack-ooyala-headers (0.1.0)
70
+ rack
71
+ rack-protection (1.2.0)
72
+ rack
73
+ rainbow (1.1.4)
74
+ rake (0.9.2.2)
75
+ scope (0.2.3)
76
+ minitest
77
+ sequel (3.40.0)
78
+ simple_uuid (0.2.0)
79
+ sinatra (1.3.3)
80
+ rack (~> 1.3, >= 1.3.6)
81
+ rack-protection (~> 1.2)
82
+ tilt (~> 1.3, >= 1.3.3)
83
+ sinatra-synchrony (0.4.1)
84
+ em-http-request (~> 1.0)
85
+ em-resolv-replace (~> 1.1)
86
+ em-synchrony (~> 1.0.1)
87
+ eventmachine (~> 1.0.0)
88
+ rack-fiber_pool (~> 0.9)
89
+ sinatra (~> 1.0)
90
+ sqlite3 (1.3.6)
91
+ tapout (0.4.1)
92
+ ansi
93
+ json
94
+ termite (0.0.26)
95
+ ecology (~> 0.0.6)
96
+ multi_json
97
+ rainbow (~> 1.1.3)
98
+ thin (1.5.0)
99
+ daemons (>= 1.0.9)
100
+ eventmachine (>= 0.12.6)
101
+ rack (>= 1.0.0)
102
+ thrift (0.8.0)
103
+ thrift_client (0.8.2)
104
+ thrift (~> 0.8.0)
105
+ tilt (1.3.3)
106
+ trollop (2.0)
107
+ yajl-ruby (1.1.0)
108
+
109
+ PLATFORMS
110
+ ruby
111
+
112
+ DEPENDENCIES
113
+ bundler
114
+ em-net-http
115
+ glowworm!
116
+ minitap (>= 0.3.5)
117
+ mocha
118
+ mysql
119
+ nodule (~> 0.0.25)
120
+ rack-ooyala-headers
121
+ rake
122
+ scope (~> 0.2.1)
123
+ sequel
124
+ sinatra
125
+ sinatra-synchrony
126
+ sqlite3
127
+ tapout
128
+ thin
129
+ yajl-ruby
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Ooyala, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,326 @@
1
+ Glowworm
2
+ ========
3
+
4
+ Glowworm allows you to do gradual rollouts of new features, and "dark
5
+ deploys" -- rolling out code for a feature, then only turning it on
6
+ selectively and after the code is in place everywhere.
7
+
8
+ We call a given feature/account combination a "feature flag". Your
9
+ apps need to know the value of that flag frequently and reliably. But
10
+ you need to change the value fairly quickly when it's time to roll the
11
+ new feature out.
12
+
13
+ Glowworm can make that happen.
14
+
15
+ The name is inspired by dark deploys. First, the code crawls into
16
+ place. When you're ready, it all lights up!
17
+
18
+ Installing
19
+ ==========
20
+
21
+ `gem install glowworm` or use a Gemfile with Bundler.
22
+
23
+ Ooyalans should make sure that "gems.sv2" is listed as a gem source in
24
+ your Gemfile or on your gem command line.
25
+
26
+ Overview
27
+ ========
28
+
29
+ At Ooyala? Want a visual step-by-step version of "how do I use
30
+ Glowworm for a new feature?" We have an online slide deck for that,
31
+ updated from a Glowworm presentation at Ooyala.
32
+ "http://portal.sliderocket.com/BKHPY/Glowworm"
33
+
34
+ If you're not at Ooyala you won't have the nice web interface for
35
+ setting up features, account sets and so on. But the workflow and
36
+ concepts are all the same.
37
+
38
+ Usage
39
+ =====
40
+
41
+ You need to specify an account name or number and a feature name.
42
+ Glowworm handles it from there.
43
+
44
+ if Glowworm.feature_flag("bob's account #", "turn_button_blue?")
45
+ # Code to turn the button blue
46
+ else
47
+ # Code for the default red button
48
+ end
49
+
50
+ You can also prefetch features if you want to:
51
+
52
+ Glowworm.prefetch(:all, :all, :timeout => 3.5)
53
+
54
+ Technically you can supply a feature name or account to prefetch,
55
+ but the current version of Glowworm ignores that and just checks
56
+ the server for all updates.
57
+
58
+ Glowworm features default to false, but begin returning true as you
59
+ turn them on. You can specify a non-boolean value as the default as a
60
+ way of determining if the value is "really" false, or if we're simply
61
+ returning the default.
62
+
63
+ Glowworm.feature_flag("EG-172434", "video_speed", :default => :the_default)
64
+
65
+ In each case where Glowworm can return false, the default will be
66
+ returned instead if you specify one.
67
+
68
+ Lifecycle
69
+ =========
70
+
71
+ When you first start querying a new feature, Glowworm will always
72
+ return false, or your default if you've set one. If the feature
73
+ or account isn't in the database, false is the initial default in
74
+ all cases.
75
+
76
+ You'll need to add the account to the database, add the feature to the
77
+ database, and turn on that feature for that account set. You can see
78
+ an example of code to do this in glowworm/server/example_test_data.rb
79
+ in the Glowworm gem code.
80
+
81
+ Once that has happened, Glowworm should begin returning true for
82
+ that feature.
83
+
84
+ You can also, instead, add an override for that combination of account
85
+ and feature. That's not a particularly scalable way to turn on a
86
+ feature for a large number of accounts in a system with many accounts,
87
+ but it's fine for testing. You can see an example of adding an
88
+ override in example_test_data.rb as well.
89
+
90
+ Options
91
+ =======
92
+
93
+ You can query or prefetch with a TTL or a timeout. The TTL specifies
94
+ how long before Glowworm queries the server about that feature again.
95
+ The timeout specifies how long to wait for a server result before just
96
+ returning a (possibly stale) cached result. 0 is a perfectly good
97
+ timeout or TTL if that's what you need in a given case.
98
+
99
+ # Don't trust cached values, make sure to query the server
100
+ Glowworm.feature_flag("12434", "myfeature", :ttl => 0.0)
101
+
102
+ # Don't wait for a result, give me a stale value but update in the background
103
+ Glowworm.feature_flag("9999", "someFeature", :timeout => 0.0)
104
+
105
+ # Don't wait for a result, give me a stale value and don't update
106
+ Glowworm.feature_flag("9999", "someFeature", :timeout => 0.0, :ttl => 1_000_000)
107
+
108
+ Caching
109
+ =======
110
+
111
+ Glowworm caches locally in memory.
112
+
113
+ Glowworm always queries all accounts and all features from the server
114
+ initially. Then it just exchanges a checksum with the server to find
115
+ out when the data has changed. As soon as the timestamp goes stale,
116
+ the server sends the new information to the client.
117
+
118
+ Configuring with an Ecology
119
+ ===========================
120
+
121
+ Glowworm supports a JSON configuration file managed by the Ecology
122
+ gem. By default it checks the location of the current executable ($0)
123
+ with extension .ecology. So "bob.rb" would have "bob.ecology" next to
124
+ it.
125
+
126
+ Whatever application is using Glowworm will need an Ecology (or to set
127
+ variables explicitly in Ruby) to specify where the Glowworm server is.
128
+ The app can also give options for things like timeout and ttl.
129
+
130
+ An Ecology file has this structure:
131
+
132
+ {
133
+ "application": "MyApp",
134
+ "features": {
135
+ "server": "glowworm.ooyala.com:4999",
136
+ "ttl": "30",
137
+ "timeout": "1000"
138
+ },
139
+ "logging": {
140
+ "console_out": false,
141
+ "default_component": "MyLibrary"
142
+ }
143
+ }
144
+
145
+ Every part is optional, including the presence of the file at all.
146
+ The example above includes extra configuration for termite, another
147
+ Ecology-enabled gem, to show how they combine.
148
+
149
+ The server property gives the hostname and port of the Glowworm
150
+ feature server. If none is specified, glowworm defaults to port 4999
151
+ on localhost. Note that if specifying a server, the port *must* also
152
+ be specified.
153
+
154
+ TTL, if present, gives the number of seconds that a given value is
155
+ considered fresh in the cache. After that time it will be updated.
156
+ This defaults to 5 minutes (300 seconds). Until that time, the cached
157
+ result will be returned. "Refresh" is an outdated name for the same
158
+ setting.
159
+
160
+ Timeout, if present, gives the number of milliseconds to wait when
161
+ querying the server for the correct answer to return. Even if this
162
+ fails the cache will be updated later after the request returns.
163
+
164
+ EventMachine
165
+ ============
166
+
167
+ If you are using glowworm with eventmachine, or in general would not like a background thread,
168
+ then you have a couple of options. In an eventmachine architecture, it is required that your app
169
+ use em-synchrony (or sinatra-synchrony), as well as em-net-http. These are not included in glowworm
170
+ to avoid inclusion of the whole eventmachine stack in the gem. You can require "glowworm/em" to use
171
+ the version which will make em-friendly http calls. This, along with require "glowworm/no_bg" use a
172
+ different version of glowworm that synchronously fetches all data at require time, and otherwise whenever
173
+ Glowworm.update_cache_in_foreground is called. Because of this, you need to be sure your glowworm server
174
+ is properly set before using these requires. One has the additional option of requiring "glowworm", and
175
+ then later in initialization calling Glowworm.no_bg, for non-em apps, or Glowworm.em for eventmachine apps.
176
+ Note that this call to Glowworm.em must be from within a fiber, as it uses EM.synchrony, and could cause your
177
+ app to hang if called from outside eventmachine itself.
178
+
179
+ Servers
180
+ =======
181
+
182
+ An example Glowworm server used by Ooyala is included in the "server"
183
+ directory of the Glowworm gem. The protocol is very simple and you
184
+ should have an easy time implementing a Glowworm server if ours is
185
+ inappropriate for your use case.
186
+
187
+ Our server is nginx serving data from a Sequel-based daemon with an ecology file to
188
+ configure it.
189
+
190
+ To run it, cd into glowworm/server and run `bundle exec ./glowworm_server.rb`
191
+
192
+ You will also need an nginx server serving /opt/ooyala/glowworm/shared/www/ on port 4999.
193
+ You can find the required config file in glowworm/server/config/nginx.conf
194
+
195
+ For basic test data, run `./example_test_data --clear` from the same
196
+ directory.
197
+
198
+ Servers at Ooyala, For Production Use
199
+ =====================================
200
+
201
+ If you're using Glowworm at Ooyala for production features then
202
+ there's more infrastructure to help out.
203
+
204
+ You can create and set flags in production using the Support Tool.
205
+ Start at the URL
206
+ [http://support-tools/features](http://support-tools:3000/features)
207
+ and add or click through to the feature you want. If you can see features
208
+ but can't add or change them, you'll need to talk to the Tools and
209
+ Automation team about getting permissions.
210
+
211
+ You can also create and destroy account sets, add and remove accounts to
212
+ them and set particular features active for particular account sets. See
213
+ [http://support-tools/account_sets](http://support-tools:3000/account_sets).
214
+
215
+ If you want to do the same things in staging rather than in
216
+ production, use the hostname support-tools-staging rather than
217
+ support-tools.
218
+
219
+ Example Application
220
+ ===================
221
+
222
+ In the example subdirectory of the gem you can find a very simple
223
+ Sinatra application that auto-refreshes every five seconds, queries a
224
+ feature on a 20-second TTL and displays a button whose text varies
225
+ according to the feature.
226
+
227
+ First, start your glowworm server (see above). Then start the example
228
+ app server (`cd glowworm/example`, run `bundle exec
229
+ ./example_server.rb`) and then from the server directory run
230
+ `example_test_data --clear --new-signup` and you should see the button
231
+ text change within 25 seconds. Run it with just `--clear`, and you
232
+ should see it change back within another 25 seconds. You can go back
233
+ and forth as often as you have the patience and the browser should
234
+ keep changing.
235
+
236
+ Updating Features and Account Sets
237
+ ==================================
238
+
239
+ The supplied Glowworm server uses a simple set of database tables, and
240
+ includes migrations to set them up. The idea is that you have account
241
+ sets, with a table of accounts in the account sets
242
+ (account_set_accounts). You also have a table of which features each
243
+ account set is true for (account_set_features). Finally, you have a
244
+ table of the features themselves, both to supply names for them and to
245
+ mark each feature fully active (i.e. active for all non-overridden
246
+ accounts). Fully active is only supported in Glowworm versions 0.2.0 and up.
247
+
248
+ Reliability
249
+ ===========
250
+
251
+ In the real world, bad things happen. Sometimes your packets can't
252
+ reach the glowworm server. Sometimes it's down. Sometimes you have
253
+ only very old cached data. Sometimes you have no cached data at all.
254
+ Sometimes the glowworm server is down when your app restarts, so it
255
+ can't load data on startup.
256
+
257
+ So what's the worst case here, and how does Glowworm respond?
258
+
259
+ If Glowworm has already gotten started and the server goes down,
260
+ Glowworm will simply continue returning the same information it last
261
+ saw. When the server comes back up, Glowworm's next poll will return
262
+ better information and fresh information will be provided to the
263
+ application. No problem.
264
+
265
+ If the server is down when Glowworm starts, everything will return
266
+ false. Glowworm will keep trying to query, but until its first
267
+ successful response from the server, everything is false in all cases.
268
+ Even "fully active" features still assume that Glowworm can find out
269
+ about that from the server, so these features return false also.
270
+
271
+ Order of Precedence to determine a Feature's Value
272
+ ==================================================
273
+
274
+ There are two main scenarios in which a feature's value must be determined,
275
+ being if data has been received from the server or not.
276
+
277
+ In the case that we have no data from the server:
278
+ 1) The default set for the Glowworm client (app- or call-level) will be returned.
279
+ 2) If none is set, false will be returned.
280
+
281
+ If the server has been contacted and the caches have been populated:
282
+ 1) If an account override is present, it's value will be returned.
283
+ 2) If an account set has a value set for this feature, it's value will be returned.
284
+ 3) If an app- or call-level default has been set, it's will be returned.
285
+ 4) If the feature has a value set in the "fully_active" field, it's value will be returned.
286
+ 5) If none of these are present, false will be returned.
287
+
288
+ Rate Limits and Scaling
289
+ =======================
290
+
291
+ Glowworm updates are expensive -- all features, all feature sets, all
292
+ overrides and all providers are sent, though not each combination of
293
+ them. However, they're also rare. One update is sent to each client
294
+ when it starts up, and then an update is sent whenever the data
295
+ actually changes on the server. That's comparatively rare.
296
+
297
+ Normally each Glowworm client sends back a checksum from the last
298
+ successful update. If nothing has changed, the server sends back a
299
+ 304 (unchanged) and no further response. The Glowworm client
300
+ considers itself fully up to date, and doesn't poll again until the
301
+ TTL has expired.
302
+
303
+ This makes short TTLs and frequent polling fairly cheap - they require
304
+ a single HTTP exchange with almost no data. However, short timeouts
305
+ are also fine since you don't have to wait for an update to be sure
306
+ it's happening.
307
+
308
+ Design
309
+ ======
310
+
311
+ For its server, database representation and wire protocol, Glowworm
312
+ uses account sets - groups of accounts for which a given feature will
313
+ normally be toggled. It also has individual per-account-and-feature
314
+ override flags, so you don't have to strictly stick with those groups.
315
+
316
+ The account sets are an optimization - we can send which account set
317
+ each account belongs to, and what accounts sets a given feature is
318
+ active for, which lets us send far less data than the full
319
+ accounts-times-features matrix. It's also an excellent user interface
320
+ convention since frequently the same accounts will tend to want the
321
+ earliest, least stable features and want them soonest.
322
+
323
+ If you only set overrides for all your features and don't use account
324
+ sets then you will get much worse performance than Glowworm is
325
+ designed for. Account sets are a significant optimization, not just a
326
+ convenience.