pghero 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pghero might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CONTRIBUTING.md +9 -7
  4. data/README.md +6 -4
  5. data/app/controllers/pg_hero/home_controller.rb +10 -2
  6. data/app/views/pg_hero/home/explain.html.erb +1 -1
  7. data/app/views/pg_hero/home/index.html.erb +16 -6
  8. data/lib/generators/pghero/templates/{config.yml → config.yml.tt} +3 -3
  9. data/lib/generators/pghero/templates/{query_stats.rb → query_stats.rb.tt} +0 -0
  10. data/lib/generators/pghero/templates/{space_stats.rb → space_stats.rb.tt} +0 -0
  11. data/lib/pghero.rb +2 -2
  12. data/lib/pghero/methods/connections.rb +16 -0
  13. data/lib/pghero/methods/indexes.rb +32 -26
  14. data/lib/pghero/methods/maintenance.rb +1 -1
  15. data/lib/pghero/methods/query_stats.rb +2 -2
  16. data/lib/pghero/methods/replication.rb +1 -1
  17. data/lib/pghero/methods/sequences.rb +6 -2
  18. data/lib/pghero/methods/suggested_indexes.rb +1 -5
  19. data/lib/pghero/version.rb +1 -1
  20. metadata +9 -45
  21. data/.gitattributes +0 -1
  22. data/.github/ISSUE_TEMPLATE.md +0 -7
  23. data/.gitignore +0 -22
  24. data/.travis.yml +0 -16
  25. data/Gemfile +0 -6
  26. data/Rakefile +0 -8
  27. data/guides/Contributing.md +0 -16
  28. data/guides/Docker.md +0 -89
  29. data/guides/Heroku.md +0 -102
  30. data/guides/Linux.md +0 -296
  31. data/guides/Permissions.md +0 -57
  32. data/guides/Query-Stats.md +0 -60
  33. data/guides/Rails.md +0 -339
  34. data/guides/Suggested-Indexes.md +0 -19
  35. data/pghero.gemspec +0 -35
  36. data/test/basic_test.rb +0 -38
  37. data/test/best_index_test.rb +0 -180
  38. data/test/gemfiles/activerecord41.gemfile +0 -6
  39. data/test/gemfiles/activerecord42.gemfile +0 -6
  40. data/test/suggested_indexes_test.rb +0 -18
  41. data/test/test_helper.rb +0 -66
@@ -1,19 +0,0 @@
1
- # How PgHero Suggests Indexes
2
-
3
- 1. Get the most time-consuming queries from [pg_stat_statements](https://www.postgresql.org/docs/current/static/pgstatstatements.html).
4
-
5
- 2. Parse queries with [pg_query](https://github.com/lfittl/pg_query). Look for a single table with a `WHERE` clause that consists of only `=`, `IN`, `IS NULL` or `IS NOT NULL` and/or an `ORDER BY` clause.
6
-
7
- 3. Use the [pg_stats](https://www.postgresql.org/docs/current/static/view-pg-stats.html) view to get estimates about distinct rows and percent of `NULL` values for each column.
8
-
9
- 4. For each column in the `WHERE` clause, sort by the highest [cardinality](https://en.wikipedia.org/wiki/Cardinality_(SQL_statements)) (most unique values). This allows the database to narrow its search the fastest. Perform [row estimation](https://www.postgresql.org/docs/current/static/row-estimation-examples.html) to get the expected number of rows as we add columns to the index.
10
-
11
- 5. Continue this process with columns in the `ORDER BY` clause.
12
-
13
- 6. To make sure we don’t add useless columns, stop once we narrow it down to 50 rows in steps 5 or 6. Also, recheck the last columns to make sure they add value.
14
-
15
- 7. Profit :moneybag:
16
-
17
- ## TODO
18
-
19
- - examples
@@ -1,35 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "pghero/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "pghero"
8
- spec.version = PgHero::VERSION
9
- spec.authors = ["Andrew Kane"]
10
- spec.email = ["andrew@chartkick.com"]
11
- spec.summary = "A performance dashboard for Postgres"
12
- spec.homepage = "https://github.com/ankane/pghero"
13
- spec.license = "MIT"
14
-
15
- spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features|guides)/})
18
- spec.require_paths = ["lib"]
19
-
20
- spec.required_ruby_version = ">= 2.2.0"
21
-
22
- spec.add_dependency "activerecord"
23
-
24
- spec.add_development_dependency "activerecord-import"
25
- spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "minitest"
27
- spec.add_development_dependency "rake"
28
-
29
- if RUBY_PLATFORM == "java"
30
- spec.add_development_dependency "activerecord-jdbcpostgresql-adapter"
31
- else
32
- spec.add_development_dependency "pg", "< 1.0.0"
33
- spec.add_development_dependency "pg_query"
34
- end
35
- end
@@ -1,38 +0,0 @@
1
- require_relative "test_helper"
2
-
3
- class BasicTest < Minitest::Test
4
- def setup
5
- City.delete_all
6
- end
7
-
8
- def test_explain
9
- City.create!
10
- PgHero.explain("ANALYZE DELETE FROM cities")
11
- assert_equal 1, City.count
12
- end
13
-
14
- def test_explain_multiple_statements
15
- City.create!
16
- assert_raises(ActiveRecord::StatementInvalid) { PgHero.explain("ANALYZE DELETE FROM cities; DELETE FROM cities; COMMIT") }
17
- end
18
-
19
- def test_analyze_tables
20
- assert PgHero.analyze_tables
21
- end
22
-
23
- def test_relation_sizes
24
- assert PgHero.relation_sizes
25
- end
26
-
27
- def test_transaction_id_danger
28
- assert PgHero.transaction_id_danger(threshold: 10000000000)
29
- end
30
-
31
- def test_autovacuum_danger
32
- assert PgHero.autovacuum_danger
33
- end
34
-
35
- def test_databases
36
- assert PgHero.databases[:primary].running_queries
37
- end
38
- end
@@ -1,180 +0,0 @@
1
- require_relative "test_helper"
2
-
3
- class BestIndexTest < Minitest::Test
4
- def test_where
5
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = 1"
6
- end
7
-
8
- def test_all_values
9
- index = PgHero.best_index("SELECT * FROM users WHERE login_attempts = 1 ORDER BY created_at")
10
- expected = {
11
- found: true,
12
- structure: {table: "users", where: [{column: "login_attempts", op: "="}], sort: [{column: "created_at", direction: "asc"}]},
13
- index: {table: "users", columns: ["login_attempts", "created_at"]},
14
- rows: 5000,
15
- row_estimates: {"login_attempts (=)" => 167, "created_at (sort)" => 1},
16
- row_progression: [5000, 167, 0]
17
- }
18
- assert_equal expected, index
19
- end
20
-
21
- def test_where_multiple_columns
22
- assert_best_index ({table: "users", columns: ["city_id", "login_attempts"]}), "SELECT * FROM users WHERE city_id = 1 and login_attempts = 2"
23
- end
24
-
25
- def test_where_unique
26
- assert_best_index ({table: "users", columns: ["email"]}), "SELECT * FROM users WHERE city_id = 1 AND email = 'person2@example.org'"
27
- end
28
-
29
- def test_order
30
- assert_best_index ({table: "users", columns: ["created_at"]}), "SELECT * FROM users ORDER BY created_at"
31
- end
32
-
33
- def test_order_multiple
34
- assert_best_index ({table: "users", columns: ["login_attempts", "created_at"]}), "SELECT * FROM users ORDER BY login_attempts, created_at"
35
- end
36
-
37
- def test_order_multiple_direction
38
- assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users ORDER BY login_attempts DESC, created_at"
39
- end
40
-
41
- def test_order_multiple_unique
42
- assert_best_index ({table: "users", columns: ["id"]}), "SELECT * FROM users ORDER BY id, created_at"
43
- end
44
-
45
- def test_where_unique_order
46
- assert_best_index ({table: "users", columns: ["email"]}), "SELECT * FROM users WHERE email = 'person2@example.org' ORDER BY created_at"
47
- end
48
-
49
- def test_where_order
50
- assert_best_index ({table: "users", columns: ["login_attempts", "created_at"]}), "SELECT * FROM users WHERE login_attempts = 1 ORDER BY created_at"
51
- end
52
-
53
- def test_where_order_unknown
54
- assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users WHERE login_attempts = 1 ORDER BY NOW()"
55
- end
56
-
57
- def test_where_in
58
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id IN (1, 2)"
59
- end
60
-
61
- def test_like
62
- assert_best_index ({table: "users", columns: ["email gist_trgm_ops"], using: "gist"}), "SELECT * FROM users WHERE email LIKE ?"
63
- end
64
-
65
- def test_like_where
66
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = ? AND email LIKE ?"
67
- end
68
-
69
- def test_like_where2
70
- assert_best_index ({table: "users", columns: ["email gist_trgm_ops"], using: "gist"}), "SELECT * FROM users WHERE email LIKE ? AND active = ?"
71
- end
72
-
73
- def test_ilike
74
- assert_best_index ({table: "users", columns: ["email gist_trgm_ops"], using: "gist"}), "SELECT * FROM users WHERE email ILIKE ?"
75
- end
76
-
77
- def test_not_equals
78
- assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users WHERE city_id != ? and login_attempts = 2"
79
- end
80
-
81
- def test_not_in
82
- assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users WHERE city_id NOT IN (?) and login_attempts = 2"
83
- end
84
-
85
- def test_between
86
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id BETWEEN 1 AND 2"
87
- end
88
-
89
- def test_multiple_range
90
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id > ? and login_attempts > ?"
91
- end
92
-
93
- def test_where_prepared
94
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = $1"
95
- end
96
-
97
- def test_where_normalized
98
- assert_best_index ({table: "users", columns: ["city_id"]}), "SELECT * FROM users WHERE city_id = ?"
99
- end
100
-
101
- def test_is_null
102
- assert_best_index ({table: "users", columns: ["zip_code"]}), "SELECT * FROM users WHERE zip_code IS NULL"
103
- end
104
-
105
- def test_is_null_equal
106
- assert_best_index ({table: "users", columns: ["zip_code", "login_attempts"]}), "SELECT * FROM users WHERE zip_code IS NULL AND login_attempts = ?"
107
- end
108
-
109
- def test_is_not_null
110
- assert_best_index ({table: "users", columns: ["login_attempts"]}), "SELECT * FROM users WHERE zip_code IS NOT NULL AND login_attempts = ?"
111
- end
112
-
113
- def test_update
114
- assert_best_index ({table: "users", columns: ["city_id"]}), "UPDATE users SET email = 'test' WHERE city_id = 1"
115
- end
116
-
117
- def test_delete
118
- assert_best_index ({table: "users", columns: ["city_id"]}), "DELETE FROM users WHERE city_id = 1"
119
- end
120
-
121
- def test_parse_error
122
- assert_no_index "Parse error", "SELECT *123'"
123
- end
124
-
125
- def test_stats_not_found
126
- assert_no_index "Stats not found", "SELECT * FROM non_existent_table WHERE id = 1"
127
- end
128
-
129
- def test_unknown_structure
130
- assert_no_index "Unknown structure", "SELECT NOW()"
131
- end
132
-
133
- def test_where_or
134
- assert_no_index "Unknown structure", "SELECT FROM users WHERE login_attempts = 0 OR login_attempts = 1"
135
- end
136
-
137
- def test_where_nested_or
138
- assert_no_index "Unknown structure", "SELECT FROM users WHERE city_id = 1 AND (login_attempts = 0 OR login_attempts = 1)"
139
- end
140
-
141
-
142
- def test_multiple_tables
143
- assert_no_index "JOIN not supported yet", "SELECT * FROM users INNER JOIN cities ON cities.id = users.city_id"
144
- end
145
-
146
- def test_no_columns
147
- assert_no_index "No columns to index", "SELECT * FROM users"
148
- end
149
-
150
- def test_small_table
151
- assert_no_index "No index needed if less than 500 rows", "SELECT * FROM states WHERE name = 'State 1'"
152
- end
153
-
154
- def test_system_table
155
- assert_no_index "System table", "SELECT COUNT(*) AS count FROM pg_extension WHERE extname = ?"
156
- end
157
-
158
- def test_insert
159
- assert_no_index "INSERT statement", "INSERT INTO users (login_attempts) VALUES (1)"
160
- end
161
-
162
- def test_set
163
- assert_no_index "SET statement", "set client_encoding to 'UTF8'"
164
- end
165
-
166
- protected
167
-
168
- def assert_best_index(expected, statement)
169
- index = PgHero.best_index(statement)
170
- assert_nil index[:explanation]
171
- assert index[:found]
172
- assert_equal expected, index[:index]
173
- end
174
-
175
- def assert_no_index(explanation, statement)
176
- index = PgHero.best_index(statement)
177
- assert !index[:found]
178
- assert_equal explanation, index[:explanation]
179
- end
180
- end
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in pghero.gemspec
4
- gemspec path: "../.."
5
-
6
- gem "activerecord", "~> 4.1.0"
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in pghero.gemspec
4
- gemspec path: "../.."
5
-
6
- gem "activerecord", "~> 4.2.0"
@@ -1,18 +0,0 @@
1
- require_relative "test_helper"
2
-
3
- class SuggestedIndexesTest < Minitest::Test
4
- def setup
5
- skip if ENV["TRAVIS_CI"]
6
- PgHero.reset_query_stats
7
- end
8
-
9
- def test_basic
10
- User.where(email: "person1@example.org").first
11
- assert_equal [{table: "users", columns: ["email"]}], PgHero.suggested_indexes.map { |q| q.except(:queries, :details) }
12
- end
13
-
14
- def test_existing_index
15
- User.where("updated_at > ?", Time.now).to_a
16
- assert_equal [], PgHero.suggested_indexes.map { |q| q.except(:queries, :details) }
17
- end
18
- end
@@ -1,66 +0,0 @@
1
- require "bundler/setup"
2
- Bundler.require(:default)
3
- require "minitest/autorun"
4
- require "minitest/pride"
5
- require "pg_query"
6
- require "activerecord-import"
7
-
8
- # for Minitest < 5
9
- Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
10
-
11
- ActiveRecord::Base.establish_connection adapter: "postgresql", database: "pghero_test"
12
-
13
- ActiveRecord::Migration.enable_extension "pg_stat_statements"
14
-
15
- ActiveRecord::Migration.create_table :cities, force: true do |t|
16
- t.string :name
17
- end
18
-
19
- ActiveRecord::Migration.create_table :states, force: true do |t|
20
- t.string :name
21
- end
22
-
23
- ActiveRecord::Migration.create_table :users, force: true do |t|
24
- t.integer :city_id
25
- t.integer :login_attempts
26
- t.string :email
27
- t.string :zip_code
28
- t.boolean :active
29
- t.timestamp :created_at
30
- t.timestamp :updated_at
31
- end
32
- ActiveRecord::Migration.add_index :users, :updated_at
33
-
34
- class City < ActiveRecord::Base
35
- end
36
-
37
- class State < ActiveRecord::Base
38
- end
39
-
40
- class User < ActiveRecord::Base
41
- end
42
-
43
- states =
44
- 50.times.map do |i|
45
- {
46
- name: "State #{i}"
47
- }
48
- end
49
- State.import states, validate: false
50
- ActiveRecord::Base.connection.execute("ANALYZE states")
51
-
52
- users =
53
- 5000.times.map do |i|
54
- city_id = i % 100
55
- {
56
- city_id: city_id,
57
- email: "person#{i}@example.org",
58
- login_attempts: rand(30),
59
- zip_code: i % 40 == 0 ? nil : "12345",
60
- active: true,
61
- created_at: Time.now - rand(50).days,
62
- updated_at: Time.now - rand(50).days
63
- }
64
- end
65
- User.import users, validate: false
66
- ActiveRecord::Base.connection.execute("ANALYZE users")