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:
parent
95d038f020
commit
05c718a109
1 changed files with 35 additions and 20 deletions
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue