Skip to content

Commit 7695ffd

Browse files
authored
Merge pull request #194 from rafiss/bump-to-6.0.0-beta.5
Backport follower reads optimization and bump to 6.0.0 beta.5
2 parents 63380a3 + 45be8e3 commit 7695ffd

File tree

5 files changed

+128
-1
lines changed

5 files changed

+128
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 6.0.0-beta.5 - 2021-04-02
4+
5+
- Added a configuration option named `use_follower_reads_for_type_introspection`.
6+
If true, it improves the speed of type introspection by allowing potentially stale
7+
type metadata to be read. Defaults to false.
8+
39
## 6.0.0-beta.4 - 2021-03-06
410

511
- Improved connection performance by refactoring an introspection

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ development:
2222
user: <username>
2323
```
2424

25+
## Configuration
26+
27+
In addition to the standard adapter settings, CockroachDB also supports the following:
28+
29+
- `use_follower_reads_for_type_introspection`: Use follower reads on queries to the `pg_type` catalog when set to `true`. This helps to speed up initialization by reading historical data, but may not find recently created user-defined types.
30+
2531
## Working with Spatial Data
2632

2733
The adapter uses [RGeo](https://github.com/rgeo/rgeo) and [RGeo-ActiveRecord](https://github.com/rgeo/rgeo-activerecord) to represent geometric and geographic data as Ruby objects and easily interface them with the adapter. The following is a brief introduction to RGeo and tips to help setup your spatial application. More documentation about RGeo can be found in the [YARD Docs](https://rubydoc.info/github/rgeo/rgeo) and [wiki](https://github.com/rgeo/rgeo/wiki).

activerecord-cockroachdb-adapter.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
44

55
Gem::Specification.new do |spec|
66
spec.name = "activerecord-cockroachdb-adapter"
7-
spec.version = "6.0.0-beta.4"
7+
spec.version = "6.0.0-beta.5"
88
spec.licenses = ["Apache-2.0"]
99
spec.authors = ["Cockroach Labs"]
1010
spec.email = ["[email protected]"]

lib/active_record/connection_adapters/cockroachdb_adapter.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,100 @@ def extract_empty_array_from_default(default)
403403
return "{}"
404404
end
405405

406+
# override
407+
# This method loads info about data types from the database to
408+
# populate the TypeMap.
409+
#
410+
# Currently, querying from the pg_type catalog can be slow due to geo-partitioning
411+
# so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
412+
def load_additional_types(oids = nil)
413+
if @config[:use_follower_reads_for_type_introspection]
414+
initializer = OID::TypeMapInitializer.new(type_map)
415+
416+
query = <<~SQL
417+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
418+
FROM pg_type as t
419+
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
420+
SQL
421+
422+
if oids
423+
query += "WHERE t.oid IN (%s)" % oids.join(", ")
424+
else
425+
query += initializer.query_conditions_for_initial_load
426+
end
427+
428+
execute_and_clear(query, "SCHEMA", []) do |records|
429+
initializer.run(records)
430+
end
431+
else
432+
super
433+
end
434+
rescue ActiveRecord::StatementInvalid => e
435+
raise e unless e.cause.is_a? PG::InvalidCatalogName
436+
# use original if database is younger than 10s
437+
super
438+
end
439+
440+
# override
441+
# This method maps data types to their proper decoder.
442+
#
443+
# Currently, querying from the pg_type catalog can be slow due to geo-partitioning
444+
# so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
445+
def add_pg_decoders
446+
if @config[:use_follower_reads_for_type_introspection]
447+
@default_timezone = nil
448+
@timestamp_decoder = nil
449+
450+
coders_by_name = {
451+
"int2" => PG::TextDecoder::Integer,
452+
"int4" => PG::TextDecoder::Integer,
453+
"int8" => PG::TextDecoder::Integer,
454+
"oid" => PG::TextDecoder::Integer,
455+
"float4" => PG::TextDecoder::Float,
456+
"float8" => PG::TextDecoder::Float,
457+
"numeric" => PG::TextDecoder::Numeric,
458+
"bool" => PG::TextDecoder::Boolean,
459+
"timestamp" => PG::TextDecoder::TimestampUtc,
460+
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
461+
}
462+
463+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
464+
query = <<~SQL % known_coder_types.join(", ")
465+
SELECT t.oid, t.typname
466+
FROM pg_type as t AS OF SYSTEM TIME '-10s'
467+
WHERE t.typname IN (%s)
468+
SQL
469+
470+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
471+
result
472+
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
473+
.compact
474+
end
475+
476+
map = PG::TypeMapByOid.new
477+
coders.each { |coder| map.add_coder(coder) }
478+
@connection.type_map_for_results = map
479+
480+
@type_map_for_results = PG::TypeMapByOid.new
481+
@type_map_for_results.default_type_map = map
482+
@type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
483+
484+
# extract timestamp decoder for use in update_typemap_for_default_timezone
485+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
486+
update_typemap_for_default_timezone
487+
else
488+
super
489+
end
490+
rescue ActiveRecord::StatementInvalid => e
491+
raise e unless e.cause.is_a? PG::InvalidCatalogName
492+
# use original if database is younger than 10s
493+
super
494+
end
495+
496+
def arel_visitor
497+
Arel::Visitors::CockroachDB.new(self)
498+
end
499+
406500
# end private
407501
end
408502
end

test/cases/adapters/postgresql/postgresql_adapter_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,34 @@ def setup
1515
@connection_handler = ActiveRecord::Base.connection_handler
1616
end
1717

18+
def teardown
19+
# use connection without follower_reads
20+
database_config = { "adapter" => "cockroachdb", "database" => "activerecord_unittest" }
21+
ar_config = ActiveRecord::Base.configurations["arunit"]
22+
database_config.update(ar_config)
23+
24+
ActiveRecord::Base.establish_connection(database_config)
25+
end
26+
1827
def test_database_exists_returns_false_when_the_database_does_not_exist
1928
config = ActiveRecord::Base.configurations["arunit"]
2029
bad_config = config.dup
2130
bad_config[:database] = "non_extant_database"
2231
assert_not ActiveRecord::ConnectionAdapters::CockroachDBAdapter.database_exists?(bad_config),
2332
"expected database #{bad_config[:database]} to not exist"
2433
end
34+
35+
def test_using_follower_reads_connects_properly
36+
database_config = { "use_follower_reads_for_type_introspection": true, "adapter" => "cockroachdb", "database" => "activerecord_unittest" }
37+
ar_config = ActiveRecord::Base.configurations["arunit"]
38+
database_config.update(ar_config)
39+
40+
ActiveRecord::Base.establish_connection(database_config)
41+
conn = ActiveRecord::Base.connection
42+
conn_config = conn.instance_variable_get("@config")
43+
44+
assert conn_config[:use_follower_reads_for_type_introspection]
45+
end
2546
end
2647
end
2748
end

0 commit comments

Comments
 (0)