Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,33 @@ Or install it yourself as:

$ gem install activerecord-debug_errors

## Prerequisites

### MySQL permissions

To collect complete MySQL diagnostics, the account used by Active Record needs the
global [`PROCESS`](https://dev.mysql.com/doc/refman/8.4/en/privileges-provided.html#priv_process)
privilege:

```sql
GRANT PROCESS ON *.* TO 'application_user'@'application_host';
```

The gem runs `SHOW ENGINE INNODB STATUS` for lock wait timeouts and deadlocks.
MySQL requires `PROCESS` to execute this statement. Without the privilege, the
gem logs the resulting permission error instead of the InnoDB diagnostic
section.

For lock wait timeouts, the gem also runs `SHOW FULL PROCESSLIST`. This
statement works without `PROCESS`, but only shows threads owned by the current
MySQL account. With `PROCESS`, it shows threads for all accounts, which may be
necessary to identify the session holding a lock.

Because `PROCESS` can expose statements executed by other users on the same
server, grant it only when the additional diagnostic visibility is acceptable.
The privilege is global and cannot be limited to the application's database.


## Usage

You only have to load the gem:
Expand Down Expand Up @@ -118,8 +145,6 @@ Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
*** WE ROLL BACK TRANSACTION (2)
```

Note that the user requires the PROCESS priviledge to collect the information.

### ActiveRecord::ConnectionTimeoutError

When `ActiveRecord::ConnectionTimeoutError` occurs, you can see the information of connection owners (threads):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ def cause_deadlock(role:)
context "when the user doesn't have the permission to execute 'SHOW ENGINE INNODB STATUS'" do
it "displays an error message" do
expect {
ActiveRecord::Base.connected_to(role: :reading) do
cause_deadlock(role: :reading)
end
cause_deadlock(role: :restricted)
}.to raise_error(ActiveRecord::Deadlocked)
expect(log.string).to include("Failed to execute")
end
Expand Down
10 changes: 5 additions & 5 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
},
}

user_for_replica = 'activerecord-debug_errors'
restricted_user = 'activerecord-debug_errors'
ActiveRecord::Base.configurations = {
default_env: {
primary: base_db_config,
primary_replica: base_db_config.merge(username: user_for_replica, replica: true),
restricted: base_db_config.merge(username: restricted_user),
}
}

Expand All @@ -48,18 +48,18 @@

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary_replica }
connects_to database: { writing: :primary, restricted: :restricted }
end
class User < ApplicationRecord; end

User.find_or_create_by!(name: 'foo')
User.find_or_create_by!(name: 'bar')

ActiveRecord::Base.connection.execute(<<~SQL)
CREATE USER IF NOT EXISTS '#{user_for_replica}'@'%' IDENTIFIED BY '#{ENV['MYSQL_PASSWORD']}'
CREATE USER IF NOT EXISTS '#{restricted_user}'@'%' IDENTIFIED BY '#{ENV['MYSQL_PASSWORD']}'
SQL
ActiveRecord::Base.connection.execute(<<~SQL)
GRANT SELECT, LOCK TABLES ON *.* To '#{user_for_replica}'@'%'
GRANT SELECT, LOCK TABLES ON *.* To '#{restricted_user}'@'%'
SQL
end
end
Loading