1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Allocation on demand in transactions

Currently 1,000 transactions creates 10,000 objects regardless whether
it is necessary or not.

This makes allocation on demand in transactions, now 1,000 transactions
creates required 5,000 objects only by default.

```ruby
ObjectSpace::AllocationTracer.setup(%i{path line type})

pp ObjectSpace::AllocationTracer.trace {
  1_000.times { User.create }
}.select { |k, _| k[0].end_with?("transaction.rb") }
```

Before (95d038f):

```
{["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  209,
  :T_HASH]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  210,
  :T_OBJECT]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  210,
  :T_HASH]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  80,
  :T_OBJECT]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  8,
  :T_ARRAY]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  81,
  :T_ARRAY]=>[1000, 0, 715, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  289,
  :T_STRING]=>[1000, 0, 714, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  116,
  :T_ARRAY]=>[1000, 0, 714, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  120,
  :T_ARRAY]=>[1000, 0, 714, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  121,
  :T_HASH]=>[1000, 0, 714, 0, 1, 0]}
```

After (this change):

```
{["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  213,
  :T_HASH]=>[1000, 0, 739, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  214,
  :T_OBJECT]=>[1000, 0, 739, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  214,
  :T_HASH]=>[1000, 0, 739, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  81,
  :T_OBJECT]=>[1000, 0, 739, 0, 1, 0],
 ["~/rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb",
  304,
  :T_STRING]=>[1000, 0, 738, 0, 1, 0]}
```
This commit is contained in:
Ryuta Kamizono 2019-06-14 06:44:12 +09:00
parent 95d038f020
commit 05c718a109

View file

@ -5,10 +5,11 @@ module ActiveRecord
class TransactionState
def initialize(state = nil)
@state = state
@children = []
@children = nil
end
def add_child(state)
@children ||= []
@children << state
end
@ -41,12 +42,12 @@ module ActiveRecord
end
def rollback!
@children.each { |c| c.rollback! }
@children&.each { |c| c.rollback! }
@state = :rolledback
end
def full_rollback!
@children.each { |c| c.rollback! }
@children&.each { |c| c.rollback! }
@state = :fully_rolledback
end
@ -75,18 +76,19 @@ module ActiveRecord
class Transaction #:nodoc:
attr_reader :connection, :state, :records, :savepoint_name, :isolation_level
def initialize(connection, options, run_commit_callbacks: false)
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
@records = []
@isolation_level = options[:isolation]
@records = nil
@isolation_level = isolation
@materialized = false
@joinable = options.fetch(:joinable, true)
@joinable = joinable
@run_commit_callbacks = run_commit_callbacks
end
def add_record(record)
records << record
@records ||= []
@records << record
end
def materialize!
@ -98,6 +100,7 @@ module ActiveRecord
end
def rollback_records
return unless records
ite = records.uniq(&:object_id)
already_run_callbacks = {}
while record = ite.shift
@ -107,16 +110,17 @@ module ActiveRecord
record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
end
ensure
ite.each do |i|
ite&.each do |i|
i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
end
end
def before_commit_records
records.uniq.each(&:before_committed!) if @run_commit_callbacks
records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
end
def commit_records
return unless records
ite = records.uniq(&:object_id)
already_run_callbacks = {}
while record = ite.shift
@ -131,7 +135,7 @@ module ActiveRecord
end
end
ensure
ite.each { |i| i.committed!(should_run_callbacks: false) }
ite&.each { |i| i.committed!(should_run_callbacks: false) }
end
def full_rollback?; true; end
@ -141,8 +145,8 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
def initialize(connection, savepoint_name, parent_transaction, *args)
super(connection, *args)
def initialize(connection, savepoint_name, parent_transaction, **options)
super(connection, options)
parent_transaction.state.add_child(@state)
@ -202,18 +206,29 @@ module ActiveRecord
@lazy_transactions_enabled = true
end
def begin_transaction(options = {})
def begin_transaction(isolation: nil, joinable: true, _lazy: true)
@connection.lock.synchronize do
run_commit_callbacks = !current_transaction.joinable?
transaction =
if @stack.empty?
RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
RealTransaction.new(
@connection,
isolation: isolation,
joinable: joinable,
run_commit_callbacks: run_commit_callbacks
)
else
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options,
run_commit_callbacks: run_commit_callbacks)
SavepointTransaction.new(
@connection,
"active_record_#{@stack.size}",
@stack.last,
isolation: isolation,
joinable: joinable,
run_commit_callbacks: run_commit_callbacks
)
end
if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && options[:_lazy] != false
if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
@has_unmaterialized_transactions = true
else
transaction.materialize!
@ -274,9 +289,9 @@ module ActiveRecord
end
end
def within_new_transaction(options = {})
def within_new_transaction(isolation: nil, joinable: true)
@connection.lock.synchronize do
transaction = begin_transaction options
transaction = begin_transaction(isolation: isolation, joinable: joinable)
yield
rescue Exception => error
if transaction