mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
Simplifications and performance improvements (#200)
* Add pool_concurrency_test manual spec Add MT connection count without Mutex * Drop checkout_some, simpler pool_prepared statement * Make pool statement a struct * Drop StringKeyCache mutex The StringKeyCache is now only used inside a connection. It's assumed that connections are not used concurrently with multiple queries. * Drop do_close in pool statements * Add specs and update comment * Fix typo
This commit is contained in:
parent
06df272740
commit
c106775ea9
10 changed files with 139 additions and 149 deletions
|
@ -38,7 +38,6 @@ module DB
|
|||
@connection_options : Connection::Options
|
||||
@pool : Pool(Connection)
|
||||
@setup_connection : Connection -> Nil
|
||||
@statements_cache = StringKeyCache(PoolPreparedStatement).new
|
||||
|
||||
# Initialize a database with the specified options and connection factory.
|
||||
# This covers more advanced use cases that might not be supported by an URI connection string such as tunneling connection.
|
||||
|
@ -81,9 +80,6 @@ module DB
|
|||
|
||||
# Closes all connection to the database.
|
||||
def close
|
||||
@statements_cache.each_value &.close
|
||||
@statements_cache.clear
|
||||
|
||||
@pool.close
|
||||
end
|
||||
|
||||
|
@ -99,15 +95,6 @@ module DB
|
|||
|
||||
# :nodoc:
|
||||
def fetch_or_build_prepared_statement(query) : PoolStatement
|
||||
if @connection_options.prepared_statements_cache
|
||||
@statements_cache.fetch(query) { build_prepared_statement(query) }
|
||||
else
|
||||
build_prepared_statement(query)
|
||||
end
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def build_prepared_statement(query) : PoolStatement
|
||||
PoolPreparedStatement.new(self, query)
|
||||
end
|
||||
|
||||
|
|
|
@ -158,27 +158,6 @@ module DB
|
|||
end
|
||||
end
|
||||
|
||||
# ```
|
||||
# selected, is_candidate = pool.checkout_some(candidates)
|
||||
# ```
|
||||
# `selected` be a resource from the `candidates` list and `is_candidate` == `true`
|
||||
# or `selected` will be a new resource and `is_candidate` == `false`
|
||||
def checkout_some(candidates : Enumerable(WeakRef(T))) : {T, Bool}
|
||||
sync do
|
||||
candidates.each do |ref|
|
||||
resource = ref.value
|
||||
if resource && is_available?(resource)
|
||||
@idle.delete resource
|
||||
resource.before_checkout
|
||||
return {resource, true}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resource = checkout
|
||||
{resource, candidates.any? { |ref| ref.value == resource }}
|
||||
end
|
||||
|
||||
def release(resource : T) : Nil
|
||||
idle_pushed = false
|
||||
|
||||
|
@ -227,8 +206,6 @@ module DB
|
|||
# if the connection is lost it will be closed by
|
||||
# the exception to release resources
|
||||
# we still need to remove it from the known pool.
|
||||
# Closed connection will be evicted from statement cache
|
||||
# in PoolPreparedStatement#clean_connections
|
||||
sync { delete(e.resource) }
|
||||
rescue e : PoolResourceRefused
|
||||
# a ConnectionRefused means a new connection
|
||||
|
|
|
@ -4,75 +4,20 @@ module DB
|
|||
# The execution of the statement is retried according to the pool configuration.
|
||||
#
|
||||
# See `PoolStatement`
|
||||
class PoolPreparedStatement < PoolStatement
|
||||
# connections where the statement was prepared
|
||||
@connections = Set(WeakRef(Connection)).new
|
||||
@mutex = Mutex.new
|
||||
|
||||
struct PoolPreparedStatement < PoolStatement
|
||||
def initialize(db : Database, query : String)
|
||||
super
|
||||
# Prepares a statement on some connection
|
||||
# otherwise the preparation is delayed until the first execution.
|
||||
# After the first initialization the connection must be released
|
||||
# it will be checked out when executing it.
|
||||
|
||||
# This only happens if the db is configured to use prepared statements cache.
|
||||
# Without that there is no reference to the already prepared statement we can
|
||||
# take advantage of.
|
||||
if db.prepared_statements_cache?
|
||||
statement_with_retry &.release_connection
|
||||
end
|
||||
|
||||
# TODO use a round-robin selection in the pool so multiple sequentially
|
||||
# initialized statements are assigned to different connections.
|
||||
end
|
||||
|
||||
protected def do_close
|
||||
@mutex.synchronize do
|
||||
# TODO close all statements on all connections.
|
||||
# currently statements are closed when the connection is closed.
|
||||
|
||||
# WHAT-IF the connection is busy? Should each statement be able to
|
||||
# deallocate itself when the connection is free.
|
||||
@connections.clear
|
||||
end
|
||||
end
|
||||
|
||||
# builds a statement over a real connection
|
||||
# the connection is registered in `@connections`
|
||||
private def build_statement : Statement
|
||||
clean_connections
|
||||
|
||||
conn, existing = @mutex.synchronize do
|
||||
@db.checkout_some(@connections)
|
||||
end
|
||||
|
||||
conn = @db.pool.checkout
|
||||
begin
|
||||
stmt = conn.prepared.build(@query)
|
||||
conn.prepared.build(@query)
|
||||
rescue ex
|
||||
conn.release
|
||||
raise ex
|
||||
end
|
||||
if !existing && @db.prepared_statements_cache?
|
||||
@mutex.synchronize do
|
||||
@connections << WeakRef.new(conn)
|
||||
end
|
||||
end
|
||||
stmt
|
||||
end
|
||||
|
||||
private def clean_connections
|
||||
return unless @db.prepared_statements_cache?
|
||||
|
||||
@mutex.synchronize do
|
||||
# remove disposed or closed connections
|
||||
@connections.each do |ref|
|
||||
conn = ref.value
|
||||
if !conn || conn.closed?
|
||||
@connections.delete ref
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ module DB
|
|||
# a statement from the DB needs to be able to represent a statement in any
|
||||
# of the connections of the pool. Otherwise the user will need to deal with
|
||||
# actual connections in some point.
|
||||
abstract class PoolStatement
|
||||
abstract struct PoolStatement
|
||||
include StatementMethods
|
||||
|
||||
def initialize(@db : Database, @query : String)
|
||||
|
|
|
@ -4,15 +4,11 @@ module DB
|
|||
# The execution of the statement is retried according to the pool configuration.
|
||||
#
|
||||
# See `PoolStatement`
|
||||
class PoolUnpreparedStatement < PoolStatement
|
||||
struct PoolUnpreparedStatement < PoolStatement
|
||||
def initialize(db : Database, query : String)
|
||||
super
|
||||
end
|
||||
|
||||
protected def do_close
|
||||
# unprepared statements do not need to be release in each connection
|
||||
end
|
||||
|
||||
# builds a statement over a real connection
|
||||
private def build_statement : Statement
|
||||
conn = @db.pool.checkout
|
||||
|
|
|
@ -2,11 +2,6 @@ module DB
|
|||
# Common interface for connection based statements
|
||||
# and for connection pool statements.
|
||||
module StatementMethods
|
||||
include Disposable
|
||||
|
||||
protected def do_close
|
||||
end
|
||||
|
||||
# See `QueryMethods#scalar`
|
||||
def scalar(*args_, args : Array? = nil)
|
||||
query(*args_, args: args) do |rs|
|
||||
|
@ -47,6 +42,10 @@ module DB
|
|||
# 6. `#do_close` is called to release the statement resources.
|
||||
abstract class Statement
|
||||
include StatementMethods
|
||||
include Disposable
|
||||
|
||||
protected def do_close
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
getter connection
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
module DB
|
||||
class StringKeyCache(T)
|
||||
@cache = {} of String => T
|
||||
@mutex = Mutex.new
|
||||
|
||||
def fetch(key : String) : T
|
||||
@mutex.synchronize do
|
||||
value = @cache.fetch(key, nil)
|
||||
value = @cache[key] = yield unless value
|
||||
value
|
||||
end
|
||||
value = @cache.fetch(key, nil)
|
||||
value = @cache[key] = yield unless value
|
||||
value
|
||||
end
|
||||
|
||||
def each_value
|
||||
@mutex.synchronize do
|
||||
@cache.each do |_, value|
|
||||
yield value
|
||||
end
|
||||
@cache.each do |_, value|
|
||||
yield value
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@mutex.synchronize do
|
||||
@cache.clear
|
||||
end
|
||||
@cache.clear
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue