mini_sql 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/CHANGELOG +4 -0
  4. data/README.md +36 -0
  5. data/bench/builder_perf.rb +138 -0
  6. data/bench/decorator_perf.rb +143 -0
  7. data/bench/mini_sql_methods_perf.rb +80 -0
  8. data/bench/prepared_perf.rb +59 -0
  9. data/bench/shared/generate_data.rb +133 -0
  10. data/bench/topic_perf.rb +21 -327
  11. data/bench/topic_wide_perf.rb +92 -0
  12. data/lib/mini_sql.rb +17 -8
  13. data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
  14. data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
  15. data/lib/mini_sql/builder.rb +61 -30
  16. data/lib/mini_sql/inline_param_encoder.rb +4 -3
  17. data/lib/mini_sql/mysql/connection.rb +6 -0
  18. data/lib/mini_sql/mysql/deserializer_cache.rb +3 -3
  19. data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
  20. data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
  21. data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
  22. data/lib/mini_sql/postgres/connection.rb +6 -0
  23. data/lib/mini_sql/postgres/deserializer_cache.rb +5 -5
  24. data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
  25. data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
  26. data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
  27. data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +3 -3
  28. data/lib/mini_sql/result.rb +10 -0
  29. data/lib/mini_sql/serializer.rb +29 -15
  30. data/lib/mini_sql/sqlite/connection.rb +9 -1
  31. data/lib/mini_sql/sqlite/deserializer_cache.rb +3 -3
  32. data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
  33. data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
  34. data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
  35. data/lib/mini_sql/version.rb +1 -1
  36. metadata +19 -2
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GenerateData
4
+ class ::Topic < ActiveRecord::Base;
5
+ belongs_to :user
6
+ belongs_to :category
7
+ end
8
+ class ::User < ActiveRecord::Base; end
9
+ class ::Category < ActiveRecord::Base; end
10
+
11
+ def initialize(count_records:)
12
+ @count_records = count_records
13
+ end
14
+
15
+ def call
16
+ conn_settings = {
17
+ password: 'postgres',
18
+ user: 'postgres',
19
+ host: 'localhost'
20
+ }
21
+
22
+ db_conn = conn_settings.merge(database: "test_db", adapter: "postgresql")
23
+
24
+ pg = PG::Connection.new(conn_settings)
25
+ pg.exec "DROP DATABASE IF EXISTS test_db"
26
+ pg.exec "CREATE DATABASE test_db"
27
+ pg.close
28
+
29
+ ActiveRecord::Base.establish_connection(db_conn)
30
+ pg = ActiveRecord::Base.connection.raw_connection
31
+
32
+ pg.exec <<~SQL
33
+ drop table if exists topics;
34
+ drop table if exists users;
35
+ drop table if exists categories;
36
+ CREATE TABLE topics (
37
+ id integer NOT NULL PRIMARY KEY,
38
+ title character varying NOT NULL,
39
+ last_posted_at timestamp without time zone,
40
+ created_at timestamp without time zone NOT NULL,
41
+ updated_at timestamp without time zone NOT NULL,
42
+ views integer DEFAULT 0 NOT NULL,
43
+ posts_count integer DEFAULT 0 NOT NULL,
44
+ user_id integer,
45
+ last_post_user_id integer NOT NULL,
46
+ reply_count integer DEFAULT 0 NOT NULL,
47
+ featured_user1_id integer,
48
+ featured_user2_id integer,
49
+ featured_user3_id integer,
50
+ avg_time integer,
51
+ deleted_at timestamp without time zone,
52
+ highest_post_number integer DEFAULT 0 NOT NULL,
53
+ image_url character varying,
54
+ like_count integer DEFAULT 0 NOT NULL,
55
+ incoming_link_count integer DEFAULT 0 NOT NULL,
56
+ category_id integer,
57
+ visible boolean DEFAULT true NOT NULL,
58
+ moderator_posts_count integer DEFAULT 0 NOT NULL,
59
+ closed boolean DEFAULT false NOT NULL,
60
+ archived boolean DEFAULT false NOT NULL,
61
+ bumped_at timestamp without time zone NOT NULL,
62
+ has_summary boolean DEFAULT false NOT NULL,
63
+ vote_count integer DEFAULT 0 NOT NULL,
64
+ archetype character varying DEFAULT 'regular'::character varying NOT NULL,
65
+ featured_user4_id integer,
66
+ notify_moderators_count integer DEFAULT 0 NOT NULL,
67
+ spam_count integer DEFAULT 0 NOT NULL,
68
+ pinned_at timestamp without time zone,
69
+ score double precision,
70
+ percent_rank double precision DEFAULT 1.0 NOT NULL,
71
+ subtype character varying,
72
+ slug character varying,
73
+ deleted_by_id integer,
74
+ participant_count integer DEFAULT 1,
75
+ word_count integer,
76
+ excerpt character varying(1000),
77
+ pinned_globally boolean DEFAULT false NOT NULL,
78
+ pinned_until timestamp without time zone,
79
+ fancy_title character varying(400),
80
+ highest_staff_post_number integer DEFAULT 0 NOT NULL,
81
+ featured_link character varying
82
+ );
83
+
84
+ CREATE TABLE users (
85
+ id integer NOT NULL PRIMARY KEY,
86
+ first_name character varying NOT NULL,
87
+ last_name character varying NOT NULL
88
+ );
89
+ CREATE TABLE categories (
90
+ id integer NOT NULL PRIMARY KEY,
91
+ name character varying NOT NULL,
92
+ title character varying NOT NULL,
93
+ description character varying NOT NULL
94
+ );
95
+ SQL
96
+
97
+ generate_table(Topic)
98
+ generate_table(User)
99
+ generate_table(Category)
100
+
101
+ pg.exec <<~SQL
102
+ CREATE INDEX user_id ON topics USING btree (user_id);
103
+ CREATE INDEX category_id ON topics USING btree (category_id);
104
+ SQL
105
+
106
+ pg.exec "vacuum full analyze topics"
107
+ pg.exec "vacuum full analyze users"
108
+ pg.exec "vacuum full analyze categories"
109
+
110
+ [ActiveRecord::Base.connection, db_conn]
111
+ end
112
+
113
+ def generate_table(klass)
114
+ data =
115
+ @count_records.times.map do |id|
116
+ topic = { id: id }
117
+ klass.columns.each do |c|
118
+ topic[c.name.to_sym] = value_from_type(c.type)
119
+ end
120
+ topic
121
+ end
122
+ klass.insert_all(data)
123
+ end
124
+
125
+ def value_from_type(type)
126
+ case type
127
+ when :integer then rand(1000)
128
+ when :datetime then Time.now
129
+ when :boolean then false
130
+ else "HELLO WORLD" * 2
131
+ end
132
+ end
133
+ end
data/bench/topic_perf.rb CHANGED
@@ -13,8 +13,9 @@ gemfile do
13
13
  gem 'benchmark-ips'
14
14
  gem 'sequel', github: 'jeremyevans/sequel'
15
15
  gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
16
- gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
16
+ gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres' # sudo apt-get install uuid-dev
17
17
  gem 'draper'
18
+ gem 'pry'
18
19
  end
19
20
 
20
21
  require 'sequel'
@@ -23,68 +24,14 @@ require 'memory_profiler'
23
24
  require 'benchmark/ips'
24
25
  require 'mini_sql'
25
26
 
26
- ActiveRecord::Base.establish_connection(
27
- :adapter => "postgresql",
28
- :database => "test_db"
29
- )
27
+ require '../mini_sql/bench/shared/generate_data'
30
28
 
31
- DB = Sequel.postgres('test_db')
32
-
33
- pg = ActiveRecord::Base.connection.raw_connection
34
-
35
- pg.async_exec <<SQL
36
- drop table if exists topics
37
- SQL
38
-
39
- pg.async_exec <<SQL
40
- CREATE TABLE topics (
41
- id integer NOT NULL PRIMARY KEY,
42
- title character varying NOT NULL,
43
- last_posted_at timestamp without time zone,
44
- created_at timestamp without time zone NOT NULL,
45
- updated_at timestamp without time zone NOT NULL,
46
- views integer DEFAULT 0 NOT NULL,
47
- posts_count integer DEFAULT 0 NOT NULL,
48
- user_id integer,
49
- last_post_user_id integer NOT NULL,
50
- reply_count integer DEFAULT 0 NOT NULL,
51
- featured_user1_id integer,
52
- featured_user2_id integer,
53
- featured_user3_id integer,
54
- avg_time integer,
55
- deleted_at timestamp without time zone,
56
- highest_post_number integer DEFAULT 0 NOT NULL,
57
- image_url character varying,
58
- like_count integer DEFAULT 0 NOT NULL,
59
- incoming_link_count integer DEFAULT 0 NOT NULL,
60
- category_id integer,
61
- visible boolean DEFAULT true NOT NULL,
62
- moderator_posts_count integer DEFAULT 0 NOT NULL,
63
- closed boolean DEFAULT false NOT NULL,
64
- archived boolean DEFAULT false NOT NULL,
65
- bumped_at timestamp without time zone NOT NULL,
66
- has_summary boolean DEFAULT false NOT NULL,
67
- vote_count integer DEFAULT 0 NOT NULL,
68
- archetype character varying DEFAULT 'regular'::character varying NOT NULL,
69
- featured_user4_id integer,
70
- notify_moderators_count integer DEFAULT 0 NOT NULL,
71
- spam_count integer DEFAULT 0 NOT NULL,
72
- pinned_at timestamp without time zone,
73
- score double precision,
74
- percent_rank double precision DEFAULT 1.0 NOT NULL,
75
- subtype character varying,
76
- slug character varying,
77
- deleted_by_id integer,
78
- participant_count integer DEFAULT 1,
79
- word_count integer,
80
- excerpt character varying(1000),
81
- pinned_globally boolean DEFAULT false NOT NULL,
82
- pinned_until timestamp without time zone,
83
- fancy_title character varying(400),
84
- highest_staff_post_number integer DEFAULT 0 NOT NULL,
85
- featured_link character varying
86
- )
87
- SQL
29
+ ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
30
+ PG_CONN = ar_connection.raw_connection
31
+ MINI_SQL = MiniSql::Connection.get(PG_CONN)
32
+ DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
33
+ # connects over unix socket
34
+ SWIFT = Swift::DB::Postgres.new(db: conn_config[:database], user: conn_config[:user], password: conn_config[:password], host: conn_config[:host])
88
35
 
89
36
  class Topic < ActiveRecord::Base
90
37
  end
@@ -92,26 +39,6 @@ end
92
39
  class TopicSequel < Sequel::Model(:topics)
93
40
  end
94
41
 
95
- Topic.transaction do
96
- topic = {
97
- }
98
- Topic.columns.each do |c|
99
- topic[c.name.to_sym] = case c.type
100
- when :integer then 1
101
- when :datetime then Time.now
102
- when :boolean then false
103
- else "HELLO WORLD" * 2
104
- end
105
- end
106
-
107
- 1000.times do |id|
108
- topic[:id] = id
109
- Topic.create!(topic)
110
- end
111
- end
112
-
113
- $conn = ActiveRecord::Base.connection.raw_connection
114
-
115
42
  def ar_title_id_pluck
116
43
  s = +""
117
44
  Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
@@ -133,7 +60,7 @@ end
133
60
  def pg_title_id
134
61
  s = +""
135
62
  # use the safe pattern here
136
- r = $conn.async_exec(-"select id, title from topics order by id limit 1000")
63
+ r = PG_CONN.async_exec(-"select id, title from topics order by id limit 1000")
137
64
 
138
65
  # this seems fastest despite extra arrays, cause array of arrays is generated
139
66
  # in c code
@@ -150,11 +77,9 @@ def pg_title_id
150
77
  s
151
78
  end
152
79
 
153
- $mini_sql = MiniSql::Connection.get($conn)
154
-
155
80
  def mini_sql_title_id
156
81
  s = +""
157
- $mini_sql.query(-"select id, title from topics order by id limit 1000").each do |t|
82
+ MINI_SQL.query(-"select id, title from topics order by id limit 1000").each do |t|
158
83
  s << t.id.to_s
159
84
  s << t.title
160
85
  end
@@ -183,7 +108,7 @@ end
183
108
  def mini_sql_title_id_query_single
184
109
  s = +""
185
110
  i = 0
186
- r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
111
+ r = MINI_SQL.query_single(-"select id, title from topics order by id limit 1000")
187
112
  while i < r.length
188
113
  s << r[i].to_s
189
114
  s << r[i + 1]
@@ -192,13 +117,10 @@ def mini_sql_title_id_query_single
192
117
  s
193
118
  end
194
119
 
195
- # connects over unix socket
196
- $swift = Swift::DB::Postgres.new(db: "test_db")
197
-
198
120
  def swift_select_title_id(l = 1000)
199
121
  s = +''
200
122
  i = 0
201
- r = $swift.execute("select id, title from topics order by id limit 1000")
123
+ r = SWIFT.execute("select id, title from topics order by id limit 1000")
202
124
  while i < r.selected_rows
203
125
  s << r.get(i, 0).to_s
204
126
  s << r.get(i, 1)
@@ -220,175 +142,6 @@ results = [
220
142
 
221
143
  exit(-1) unless results.uniq.length == 1
222
144
 
223
- # https://github.com/drapergem/draper
224
- class TopicDraper < Draper::Decorator
225
- delegate :id
226
-
227
- def title_bang
228
- object.title + '!!!'
229
- end
230
- end
231
-
232
- # https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html
233
- class TopicSimpleDelegator < SimpleDelegator
234
- def title_bang
235
- title + '!!!'
236
- end
237
- end
238
-
239
- class TopicDecoratorSequel < TopicSequel
240
- def title_bang
241
- title + '!!!'
242
- end
243
- end
244
-
245
- class TopicArModel < Topic
246
- def title_bang
247
- title + '!!!'
248
- end
249
- end
250
-
251
- module TopicDecorator
252
- def title_bang
253
- title + '!!!'
254
- end
255
- end
256
-
257
- Benchmark.ips do |r|
258
- r.report('query_decorator') do |n|
259
- while n > 0
260
- $mini_sql.query_decorator(TopicDecorator, 'select id, title from topics order by id limit 1000').each do |obj|
261
- obj.title_bang
262
- obj.id
263
- end
264
- n -= 1
265
- end
266
- end
267
- r.report('extend') do |n|
268
- while n > 0
269
- $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
270
- d_obj = obj.extend(TopicDecorator)
271
- d_obj.title_bang
272
- d_obj.id
273
- end
274
- n -= 1
275
- end
276
- end
277
- r.report('draper') do |n|
278
- while n > 0
279
- $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
280
- d_obj = TopicDraper.new(obj)
281
- d_obj.title_bang
282
- d_obj.id
283
- end
284
- n -= 1
285
- end
286
- end
287
- r.report('simple_delegator') do |n|
288
- while n > 0
289
- $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
290
- d_obj = TopicSimpleDelegator.new(obj)
291
- d_obj.title_bang
292
- d_obj.id
293
- end
294
- n -= 1
295
- end
296
- end
297
- r.report('query') do |n|
298
- while n > 0
299
- $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
300
- obj.title + '!!!'
301
- obj.id
302
- end
303
- n -= 1
304
- end
305
- end
306
- r.report('ar model') do |n|
307
- while n > 0
308
- TopicArModel.limit(1000).order(:id).select(:id, :title).each do |obj|
309
- obj.title_bang
310
- obj.id
311
- end
312
- n -= 1
313
- end
314
- end
315
- r.report('sequel model') do |n|
316
- while n > 0
317
- TopicDecoratorSequel.limit(1000).order(:id).select(:id, :title).each do |obj|
318
- obj.title_bang
319
- obj.id
320
- end
321
- n -= 1
322
- end
323
- end
324
-
325
- r.compare!
326
- end
327
-
328
- # Comparison:
329
- # query: 828.4 i/s
330
- # query_decorator: 819.3 i/s - same-ish: difference falls within error
331
- # sequel model: 672.4 i/s - 1.23x slower
332
- # extend: 519.4 i/s - 1.59x slower
333
- # simple_delegator: 496.8 i/s - 1.67x slower
334
- # draper: 416.2 i/s - 1.99x slower
335
- # ar model: 113.4 i/s - 7.30x slower
336
-
337
- Benchmark.ips do |r|
338
- r.report('query_hash') do |n|
339
- while n > 0
340
- $mini_sql.query_hash('select id, title from topics order by id limit 1000').each do |hash|
341
- [hash['id'], hash['title']]
342
- end
343
- n -= 1
344
- end
345
- end
346
- r.report('query_array') do |n|
347
- while n > 0
348
- $mini_sql.query_array('select id, title from topics order by id limit 1000').each do |id, title|
349
- [id, title]
350
- end
351
- n -= 1
352
- end
353
- end
354
- r.report('query') do |n|
355
- while n > 0
356
- $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
357
- [obj.id, obj.title]
358
- end
359
- n -= 1
360
- end
361
- end
362
-
363
- r.compare!
364
- end
365
-
366
- # Comparison:
367
- # query_array: 1351.6 i/s
368
- # query: 963.8 i/s - 1.40x slower
369
- # query_hash: 787.4 i/s - 1.72x slower
370
-
371
- Benchmark.ips do |r|
372
- r.report('query_single') do |n|
373
- while n > 0
374
- $mini_sql.query_single('select id from topics order by id limit 1000')
375
- n -= 1
376
- end
377
- end
378
- r.report('query_array') do |n|
379
- while n > 0
380
- $mini_sql.query_array('select id from topics order by id limit 1000').flatten
381
- n -= 1
382
- end
383
- end
384
-
385
- r.compare!
386
- end
387
-
388
- # Comparison:
389
- # query_single: 2368.9 i/s
390
- # query_array: 1350.1 i/s - 1.75x slower
391
-
392
145
  Benchmark.ips do |r|
393
146
  r.report("ar select title id") do |n|
394
147
  while n > 0
@@ -441,71 +194,12 @@ Benchmark.ips do |r|
441
194
  r.compare!
442
195
  end
443
196
 
444
- def wide_topic_ar
445
- Topic.first
446
- end
447
-
448
- def wide_topic_pg
449
- r = $conn.async_exec("select * from topics limit 1")
450
- row = r.first
451
- r.clear
452
- row
453
- end
454
-
455
- def wide_topic_sequel
456
- TopicSequel.first
457
- end
458
-
459
- def wide_topic_mini_sql
460
- $conn.query("select * from topics limit 1").first
461
- end
462
-
463
- Benchmark.ips do |r|
464
- r.report("wide topic ar") do |n|
465
- while n > 0
466
- wide_topic_ar
467
- n -= 1
468
- end
469
- end
470
- r.report("wide topic sequel") do |n|
471
- while n > 0
472
- wide_topic_sequel
473
- n -= 1
474
- end
475
- end
476
- r.report("wide topic pg") do |n|
477
- while n > 0
478
- wide_topic_pg
479
- n -= 1
480
- end
481
- end
482
- r.report("wide topic mini sql") do |n|
483
- while n > 0
484
- wide_topic_mini_sql
485
- n -= 1
486
- end
487
- end
488
- r.compare!
489
- end
490
-
491
197
  # Comparison:
492
- # pg select title id: 1519.7 i/s
493
- # mini_sql query_single title id: 1335.0 i/s - 1.14x slower
494
- # sequel title id pluck: 1261.6 i/s - 1.20x slower
495
- # mini_sql select title id: 1188.6 i/s - 1.28x slower
496
- # swift title id: 1077.5 i/s - 1.41x slower
497
- # sequel title id select: 969.7 i/s - 1.57x slower
498
- # ar select title id pluck: 738.7 i/s - 2.06x slower
499
- # ar select title id: 149.6 i/s - 10.16x slower
500
- #
501
- #
502
- # Comparison:
503
- # wide topic pg: 7474.0 i/s
504
- # wide topic mini sql: 7355.2 i/s - same-ish: difference falls within error
505
- # wide topic sequel: 5696.8 i/s - 1.31x slower
506
- # wide topic ar: 2515.0 i/s - 2.97x slower
507
-
508
- # to run deep analysis run
509
- # MemoryProfiler.report do
510
- # ar
511
- # end.pretty_print
198
+ # pg select title id: 1315.1 i/s
199
+ # swift title id: 1268.4 i/s - same-ish: difference falls within error
200
+ # mini_sql query_single title id: 1206.3 i/s - same-ish: difference falls within error
201
+ # mini_sql select title id: 1063.8 i/s - 1.24x (± 0.00) slower
202
+ # sequel title id pluck: 1054.5 i/s - 1.25x (± 0.00) slower
203
+ # sequel title id select: 814.1 i/s - 1.62x (± 0.00) slower
204
+ # ar select title id pluck: 667.7 i/s - 1.97x (± 0.00) slower
205
+ # ar select title id: 215.8 i/s - 6.09x (± 0.00) slower