agoo 2.15.9 → 2.15.11

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/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.9
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-02-26 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
@@ -163,7 +177,12 @@ files:
163
177
  homepage: https://github.com/ohler55/agoo
164
178
  licenses:
165
179
  - MIT
166
- metadata: {}
180
+ metadata:
181
+ bug_tracker_uri: https://github.com/ohler55/agoo/issues
182
+ changelog_uri: https://github.com/ohler55/agoo/CHANGELOG.md
183
+ documentation_uri: http://www.ohler.com/agoo/index.html
184
+ source_code_uri: https://github.com/ohler55/agoo
185
+ homepage: https://github.com/ohler55/agoo
167
186
  post_install_message:
168
187
  rdoc_options:
169
188
  - "-t"