activerecord-analyze 0.4.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5163e6745e43205926a1c88a02c4459bd3d9971dde022f5dd64c3368d2c72c13
4
- data.tar.gz: f084b56b9a5b9c5ba1e6ae69e6653c01224287b6e0107ca97666cbd695c04b73
3
+ metadata.gz: b2021239ed9352a040106ce8c1de50503e785fccd9a3093928eeaf5dfa79a0dc
4
+ data.tar.gz: 8ba00244e759dc1f31091ec4196aded3887c9007902faf867431ea2ef9ae93dc
5
5
  SHA512:
6
- metadata.gz: 10a11c9983859c51270ae38503e6f751b49f281c85c2f5fd7f28a7709f93d285e6288db8c00741e402f031771b1208c531a142ee28b2bcf5f84d8f14f84c8c18
7
- data.tar.gz: 3669dc47d6dc8e4b1b63881d3bf61fa2d6eff4be1264dab55f77a98a6e55d98ebf22cd37e22b731f1d866954c20032f381d3577db168527d7490b88275037f98
6
+ metadata.gz: ad2874f8422dc7299b8b7b3036fbec8e90f29780862ea3dd192629894905aafede52731314cc569afb6c9ce31f61a379b31ac308af0c304fc9e72bea583e58a7
7
+ data.tar.gz: 114236b8af394e3f7fad06549e3bc99fa1814e815dd93282c7d3abfc0a0535ee568d90c0360abab021c83a7f1b38d2bfd0bfec31f043720893ab321776fe83fe
@@ -0,0 +1,30 @@
1
+ version: 2
2
+ jobs:
3
+ test:
4
+ docker:
5
+ - image: circleci/ruby:2.6.5
6
+ environment:
7
+ DATABASE_URL: postgresql://postgres:secret@localhost:5432/activerecord-analyze-test
8
+ - image: circleci/postgres:11.5
9
+ environment:
10
+ POSTGRES_USER: postgres
11
+ POSTGRES_DB: activerecord-analyze-test
12
+ POSTGRES_PASSWORD: secret
13
+ parallelism: 1
14
+ steps:
15
+ - checkout
16
+ - run: gem update --system
17
+ - run: gem install bundler
18
+ - run: bundle install --path vendor/bundle
19
+ - run: sudo apt-get update
20
+ - run: sudo apt install postgresql-client-11
21
+ - run: dockerize -wait tcp://localhost:5432 -timeout 1m
22
+ - run:
23
+ name: Run specs
24
+ command: |
25
+ bundle exec rspec spec/
26
+ workflows:
27
+ version: 2
28
+ test:
29
+ jobs:
30
+ - test
data/README.md CHANGED
@@ -1,7 +1,13 @@
1
- # ActiveRecord Analyze
1
+ # ActiveRecord Analyze [![Gem Version](https://badge.fury.io/rb/activerecord-analyze.svg)](https://badge.fury.io/rb/activerecord-analyze) [![CircleCI](https://circleci.com/gh/pawurb/activerecord-analyze.svg?style=svg)](https://circleci.com/gh/pawurb/activerecord-analyze)
2
2
 
3
3
  This gem adds an `analyze` method to Active Record query objects. It executes `EXPLAIN ANALYZE` on a query SQL.
4
4
 
5
+ You can check out this blog post for more info on how to [debug and fix slow queries in Rails apps](https://pawelurbanek.com/slow-rails-queries).
6
+
7
+ The following `format` options are supported `:json, :hash, :yaml, :text, :xml`. Especially the `:json` format is useful because it let's you visualize a query plan using [a visualizer tool](https://tatiyants.com/pev/#/plans/new).
8
+
9
+ ![PG Query visualizer plan](https://raw.githubusercontent.com/pawurb/activerecord-analyze/master/query-plan.png)
10
+
5
11
  ## Installation
6
12
 
7
13
  In your Gemfile:
@@ -12,6 +18,97 @@ gem 'activerecord-analyze'
12
18
 
13
19
  ```
14
20
 
21
+ ## Options
22
+
23
+ The `analyze` method supports the following EXPLAIN query options ([PostgreSQL docs reference](https://www.postgresql.org/docs/12/sql-explain.html)):
24
+
25
+ ```
26
+ BUFFERS [ boolean ]
27
+ VERBOSE [ boolean ]
28
+ COSTS [ boolean ]
29
+ SETTINGS [ boolean ]
30
+ TIMING [ boolean ]
31
+ SUMMARY [ boolean ]
32
+ FORMAT { TEXT | XML | JSON | YAML }
33
+ ```
34
+
35
+ You can execute it like that:
36
+
37
+ ```ruby
38
+
39
+ User.all.analyze(
40
+ format: :json,
41
+ verbose: true,
42
+ costs: true,
43
+ settings: true,
44
+ buffers: true,
45
+ timing: true,
46
+ summary: true
47
+ )
48
+
49
+ # EXPLAIN (FORMAT JSON, ANALYZE, VERBOSE, COSTS, SETTINGS, BUFFERS, TIMING, SUMMARY)
50
+ # SELECT "users".* FROM "users"
51
+ # [
52
+ # {
53
+ # "Plan": {
54
+ # "Node Type": "Seq Scan",
55
+ # "Parallel Aware": false,
56
+ # "Relation Name": "users",
57
+ # "Schema": "public",
58
+ # "Alias": "users",
59
+ # "Startup Cost": 0.00,
60
+ # "Total Cost": 11.56,
61
+ # "Plan Rows": 520,
62
+ # "Plan Width": 127,
63
+ # "Actual Startup Time": 0.006,
64
+ # "Actual Total Time": 0.007,
65
+ # "Actual Rows": 2,
66
+ # "Actual Loops": 1,
67
+ # "Output": ["id", "team_id", "email"],
68
+ # "Shared Hit Blocks": 1,
69
+ # "Shared Read Blocks": 0,
70
+ # "Shared Dirtied Blocks": 0,
71
+ # "Shared Written Blocks": 0,
72
+ # "Local Hit Blocks": 0,
73
+ # "Local Read Blocks": 0,
74
+ # "Local Dirtied Blocks": 0,
75
+ # "Local Written Blocks": 0,
76
+ # "Temp Read Blocks": 0,
77
+ # "Temp Written Blocks": 0,
78
+ # "I/O Read Time": 0.000,
79
+ # "I/O Write Time": 0.000
80
+ # },
81
+ # "Settings": {
82
+ # "cpu_index_tuple_cost": "0.001",
83
+ # "cpu_operator_cost": "0.0005",
84
+ # "cpu_tuple_cost": "0.003",
85
+ # "effective_cache_size": "10800000kB",
86
+ # "max_parallel_workers_per_gather": "1",
87
+ # "random_page_cost": "2",
88
+ # "work_mem": "100MB"
89
+ # },
90
+ # "Planning Time": 0.033,
91
+ # "Triggers": [
92
+ # ],
93
+ # "Execution Time": 0.018
94
+ # }
95
+ # ]
96
+
97
+ ```
98
+
99
+ Optionally you can disable running the `ANALYZE` query and only generate the plan:
100
+
101
+ ```ruby
102
+
103
+ User.all.analyze(analyze: false)
104
+
105
+ # EXPLAIN ANALYZE for: SELECT "users".* FROM "users"
106
+ # QUERY PLAN
107
+ # ----------------------------------------------------------
108
+ # Seq Scan on users (cost=0.00..15.20 rows=520 width=127)
109
+
110
+ ```
111
+
15
112
  ### Disclaimer
16
113
 
17
114
  It is a bit experimental and can break with new Rails release.
@@ -16,4 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.license = "MIT"
18
18
  gem.add_dependency "rails", ">= 5.1.0"
19
+ gem.add_development_dependency "rake"
20
+ gem.add_development_dependency "pg"
21
+ gem.add_development_dependency "rspec"
19
22
  end
@@ -0,0 +1,11 @@
1
+ version: '3'
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:11.5-alpine
6
+ environment:
7
+ POSTGRES_USER: postgres
8
+ POSTGRES_DB: ruby-pg-extras-test
9
+ POSTGRES_PASSWORD: secret
10
+ ports:
11
+ - '5432:5432'
@@ -7,6 +7,8 @@ module ActiveRecord
7
7
  case fmt
8
8
  when :json
9
9
  "FORMAT JSON,"
10
+ when :hash
11
+ "FORMAT JSON,"
10
12
  when :yaml
11
13
  "FORMAT YAML,"
12
14
  when :text
@@ -50,6 +52,7 @@ module ActiveRecord
50
52
  .strip.gsub(/\s+/, " ")
51
53
  .gsub(/\(\s?\s?\s?,/, "(")
52
54
  .gsub(/\s,\s/, " ")
55
+ .gsub(/\(\s?\)/, "")
53
56
 
54
57
  sql = "EXPLAIN #{opts_sql} #{to_sql(arel, binds)}"
55
58
  PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN #{opts_sql}".strip, binds))
@@ -62,7 +65,20 @@ end
62
65
  module ActiveRecord
63
66
  class Relation
64
67
  def analyze(opts = {})
65
- exec_analyze(collecting_queries_for_explain { exec_queries }, opts)
68
+ res = exec_analyze(collecting_queries_for_explain { exec_queries }, opts)
69
+ if [:json, :hash].include?(opts[:format])
70
+ start = res.index("[\n")
71
+ finish = res.rindex("]")
72
+ raw_json = res.slice(start, finish - start + 1)
73
+
74
+ if opts[:format] == :json
75
+ JSON.parse(raw_json).to_json
76
+ elsif opts[:format] == :hash
77
+ JSON.parse(raw_json)
78
+ end
79
+ else
80
+ res
81
+ end
66
82
  end
67
83
  end
68
84
  end
@@ -71,7 +87,13 @@ module ActiveRecord
71
87
  module Explain
72
88
  def exec_analyze(queries, opts = {}) # :nodoc:
73
89
  str = queries.map do |sql, binds|
74
- msg = "EXPLAIN ANALYZE for: #{sql}".dup
90
+ analyze_msg = if opts[:analyze] == false
91
+ ""
92
+ else
93
+ " ANALYZE"
94
+ end
95
+
96
+ msg = "EXPLAIN#{analyze_msg} for: #{sql}".dup
75
97
  unless binds.empty?
76
98
  msg << " "
77
99
  msg << binds.map { |attr| render_bind(attr) }.inspect
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordAnalyze
2
- VERSION = "0.4.0"
2
+ VERSION = "0.7.1"
3
3
  end
data/query-plan.png ADDED
Binary file
data/spec/main_spec.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require "migrations/create_users_migration.rb"
5
+
6
+ class User < ActiveRecord::Base; end
7
+
8
+ describe "ActiveRecord analyze" do
9
+ before(:all) do
10
+ ActiveRecord::Base.establish_connection(
11
+ ENV.fetch("DATABASE_URL")
12
+ )
13
+
14
+ @schema_migration = ActiveRecord::Base.connection.schema_migration
15
+ ActiveRecord::Migrator.new(:up, [CreateUsers.new], @schema_migration).migrate
16
+ end
17
+
18
+ describe "no opts" do
19
+ it "works" do
20
+ expect do
21
+ User.all.analyze
22
+ end.not_to raise_error
23
+ end
24
+ end
25
+
26
+ describe "format json" do
27
+ it "works" do
28
+ result = User.all.analyze(format: :json)
29
+ expect(JSON.parse(result)[0].keys.sort).to eq [
30
+ "Execution Time", "Plan", "Planning Time", "Triggers"
31
+ ]
32
+ end
33
+ end
34
+
35
+ describe "format hash" do
36
+ it "works" do
37
+ result = User.all.analyze(format: :hash)
38
+ expect(result[0].keys.sort).to eq [
39
+ "Execution Time", "Plan", "Planning Time", "Triggers"
40
+ ]
41
+ end
42
+ end
43
+
44
+ describe "supports options" do
45
+ it "works" do
46
+ result = User.all.limit(10).where.not(email: nil).analyze(
47
+ format: :hash,
48
+ costs: true,
49
+ timing: true,
50
+ summary: true
51
+ )
52
+ expect(result[0].keys.sort).to eq [
53
+ "Execution Time", "Plan", "Planning Time", "Triggers"
54
+ ]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ class CreateUsers < ActiveRecord::Migration::Current
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :name
5
+ t.string :email
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require "active_record/railtie"
5
+ require 'bundler/setup'
6
+ require_relative '../lib/activerecord-analyze'
7
+
8
+ ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:5432/activerecord-analyze-test"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-analyze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-12 00:00:00.000000000 Z
11
+ date: 2021-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pg
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
27
69
  description: ' Gem adds an "analyze" method to all Active Record query objects. Compatible
28
70
  with PostgreSQL database. '
29
71
  email:
@@ -32,15 +74,21 @@ executables: []
32
74
  extensions: []
33
75
  extra_rdoc_files: []
34
76
  files:
77
+ - ".circleci/config.yml"
35
78
  - ".gitignore"
36
79
  - Gemfile
37
80
  - LICENSE.txt
38
81
  - README.md
39
82
  - Rakefile
40
83
  - activerecord-analyze.gemspec
84
+ - docker-compose.yml.sample
41
85
  - lib/activerecord-analyze.rb
42
86
  - lib/activerecord-analyze/main.rb
43
87
  - lib/activerecord-analyze/version.rb
88
+ - query-plan.png
89
+ - spec/main_spec.rb
90
+ - spec/migrations/create_users_migration.rb
91
+ - spec/spec_helper.rb
44
92
  homepage: http://github.com/pawurb/activerecord-analyze
45
93
  licenses:
46
94
  - MIT
@@ -64,4 +112,7 @@ rubygems_version: 3.1.4
64
112
  signing_key:
65
113
  specification_version: 4
66
114
  summary: Add EXPLAIN ANALYZE to Active Record query objects
67
- test_files: []
115
+ test_files:
116
+ - spec/main_spec.rb
117
+ - spec/migrations/create_users_migration.rb
118
+ - spec/spec_helper.rb