tobias 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/lib/tobias/cli.rb +3 -19
- data/lib/tobias/container.rb +1 -1
- data/lib/tobias/evaluations/base.rb +32 -0
- data/lib/tobias/evaluations/work_mem.rb +63 -0
- data/lib/tobias/evaluations.rb +11 -0
- data/lib/tobias/version.rb +1 -2
- data/lib/tobias/work_mem.rb +17 -1
- data/lib/tobias.rb +5 -2
- metadata +5 -3
- data/lib/tobias/evaluation.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b50326ed51ec5388f5f63528cbfe0cda2f27db4e505b0cdf1349f1cc9313311
|
4
|
+
data.tar.gz: e76ddcd08800005f89ddb772953df045ad623fcfc13e114090bdfcdd49c0dabb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456a7a52310fcac42a5755ad02aeb464bb429742888a195569fb5d5df7f33416d583cef442eca92b035af9807e7ec5d17c626b0eda20b9d43998fc4c99ccb6f3
|
7
|
+
data.tar.gz: e8c5d112db7cd2e87f37455d90b25a6dfe53eba27e60a492c4fbc4798a941bdc9b69532b813eb39f4c69b68c34812e09112ec19a80485f90e526869e2d0c1794
|
data/lib/tobias/cli.rb
CHANGED
@@ -10,7 +10,7 @@ module Tobias
|
|
10
10
|
|
11
11
|
desc "profile SCRIPT", "profile"
|
12
12
|
option :database_url, type: :string, required: true
|
13
|
-
option :iterations, type: :numeric, default:
|
13
|
+
option :iterations, type: :numeric, default: 10
|
14
14
|
option :debug, type: :boolean, default: false
|
15
15
|
def profile(script)
|
16
16
|
database = Sequel.connect(options[:database_url])
|
@@ -24,7 +24,6 @@ module Tobias
|
|
24
24
|
end
|
25
25
|
|
26
26
|
container = Container.new(code)
|
27
|
-
work_mems = WorkMem.valid_for(database)
|
28
27
|
results = {}
|
29
28
|
|
30
29
|
parsed = TTY::Markdown.parse(<<~MARKDOWN)
|
@@ -33,11 +32,7 @@ module Tobias
|
|
33
32
|
puts parsed
|
34
33
|
|
35
34
|
thinking_time = Benchmark.realtime do
|
36
|
-
|
37
|
-
work_mem = Evaluation.new(database, work_mems).run(options, &block)
|
38
|
-
|
39
|
-
results[name] = work_mem
|
40
|
-
end
|
35
|
+
results = Evaluations.run(database, container, options)
|
41
36
|
end
|
42
37
|
|
43
38
|
parsed = TTY::Markdown.parse(<<~MARKDOWN)
|
@@ -45,18 +40,7 @@ module Tobias
|
|
45
40
|
|
46
41
|
I thought about your queries for precisely #{thinking_time.round(2)} seconds and here is what I recommend:
|
47
42
|
|
48
|
-
|
49
|
-
|-------|-------------------|
|
50
|
-
#{results.map { |name, work_mem| "| #{name} | #{work_mem.to_sql} |" }.join("\n")}
|
51
|
-
|
52
|
-
Your application will need to run with at least #{results.values.max.to_sql} of work_mem.
|
53
|
-
|
54
|
-
To apply my recommendations, run the following SQL:
|
55
|
-
|
56
|
-
```sql
|
57
|
-
ALTER SYSTEM SET work_mem = '#{results.values.max.to_sql}';
|
58
|
-
SELECT pg_reload_conf();
|
59
|
-
```
|
43
|
+
#{results.join("\n")}
|
60
44
|
|
61
45
|
Regards,
|
62
46
|
~ Tobias
|
data/lib/tobias/container.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tobias
|
4
|
+
module Evaluations
|
5
|
+
class Base
|
6
|
+
attr_reader :database, :container
|
7
|
+
|
8
|
+
def initialize(database, container)
|
9
|
+
@database = database
|
10
|
+
@container = container
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(options, &block)
|
14
|
+
results = {}
|
15
|
+
|
16
|
+
container.queries.each do |name, query|
|
17
|
+
results.merge!(run_each(name, query, options))
|
18
|
+
end
|
19
|
+
|
20
|
+
to_markdown(results)
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_each(query, options)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_markdown(results)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tobias
|
4
|
+
module Evaluations
|
5
|
+
class WorkMem < Base
|
6
|
+
|
7
|
+
def work_mems
|
8
|
+
Tobias::WorkMem.valid_for(database)
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
"Optional work_mem settings"
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_each(name, query, options)
|
16
|
+
work_mems.each do |value|
|
17
|
+
database.transaction do
|
18
|
+
database.run("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
|
19
|
+
database.run("SET LOCAL work_mem = '#{value.to_sql}'")
|
20
|
+
database.select(Sequel.function(:pg_stat_reset)).first
|
21
|
+
|
22
|
+
query_result = database.instance_eval(&query)
|
23
|
+
options[:iterations].to_i.times do
|
24
|
+
database.run(query_result.sql)
|
25
|
+
end
|
26
|
+
|
27
|
+
stats = database[:pg_stat_database].where(datname: Sequel.function(:current_database)).first
|
28
|
+
|
29
|
+
if stats[:temp_files] == 0 && stats[:temp_bytes] == 0
|
30
|
+
return { name => value }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: Add a warning message or something.
|
36
|
+
return { name => nil }
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_markdown(results)
|
40
|
+
current_work_mem = Tobias::WorkMem.from_sql(database.fetch("SHOW work_mem").first[:work_mem])
|
41
|
+
|
42
|
+
<<~MARKDOWN
|
43
|
+
## #{description}
|
44
|
+
|
45
|
+
| Query | Required work_mem |
|
46
|
+
|-------|-------------------|
|
47
|
+
#{results.map { |name, work_mem| "| #{name} | #{work_mem.to_sql} |" }.join("\n")}
|
48
|
+
|
49
|
+
Your application will need to run with at least #{results.values.max.to_sql} of work_mem.
|
50
|
+
|
51
|
+
I see that your current work_mem setting is #{current_work_mem.to_sql}.
|
52
|
+
|
53
|
+
To apply my recommendations, run the following SQL:
|
54
|
+
|
55
|
+
```sql
|
56
|
+
ALTER SYSTEM SET work_mem = '#{results.values.max.to_sql}';
|
57
|
+
SELECT pg_reload_conf();
|
58
|
+
```
|
59
|
+
MARKDOWN
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/tobias/version.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Tobias
|
4
|
-
VERSION = "0.
|
5
|
-
|
4
|
+
VERSION = "0.3.0"
|
6
5
|
SUMMARY = "Tobias is a tool to help you find the optimal work_mem for your queries."
|
7
6
|
DESCRIPTION = "Tobias is a tool to help you find the optimal work_mem for your queries."
|
8
7
|
end
|
data/lib/tobias/work_mem.rb
CHANGED
@@ -4,6 +4,21 @@ module Tobias
|
|
4
4
|
class WorkMem
|
5
5
|
attr_reader :amount
|
6
6
|
|
7
|
+
def self.from_sql(sql)
|
8
|
+
case sql
|
9
|
+
when /^\d+B$/
|
10
|
+
new(sql.to_i)
|
11
|
+
when /^\d+kB$/
|
12
|
+
new(sql.to_i * 1024)
|
13
|
+
when /^\d+MB$/
|
14
|
+
new(sql.to_i * 1024 * 1024)
|
15
|
+
when /^\d+GB$/
|
16
|
+
new(sql.to_i * 1024 * 1024 * 1024)
|
17
|
+
else
|
18
|
+
raise "Invalid work_mem setting: #{sql}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
7
22
|
def initialize(amount)
|
8
23
|
@amount = amount
|
9
24
|
end
|
@@ -52,6 +67,7 @@ module Tobias
|
|
52
67
|
[
|
53
68
|
new(64.kilobytes),
|
54
69
|
new(128.kilobytes),
|
70
|
+
new(256.kilobytes),
|
55
71
|
new(512.kilobytes),
|
56
72
|
new(1.megabyte),
|
57
73
|
new(4.megabytes),
|
@@ -101,4 +117,4 @@ module Tobias
|
|
101
117
|
self.all.select { |work_mem| work_mem.amount < bytes_per_connection.to_i }
|
102
118
|
end
|
103
119
|
end
|
104
|
-
end
|
120
|
+
end
|
data/lib/tobias.rb
CHANGED
@@ -23,9 +23,12 @@ require "tty-markdown"
|
|
23
23
|
|
24
24
|
$LOAD_PATH.unshift File.dirname(__FILE__)
|
25
25
|
|
26
|
+
require "tobias/evaluations"
|
27
|
+
require "tobias/evaluations/base"
|
28
|
+
require "tobias/evaluations/work_mem"
|
29
|
+
|
26
30
|
module Tobias
|
27
31
|
autoload :CLI, "tobias/cli"
|
28
32
|
autoload :Container, "tobias/container"
|
29
|
-
autoload :Evaluation, "tobias/evaluation"
|
30
33
|
autoload :WorkMem, "tobias/work_mem"
|
31
|
-
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tobias
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Daniel
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-08-
|
10
|
+
date: 2025-08-04 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -240,7 +240,9 @@ files:
|
|
240
240
|
- lib/tobias.rb
|
241
241
|
- lib/tobias/cli.rb
|
242
242
|
- lib/tobias/container.rb
|
243
|
-
- lib/tobias/
|
243
|
+
- lib/tobias/evaluations.rb
|
244
|
+
- lib/tobias/evaluations/base.rb
|
245
|
+
- lib/tobias/evaluations/work_mem.rb
|
244
246
|
- lib/tobias/version.rb
|
245
247
|
- lib/tobias/work_mem.rb
|
246
248
|
homepage: https://github.com/binarycleric/tobias
|
data/lib/tobias/evaluation.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Tobias
|
4
|
-
class Evaluation
|
5
|
-
attr_reader :database
|
6
|
-
|
7
|
-
def initialize(database, work_mems)
|
8
|
-
@database = database
|
9
|
-
@work_mems = work_mems
|
10
|
-
end
|
11
|
-
|
12
|
-
def run(options, &block)
|
13
|
-
@work_mems.each do |value|
|
14
|
-
database.transaction do
|
15
|
-
database.run("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
|
16
|
-
database.run("SET LOCAL work_mem = '#{value.to_sql}'")
|
17
|
-
database.select(Sequel.function(:pg_stat_reset)).first
|
18
|
-
database.instance_eval(&block)
|
19
|
-
|
20
|
-
query = database.instance_eval(&block)
|
21
|
-
options[:iterations].to_i.times do
|
22
|
-
database.run(query.sql)
|
23
|
-
end
|
24
|
-
|
25
|
-
stats = database[:pg_stat_database].where(datname: Sequel.function(:current_database)).first
|
26
|
-
|
27
|
-
if stats[:temp_files] == 0 && stats[:temp_bytes] == 0
|
28
|
-
return value
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
raise "No work_mem found."
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|