Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix performance issues with JRuby #647

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 1 addition & 1 deletion lib/ddtrace/contrib/active_record/events/sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def span_name
end

def process(span, event, _id, payload)
config = Utils.connection_config(payload[:connection_id])
config = Utils.connection_config(payload[:connection], payload[:connection_id])
settings = Datadog.configuration[:active_record, config]
adapter_name = Datadog::Utils::Database.normalize_vendor(config[:adapter])
service_name = !settings.nil? ? settings.service_name : configuration[:service_name]
Expand Down
45 changes: 22 additions & 23 deletions lib/ddtrace/contrib/active_record/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Contrib
module ActiveRecord
# Common utilities for Rails
module Utils
EMPTY_CONFIG = {}.freeze

def self.adapter_name
Datadog::Utils::Database.normalize_vendor(connection_config[:adapter])
end
Expand All @@ -19,28 +21,25 @@ def self.adapter_port
connection_config[:port]
end

def self.connection_config(object_id = nil)
object_id.nil? ? default_connection_config : connection_config_by_id(object_id)
end

# Attempt to retrieve the connection from an object ID.
def self.connection_by_id(object_id)
return nil if object_id.nil?
ObjectSpace._id2ref(object_id)
rescue StandardError
nil
end

# Attempt to retrieve the connection config from an object ID.
# Typical of ActiveSupport::Notifications `sql.active_record`
def self.connection_config_by_id(object_id)
connection = connection_by_id(object_id)
return {} if connection.nil?

if connection.instance_variable_defined?(:@config)
connection.instance_variable_get(:@config)
# In newer Rails versions, the `payload` contains both the `connection` and its `object_id` named `connection_id`.
#
# So, if rails is recent we'll have a direct access to the connection.
# Else, we'll find it thanks to the passed `connection_id`.
#
# See this PR for more details: https://github.com/rails/rails/pull/34602
#
def self.connection_config(connection = nil, connection_id = nil)
if connection.nil? && connection_id.nil?
default_connection_config
else
{}
conn =
connection || ::ActiveRecord::Base
.connection_handler
.connection_pool_list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this iterate over all ActiveRecord connections? Regardless if they were made with ActiveRecord::Base.establish_connection, or from a class that inherits from ActiveRecord::Base? Just want to make sure connections configured in a legitimate way for Rails don't slip through the cracks.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep all there is connected (in whatever way) with AR

Copy link
Contributor Author

@guizmaii guizmaii Dec 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, but I'm not sure, that it's all connections in the current process, because here's how connection_pool_list is defined (at least in Rails 4.2):

      def connection_pool_list
        owner_to_pool.values.compact
      end

and here's how owner_to_pool is defined:

      def owner_to_pool
        @owner_to_pool[Process.pid]
      end

If I understand well (and I'm absolutely not sure of that) this line of code: @owner_to_pool[Process.pid], it says that it takes the pools for the current process only.

Which means that for a multi-processes runtime (like Puma with workers or Unicorn) the number of connections should be fairly low (at least, it's my assumption. I know that AR is by default configured to use 5 connections, IIRC)
And for a multi-threaded runtime (like Puma with JRuby) it means all the connections but it seems that in this case AR uses only one connection per thread (maybe per db) which is again fairly small (I think 😕).

WDYT ?

Copy link

@kares kares Dec 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you got that right: one connection per thread
per Model.establish_connection (class_to_pool) - which usually means a different DB connection.

Process.pid is really only due MRI - not much forking under JRuby.
under a multi-process (MRI) connections are low (usually ~1 since one process == one request), under a GIL-less runtime it can go as high as the server handles concurrent requests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kares Clever that since its segmented by process/thread that it makes the pool to be searched much smaller. I think this is only problematic if the AR event is generated from one thread, and handled in another, which would cause it to search the wrong pool. I can't really think of how that would happen here.

@guizmaii Okay, so I guess my last concern is, does this work with Rails 3.0 - latest? We have broken tests in this PR so its hard to tell. If we can get the build passing, it will run the Rails tests for these different versions, and that would be good enough to tell me whether this condition has been satisfied.

.flat_map(&:connections)
.find { |c| c.object_id == connection_id }

conn.respond_to?(:config) ? conn.config : EMPTY_CONFIG
end
end

Expand All @@ -53,9 +52,9 @@ def self.default_connection_config
end

connection_pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(current_connection_name)
connection_pool.nil? ? {} : (@default_connection_config = connection_pool.spec.config)
connection_pool.nil? ? EMPTY_CONFIG : (@default_connection_config = connection_pool.spec.config)
rescue StandardError
{}
EMPTY_CONFIG
end
end
end
Expand Down