-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path01_n_plus_one.rb
106 lines (89 loc) · 3.2 KB
/
01_n_plus_one.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
require_relative 'setup'
# Set up fresh database and generate sample data
setup_database
generate_sample_data(users: 1000, posts_per_user: 10, comments_per_post: 5)
puts "\n=== N+1 Query Examples ===\n"
def measure_and_report(title)
puts "\n#{title}:"
queries_count = 0
time = Benchmark.realtime do
ActiveSupport::Notifications.subscribed(-> (*) { queries_count += 1 }, "sql.active_record") do
yield
end
end
puts "Time taken: #{time.round(2)} seconds"
puts "Queries executed: #{queries_count}"
end
# 1. Basic N+1 Problem
puts "\n1. Basic N+1 Problem\n"
puts "\nBad approach (N+1 queries):"
measure_and_report("Bad approach (N+1 queries)") do
User.limit(50).each do |user|
puts "User #{user.name} has #{user.posts.count} posts"
end
end
puts "\nGood approach (with includes):"
measure_and_report("Good approach (with includes)") do
User.includes(:posts).limit(50).each do |user|
puts "User #{user.name} has #{user.posts.length} posts"
end
end
# 2. Nested N+1 Problem
puts "\n2. Nested N+1 Problem\n"
puts "\nBad approach (nested N+1):"
measure_and_report("Bad approach (nested N+1)") do
Post.limit(150).each do |post|
puts "Post '#{post.title}' has #{post.comments.count} comments"
end
end
puts "\nGood approach (nested includes):"
measure_and_report("Good approach (nested includes)") do
Post.includes(:comments).limit(150).each do |post|
puts "Post '#{post.title}' has #{post.comments.length} comments"
end
end
# 3. Counter Cache Benefits
puts "\n3. Counter Cache Benefits\n"
puts "\nWithout counter cache (counting posts):"
measure_and_report("Without counter cache") do
User.limit(50).each do |user|
puts "User #{user.name} has #{user.posts.count} posts"
end
end
puts "\nWith counter cache (using cached count):"
measure_and_report("With counter cache") do
User.limit(50).each do |user|
puts "User #{user.name} has #{user.posts_count} posts"
end
end
# 4. Different Eager Loading Strategies
puts "\n4. Different Eager Loading Strategies\n"
puts "\nUsing preload (separate queries):"
measure_and_report("Using preload") do
users = User.preload(:posts).limit(50)
users.each do |user|
puts "User #{user.name} has posts: #{user.posts.map(&:title).join(', ')}"
end
end
puts "\nUsing eager_load (LEFT OUTER JOIN):"
measure_and_report("Using eager_load") do
users = User.eager_load(:posts).limit(50)
users.each do |user|
puts "User #{user.name} has posts: #{user.posts.map(&:title).join(', ')}"
end
end
puts "\nUsing includes (lets ActiveRecord decide):"
measure_and_report("Using includes") do
users = User.includes(:posts).limit(50)
users.each do |user|
puts "User #{user.name} has posts: #{user.posts.map(&:title).join(', ')}"
end
end
puts "\nKey Findings:"
puts "1. N+1 queries can significantly impact performance with larger datasets"
puts "2. Nested N+1 problems compound the performance impact"
puts "3. Counter caches are effective for frequently accessed counts"
puts "4. Different eager loading strategies have varying performance characteristics:"
puts " - preload: Separate queries, good for simple associations"
puts " - eager_load: LEFT OUTER JOIN, good for filtering"
puts " - includes: Automatically chooses between preload and eager_load"