agoo 2.15.10 → 2.15.11

Sign up to get free protection for your applications and to get access to all the features.
data/misc/rails.md ADDED
@@ -0,0 +1,174 @@
1
+ # Rails with Agoo
2
+
3
+ Agoo gives Rails a performance boost. It's easy to use Agoo with Rails. Start
4
+ with a Rails project. Something simple.
5
+
6
+ ```
7
+ $ rails new blog
8
+ $ rails g scaffold User
9
+ $ rails db:migrate
10
+ ```
11
+
12
+ Now run it with Agoo.
13
+
14
+ ```
15
+ $ rackup -r agoo -s agoo
16
+ ```
17
+
18
+ Or for an extra boost on a machine with multiple cores fire up Agoo with
19
+ multiple workers. The optimium seems to be about one worker per hyperthread or
20
+ two per core.
21
+
22
+ ```
23
+ $ rackup -r agoo -s agoo -O wc=12
24
+ ```
25
+
26
+ What you should see is a faster Rails. Requests to something like
27
+ `http://localhost:9292/users/1` should be several times faster and asset
28
+ fetches should be 8000 times faster. Here are some benchmark runs using the
29
+ [perfer](https://github.com/ohler55/perfer) benchmark tool.
30
+
31
+ It is easy to push Rails too hard and watch the latency climb. To avoid that
32
+ the number of open connections use is adjusted according to how well the
33
+ server and Rails can handle the load. A balance where the latency is as close
34
+ to the best attainable is used and the throughput is maximized for that
35
+ latency is used in all cases.
36
+
37
+ All benchmarks were run on a 6 core i7-8700 system with 16GB of memory. The
38
+ driver, `perfer` was run on a separate machine to allow Rails and the web
39
+ server full use of the benchmark machine.
40
+
41
+ ### With Rails managed objects
42
+
43
+ For the benchmarks a blank `User` object is created using a browser. The a
44
+ fetch is performed repeatedly on that object.
45
+
46
+ ##### With Rails and the default Puma server.
47
+
48
+ ```
49
+ $ perfer -t 1 -c 1 -b 1 192.168.1.11:9292 -p /users/1 -d 10
50
+ Benchmarks for:
51
+ URL: 192.168.1.11:9292/users/1
52
+ Threads: 1
53
+ Connections/thread: 1
54
+ Duration: 10.0 seconds
55
+ Keep-Alive: false
56
+ Results:
57
+ Throughput: 41 requests/second
58
+ Latency: 23.612 +/-5.111 msecs (and stdev)
59
+ ```
60
+
61
+ ##### Now the same request with Agoo in non-clustered mode.
62
+
63
+ ```
64
+ $ perfer -t 2 -k -c 4 -b 1 192.168.1.11:9292 -p /users/1 -d 10
65
+ Benchmarks for:
66
+ URL: 192.168.1.11:9292/users/1
67
+ Threads: 2
68
+ Connections/thread: 4
69
+ Duration: 5.0 seconds
70
+ Keep-Alive: true
71
+ Results:
72
+ Throughput: 279 requests/second
73
+ Latency: 28.608 +/-3.497 msecs (and stdev)
74
+ ```
75
+
76
+ Agoo was able to handle 8 concurrent connections and still maintain the
77
+ latency target of under 30 millisecond. Throughput with Agoo was also 7 times
78
+ greater. Most of the time spent with both Agoo and Puma is most likely in
79
+ Rails and Rackup so lets try Agoo in cluster mode with the application is
80
+ stateless.
81
+
82
+ ##### Now the same request with Agoo in clustered mode.
83
+
84
+ ```
85
+ $ perfer -t 2 -k -c 20 -b 1 192.168.1.11:9292 -p /users/1 -d 10
86
+ Benchmarks for:
87
+ URL: 192.168.1.11:9292/users/1
88
+ Threads: 2
89
+ Connections/thread: 20
90
+ Duration: 5.0 seconds
91
+ Keep-Alive: true
92
+ Results:
93
+ Throughput: 1476 requests/second
94
+ Latency: 27.051 +/-11.107 msecs (and stdev)
95
+ ```
96
+
97
+ Another 5x boost in throughput. That make Agoo in cluster mode 36 times faster
98
+ than the default Puma. In all fairness, Puma can be put into clustered mode as
99
+ well using a custom configuration file. If anyone would like to provide
100
+ formula for running Puma at optimum please let me know or create a PR for how
101
+ to run it more effectively.
102
+
103
+ ### Fetching static assets
104
+
105
+ Rails likes to be in charge and is responsible for serving static assets. Some
106
+ servers such as Agoo offer an option to bypass Rails handling of static
107
+ assets. Allowing static assets to be loaded directly means CSS, HTML, images,
108
+ Javascript, and other static files load quickly so that web sites are more
109
+ snappy even when more than a handful of users are connected.
110
+
111
+ ##### Rails with the default Puma server loading a static asset.
112
+ ```
113
+ $ perfer -t 2 -k -c 1 -b 1 192.168.1.11:9292 -p /robots.txt -d 10
114
+ Benchmarks for:
115
+ URL: 192.168.1.11:9292/robots.txt
116
+ Threads: 2
117
+ Connections/thread: 1
118
+ Duration: 10.0 seconds
119
+ Keep-Alive: true
120
+ Results:
121
+ Throughput: 79 requests/second
122
+ Latency: 25.027 +/-7.800 msecs (and stdev)
123
+ ```
124
+
125
+ Well, Rails with Puma handles almost twice as as many request as it does for
126
+ Ruby processing of managed object calls. Note that Puma was able to handle 2
127
+ concurrent connections without degradation of the latency.
128
+
129
+ ##### Rails with Agoo loading a static asset in non-clustered mode.
130
+ ```
131
+ $ perfer -t 2 -k -c 40 -b 2 192.168.1.11:9292 -p /robots.txt -d 10
132
+ Benchmarks for:
133
+ URL: 192.168.1.11:9292/robots.txt
134
+ Threads: 2
135
+ Connections/thread: 40
136
+ Duration: 5.0 seconds
137
+ Keep-Alive: true
138
+ Results:
139
+ Throughput: 500527 requests/second
140
+ Latency: 0.292 +/-0.061 msecs (and stdev)
141
+ ```
142
+
143
+ Wow, more than 6000 times faster and the latency drops from 25 milliseconds to
144
+ a fraction of a millisecond. There is a reason for that. Agoo looks at the
145
+ `Rails.configuration.assets.paths` variable and sets up a bypass to load those
146
+ files directly using the same approach as Rails. Ruby is no longer has to deal
147
+ with basic file serving but can be relogated to taking care of business. It
148
+ also means tha no Ruby objects are created for serving static assets.
149
+
150
+ Now how about Agoo in clustered mode.
151
+
152
+ ##### Rails with Agoo loading a static asset in clustered mode.
153
+ ```
154
+ $ perfer -t 2 -k -c 40 -b 2 192.168.1.11:9292 -p /robots.txt -d 10
155
+ Benchmarks for:
156
+ URL: 192.168.1.11:9292/robots.txt
157
+ Threads: 2
158
+ Connections/thread: 40
159
+ Duration: 5.0 seconds
160
+ Keep-Alive: true
161
+ Results:
162
+ Throughput: 657777 requests/second
163
+ Latency: 0.223 +/-0.073 msecs (and stdev)
164
+ ```
165
+
166
+ A bit faster but not that much over the non-clustered Agoo. The limiting
167
+ factor with static assets is the network. Agoo handles 80 concurrent
168
+ connections at the same latency as one.
169
+
170
+ ### Summary
171
+
172
+ Benchmarks are not everything but if they are an important consideration when
173
+ selecting a web server for Rails or Rack then Agoo clearly has an advantage
174
+ over the Rack and Rails default.
data/misc/song.md ADDED
@@ -0,0 +1,268 @@
1
+ # GraphQL with a Song
2
+
3
+ You've may have heard developer singing praises about how wonderful GraphQL
4
+ is. Maybe you thought it was looking into. I like to learn new technologies by
5
+ using them so this article is a simple example of using GraphQL.
6
+
7
+ GraphQL is a language for describing an applicaiton API. It holds a similar
8
+ position in the development stack as a REST API but with more
9
+ flexibility. Unlike REST, GraphQL allows response formats and content to be
10
+ specified by the client. Just as SQL `SELECT` statements allow query results
11
+ to be specified, GraphQL allows returned JSON data structure to be
12
+ specified. Following the SQL analogy GraphQL does not provide a `WHERE` clause
13
+ but identifies fields on application objects that should provide the data for
14
+ the response.
15
+
16
+ GraphQL, as the name suggests models APIs as if the application is a graph of
17
+ data. While that description may not be how you have viewed your application
18
+ it is a model used in most systems. If your application is object based then
19
+ the objects refer to other objects. These object-method-object define a
20
+ graph. Data that can be represented by JSON is a graph as JSON is just a
21
+ directed graph. Thinking about the application as presenting a graph model
22
+ through the API will make GraphQL much easier to understand.
23
+
24
+ ## Consider the Application
25
+
26
+ Enough of the abstract. Lets get down to actually building an application that
27
+ uses GraphQL by starting with a definition of the data model or the
28
+ graph. Last year I picked up a new hobby. I'm learning to play the electric
29
+ upright bass and about music so a music related example came to mind when
30
+ coming up with and example.
31
+
32
+ Keeping it simple the object types are __Artist__ and __Song__. __Artists__
33
+ have multiple __Song__ and a __Song__ is associate with an __Artist__. Each
34
+ object type has attributes such as a `name`. Pretty basic and simple.
35
+
36
+ ## Define the API
37
+
38
+ GraphQL uses SDL (Schema Definition Language) which is sometimes refered to as
39
+ "type system definition language" in the GraphQL specification. GraphQL types
40
+ can, in theory be defined in any language but most common language agnostic
41
+ language is SDL so lets use SDL to define the API.
42
+
43
+ ```
44
+ type Artist {
45
+ name: String!
46
+ songs: [Song]
47
+ origin: [String]
48
+ }
49
+
50
+ type Song {
51
+ name: String!
52
+ artist: Artist
53
+ duration: Int
54
+ release: String
55
+ }
56
+ ```
57
+
58
+ Pretty easy to understand. An __Artist__ has a `name` that is a `String`,
59
+ `songs` that is an array of __Song__ objects, and `origin` which is a `String`
60
+ array. __Song__ is similar but with one odd field. The `release` field should
61
+ be a time or date type but GraphQL does not have that type defined as a core
62
+ type. To be completely portable between any GraphQL implementation a `String`
63
+ is used. The GraphQL implemenation we will use as added the `Time` type so
64
+ lets change the __Song__ definition so that the `release` field is a `Time`
65
+ type. The returned value will be a `String` but by setting the type to `Time`
66
+ we document the API more accurately.
67
+
68
+ ```
69
+ release: Time
70
+ ```
71
+
72
+ The last step is to describe how to get one or more of the objects. This is
73
+ referred to as the root or for queries the query root. Our root will have just
74
+ one field or method called `artist` and will require an artist `name`.
75
+
76
+ ```
77
+ type Query {
78
+ artist(name: String!): Artist
79
+ }
80
+ ```
81
+
82
+ ## Writing the Application
83
+
84
+ Ruby will be used write the application. There is more than one implemenation
85
+ of a GraphQL server for Ruby. Some approachs require the SDL above to be
86
+ translated into a Ruby equivalent. [Agoo](https://github.com/ohler55/agoo)
87
+ uses the SDL defintion as it is and the Ruby code is plain vanilla Ruby so
88
+ that will be used for this example.
89
+
90
+ By having the Ruby class names match the GraphQL type names we keep things
91
+ simple. Note that the Ruby classes match the GraphQL types.
92
+
93
+ ```ruby
94
+ class Artist
95
+ attr_reader :name
96
+ attr_reader :songs
97
+ attr_reader :origin
98
+
99
+ def initialize(name, origin)
100
+ @name = name
101
+ @songs = []
102
+ @origin = origin
103
+ end
104
+
105
+ def song(args={})
106
+ @songs[args['name']]
107
+ end
108
+
109
+ # Only used by the Song to add itself to the artist.
110
+ def add_song(song)
111
+ @songs << song
112
+ end
113
+ end
114
+
115
+ class Song
116
+ attr_reader :name # string
117
+ attr_reader :artist # reference
118
+ attr_reader :duration # integer
119
+ attr_reader :release # time
120
+
121
+ def initialize(name, artist, duration, release)
122
+ @name = name
123
+ @artist = artist
124
+ @duration = duration
125
+ @release = release
126
+ artist.add_song(self)
127
+ end
128
+ end
129
+ ```
130
+
131
+ Method match fields in the Ruby classes. Note the method all have
132
+ either no arguments or `args={}`. This is what the GraphQL APIs expect
133
+ and the [Agoo](https://github.com/ohler55/agoo) GraphQL implementation
134
+ follows suit. [Agoo](https://github.com/ohler55/agoo) offers
135
+ additional options that are available based on the method
136
+ signature. If a method signature is `(args, req)` or `(args, req,
137
+ plan)` then the request object is included. The plan, if included, is
138
+ an object that can be queried for information about the query. (It has
139
+ not been implemented as of version 2.12.0.)
140
+
141
+ The `initialize` methods are used to set up the data for this example
142
+ as we will see shortly.
143
+
144
+ The query root class als needs to be defined. Note the `artist` method which
145
+ matches the SDL `Query` root type. An `attr_reader` for `artists` was also
146
+ added. That would be exposed to the API simply by adding that field to the
147
+ `Query` type in the SDL document.
148
+
149
+ ```ruby
150
+ class Query
151
+ attr_reader :artists
152
+
153
+ def initialize(artists)
154
+ @artists = artists
155
+ end
156
+
157
+ def artist(args={})
158
+ @artists[args['name']]
159
+ end
160
+ end
161
+ ```
162
+
163
+ The GraphQL root, not to be confused with the query root sites above the query
164
+ root. GraphQL defines it to optionally have three field. The Ruby class in
165
+ this case implements on the `query` field. The initializer loads up some data
166
+ for an Indie band from New Zealand I like to listen to.
167
+
168
+ ```ruby
169
+ class Schema
170
+ attr_reader :query
171
+ attr_reader :mutation
172
+ attr_reader :subscription
173
+
174
+ def initialize()
175
+ # Set up some data for testing.
176
+ artist = Artist.new('Fazerdaze', ['Morningside', 'Auckland', 'New Zealand'])
177
+ Song.new('Jennifer', artist, 240, Time.utc(2017, 5, 5))
178
+ Song.new('Lucky Girl', artist, 170, Time.utc(2017, 5, 5))
179
+ Song.new('Friends', artist, 194, Time.utc(2017, 5, 5))
180
+ Song.new('Reel', artist, 193, Time.utc(2015, 11, 2))
181
+ @artists = {artist.name => artist}
182
+
183
+ @query = Query.new(@artists)
184
+ end
185
+ end
186
+ ```
187
+
188
+ The final hookup is implemenation specific. For
189
+ [Agoo](https://github.com/ohler55/agoo) the server is initialized to include a
190
+ handler for the `/graphql` HTTP request path ad then it is started.
191
+
192
+ ```
193
+ Agoo::Server.init(6464, 'root', thread_count: 1, graphql: '/graphql')
194
+ Agoo::Server.start()
195
+ ```
196
+
197
+ The GraphQL implementation is then configured with the SDL defined earlier
198
+ (`$songs_sdl`) and then the appliation sleeps while the server processes
199
+ requests.
200
+
201
+ ```
202
+ Agoo::GraphQL.schema(Schema.new) {
203
+ Agoo::GraphQL.load($songs_sdl)
204
+ }
205
+ sleep
206
+ ```
207
+
208
+ The code for this example is in (https://github.com/ohler55/agoo/example/graphql/song.rb).
209
+
210
+ ## Using the API
211
+
212
+ A web broswer can be used to try out the API as can `curl`.
213
+
214
+ The GraphQL query to try looks like the following.
215
+
216
+ ```
217
+ {
218
+ artist(name:"Fazerdaze") {
219
+ name
220
+ songs{
221
+ name
222
+ duration
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ The query asks for the __Artist__ named `Frazerdaze` and returns the `name` and `songs` in a JSON document. For each __Song__ the `name` and `duration` of the __Song__ is returned in a JSON object for each __Song__. The output should look like this.
229
+
230
+ ```
231
+ {
232
+ "data":{
233
+ "artist":{
234
+ "name":"Fazerdaze",
235
+ "songs":[
236
+ {
237
+ "name":"Jennifer",
238
+ "duration":240
239
+ },
240
+ {
241
+ "name":"Lucky Girl",
242
+ "duration":170
243
+ },
244
+ {
245
+ "name":"Friends",
246
+ "duration":194
247
+ },
248
+ {
249
+ "name":"Reel",
250
+ "duration":193
251
+ }
252
+ ]
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ After getting rid of the optional whitespace in the query an HTTP GET made
259
+ with curl should return that content.
260
+
261
+ ```
262
+ curl -w "\n" 'localhost:6464/graphql?query=\{artist(name:"Fazerdaze")\{name,songs\{name,duration\}\}\}&indent=2'
263
+ ```
264
+
265
+ Try changing the query and replace `duration` with `release` and not the
266
+ conversion of the Ruby Time to a JSON string.
267
+
268
+ Hope you enjoyed the example. Now you can sing the GraphQL song.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agoo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.10
4
+ version: 2.15.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-14 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -41,6 +41,13 @@ extra_rdoc_files:
41
41
  - README.md
42
42
  - CHANGELOG.md
43
43
  - LICENSE
44
+ - misc/flymd.md
45
+ - misc/glue.md
46
+ - misc/optimize.md
47
+ - misc/push.md
48
+ - misc/rails.md
49
+ - misc/song.md
50
+ - misc/glue-diagram.svg
44
51
  files:
45
52
  - CHANGELOG.md
46
53
  - LICENSE
@@ -150,6 +157,13 @@ files:
150
157
  - lib/agoo/graphql/type.rb
151
158
  - lib/agoo/version.rb
152
159
  - lib/rack/handler/agoo.rb
160
+ - misc/flymd.md
161
+ - misc/glue-diagram.svg
162
+ - misc/glue.md
163
+ - misc/optimize.md
164
+ - misc/push.md
165
+ - misc/rails.md
166
+ - misc/song.md
153
167
  - test/base_handler_test.rb
154
168
  - test/bind_test.rb
155
169
  - test/domain_test.rb
@@ -164,7 +178,9 @@ homepage: https://github.com/ohler55/agoo
164
178
  licenses:
165
179
  - MIT
166
180
  metadata:
181
+ bug_tracker_uri: https://github.com/ohler55/agoo/issues
167
182
  changelog_uri: https://github.com/ohler55/agoo/CHANGELOG.md
183
+ documentation_uri: http://www.ohler.com/agoo/index.html
168
184
  source_code_uri: https://github.com/ohler55/agoo
169
185
  homepage: https://github.com/ohler55/agoo
170
186
  post_install_message:
@@ -193,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
209
  version: '0'
194
210
  requirements:
195
211
  - Linux or macOS
196
- rubygems_version: 3.4.10
212
+ rubygems_version: 3.4.1
197
213
  signing_key:
198
214
  specification_version: 4
199
215
  summary: An HTTP server