2022-08-30 11:14:53 -05:00
|
|
|
require 'pg'
|
|
|
|
|
require 'toxiproxy'
|
|
|
|
|
|
|
|
|
|
class PgInstance
|
|
|
|
|
attr_reader :port
|
|
|
|
|
attr_reader :username
|
|
|
|
|
attr_reader :password
|
|
|
|
|
attr_reader :database_name
|
|
|
|
|
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
def self.mass_takedown(databases)
|
|
|
|
|
raise StandardError "block missing" unless block_given?
|
|
|
|
|
|
|
|
|
|
databases.each do |database|
|
|
|
|
|
database.toxiproxy.toxic(:limit_data, bytes: 1).toxics.each(&:save)
|
|
|
|
|
end
|
|
|
|
|
sleep 0.1
|
|
|
|
|
yield
|
|
|
|
|
ensure
|
|
|
|
|
databases.each do |database|
|
|
|
|
|
database.toxiproxy.toxics.each(&:destroy)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-08-30 11:14:53 -05:00
|
|
|
def initialize(port, username, password, database_name)
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
@original_port = port.to_i
|
2022-08-30 11:14:53 -05:00
|
|
|
@toxiproxy_port = 10000 + port.to_i
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
@port = @toxiproxy_port.to_i
|
2022-08-30 11:14:53 -05:00
|
|
|
|
|
|
|
|
@username = username
|
|
|
|
|
@password = password
|
|
|
|
|
@database_name = database_name
|
|
|
|
|
@toxiproxy_name = "database_#{@original_port}"
|
|
|
|
|
Toxiproxy.populate([{
|
|
|
|
|
name: @toxiproxy_name,
|
|
|
|
|
listen: "0.0.0.0:#{@toxiproxy_port}",
|
|
|
|
|
upstream: "localhost:#{@original_port}",
|
|
|
|
|
}])
|
|
|
|
|
|
|
|
|
|
# Toxiproxy server will outlive our PgInstance objects
|
|
|
|
|
# so we want to destroy our proxies before exiting
|
|
|
|
|
# Ruby finalizer is ideal for doing this
|
|
|
|
|
ObjectSpace.define_finalizer(@toxiproxy_name, proc { Toxiproxy[@toxiproxy_name].destroy })
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def with_connection
|
|
|
|
|
conn = PG.connect("postgres://#{@username}:#{@password}@localhost:#{port}/#{database_name}")
|
|
|
|
|
yield conn
|
|
|
|
|
ensure
|
|
|
|
|
conn&.close
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def reset
|
|
|
|
|
reset_toxics
|
|
|
|
|
reset_stats
|
2023-03-10 06:23:51 -06:00
|
|
|
drop_connections
|
|
|
|
|
sleep 0.1
|
2022-08-30 11:14:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def toxiproxy
|
|
|
|
|
Toxiproxy[@toxiproxy_name]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def take_down
|
|
|
|
|
if block_given?
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
Toxiproxy[@toxiproxy_name].toxic(:limit_data, bytes: 1).apply { yield }
|
2022-08-30 11:14:53 -05:00
|
|
|
else
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
Toxiproxy[@toxiproxy_name].toxic(:limit_data, bytes: 1).toxics.each(&:save)
|
2022-08-30 11:14:53 -05:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def add_latency(latency)
|
|
|
|
|
if block_given?
|
|
|
|
|
Toxiproxy[@toxiproxy_name].toxic(:latency, latency: latency).apply { yield }
|
|
|
|
|
else
|
|
|
|
|
Toxiproxy[@toxiproxy_name].toxic(:latency, latency: latency).toxics.each(&:save)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def delete_proxy
|
|
|
|
|
Toxiproxy[@toxiproxy_name].delete
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def reset_toxics
|
|
|
|
|
Toxiproxy[@toxiproxy_name].toxics.each(&:destroy)
|
2023-03-10 06:23:51 -06:00
|
|
|
sleep 0.1
|
2022-08-30 11:14:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def reset_stats
|
|
|
|
|
with_connection { |c| c.async_exec("SELECT pg_stat_statements_reset()") }
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-10 06:23:51 -06:00
|
|
|
def drop_connections
|
|
|
|
|
username = with_connection { |c| c.async_exec("SELECT current_user")[0]["current_user"] }
|
|
|
|
|
with_connection { |c| c.async_exec("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND usename='#{username}'") }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def count_connections
|
|
|
|
|
with_connection { |c| c.async_exec("SELECT COUNT(*) as count FROM pg_stat_activity")[0]["count"].to_i }
|
|
|
|
|
end
|
|
|
|
|
|
2022-08-30 11:14:53 -05:00
|
|
|
def count_query(query)
|
|
|
|
|
with_connection { |c| c.async_exec("SELECT SUM(calls) FROM pg_stat_statements WHERE query = '#{query}'")[0]["sum"].to_i }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def count_select_1_plus_2
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
with_connection { |c| c.async_exec("SELECT SUM(calls) FROM pg_stat_statements WHERE query LIKE '%SELECT $1 + $2%'")[0]["sum"].to_i }
|
2022-08-30 11:14:53 -05:00
|
|
|
end
|
|
|
|
|
end
|