diff options
author | Ryuta Kamizono <kamipo@gmail.com> | 2019-06-14 06:44:12 +0900 |
---|---|---|
committer | Ryuta Kamizono <kamipo@gmail.com> | 2019-06-14 07:16:53 +0900 |
commit | 05c718a109184440ecc3e254e13f1145c30b2a6c (patch) | |
tree | 79c45ceeee4e5d89a9a0f7d4df2418274ac93155 | |
parent | 95d038f020d91628c8b0633a28b654471cf72a9d (diff) | |
download | rails-05c718a109184440ecc3e254e13f1145c30b2a6c.tar.gz rails-05c718a109184440ecc3e254e13f1145c30b2a6c.tar.bz2 rails-05c718a109184440ecc3e254e13f1145c30b2a6c.zip |
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]}
```
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/transaction.rb | 55 |
1 files changed, 35 insertions, 20 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index cc67baf18a..53ce8df491 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -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 |