Skip to content

Commit

Permalink
Allocation on demand in transactions
Browse files Browse the repository at this point in the history
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]}
```
  • Loading branch information
kamipo committed Jun 13, 2019
1 parent 95d038f commit 05c718a
Showing 1 changed file with 35 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ module ConnectionAdapters
class TransactionState
def initialize(state = nil)
@state = state
@children = []
@children = nil
end

def add_child(state)
@children ||= []
@children << state
end

Expand Down Expand Up @@ -41,12 +42,12 @@ def completed?
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

Expand Down Expand Up @@ -75,18 +76,19 @@ def add_record(record); end
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!
Expand All @@ -98,6 +100,7 @@ def materialized?
end

def rollback_records
return unless records
ite = records.uniq(&:object_id)
already_run_callbacks = {}
while record = ite.shift
Expand All @@ -107,16 +110,17 @@ def rollback_records
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
Expand All @@ -131,7 +135,7 @@ def commit_records
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
Expand All @@ -141,8 +145,8 @@ def open?; !closed?; end
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)

Expand Down Expand Up @@ -202,18 +206,29 @@ def initialize(connection)
@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!
Expand Down Expand Up @@ -274,9 +289,9 @@ def rollback_transaction(transaction = nil)
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
Expand Down

0 comments on commit 05c718a

Please sign in to comment.