Skip to content

Commit d8391b2

Browse files
Merge pull request rails#49349 from fractaledmind/ar-sqlite-tune-connection-config
Performance tune the SQLite3 adapter connection configuration
2 parents 7c07787 + c6d7ffc commit d8391b2

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* Performance tune the SQLite3 adapter connection configuration
2+
3+
For Rails applications, the Write-Ahead-Log in normal syncing mode with a capped journal size, a healthy shared memory buffer and a shared cache will perform, on average, 2× better.
4+
5+
*Stephen Margheim*
6+
17
* Allow SQLite3 `busy_handler` to be configured with simple max number of `retries`
28

39
Retrying busy connections without delay is a preferred practice for performance-sensitive applications. Add support for a `database.yml` `retries` integer, which is used in a simple `busy_handler` function to retry busy connections without exponential backoff up to the max number of `retries`.

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,29 @@ def configure_connection
723723
end
724724
end
725725

726+
# Enforce foreign key constraints
727+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
728+
# https://www.sqlite.org/foreignkeys.html
726729
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730+
unless @memory_database
731+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
732+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
733+
raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734+
# Set more relaxed level of database durability
735+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
736+
# https://www.sqlite.org/pragma.html#pragma_synchronous
737+
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
738+
# Set the global memory map so all processes can share some data
739+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
740+
# https://www.sqlite.org/mmap.html
741+
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
742+
end
743+
# Impose a limit on the WAL file to prevent unlimited growth
744+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
745+
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
746+
# Set the local connection cache to 2000 pages
747+
# https://www.sqlite.org/pragma.html#pragma_cache_size
748+
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
727749
end
728750
end
729751
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)

activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ def test_encoding
136136
assert_equal "UTF-8", @conn.encoding
137137
end
138138

139+
def test_default_pragmas
140+
if in_memory_db?
141+
assert_equal [{ "foreign_keys" => 1 }], @conn.execute("PRAGMA foreign_keys")
142+
assert_equal [{ "journal_mode" => "memory" }], @conn.execute("PRAGMA journal_mode")
143+
assert_equal [{ "synchronous" => 2 }], @conn.execute("PRAGMA synchronous")
144+
assert_equal [{ "journal_size_limit" => 67108864 }], @conn.execute("PRAGMA journal_size_limit")
145+
assert_equal [], @conn.execute("PRAGMA mmap_size")
146+
assert_equal [{ "cache_size" => 2000 }], @conn.execute("PRAGMA cache_size")
147+
else
148+
with_file_connection do |conn|
149+
assert_equal [{ "foreign_keys" => 1 }], conn.execute("PRAGMA foreign_keys")
150+
assert_equal [{ "journal_mode" => "wal" }], conn.execute("PRAGMA journal_mode")
151+
assert_equal [{ "synchronous" => 1 }], conn.execute("PRAGMA synchronous")
152+
assert_equal [{ "journal_size_limit" => 67108864 }], conn.execute("PRAGMA journal_size_limit")
153+
assert_equal [{ "mmap_size" => 134217728 }], conn.execute("PRAGMA mmap_size")
154+
assert_equal [{ "cache_size" => 2000 }], conn.execute("PRAGMA cache_size")
155+
end
156+
end
157+
end
158+
139159
def test_exec_no_binds
140160
with_example_table "id int, data string" do
141161
result = @conn.exec_query("SELECT id, data FROM ex")
@@ -815,6 +835,17 @@ def with_strict_strings_by_default
815835
ensure
816836
SQLite3Adapter.strict_strings_by_default = false
817837
end
838+
839+
def with_file_connection(options = {})
840+
options = options.dup
841+
db_config = ActiveRecord::Base.configurations.configurations.find { |config| !config.database.include?(":memory:") }
842+
options[:database] ||= db_config.database
843+
conn = ActiveRecord::Base.sqlite3_connection(options)
844+
845+
yield(conn)
846+
ensure
847+
conn.disconnect! if conn
848+
end
818849
end
819850
end
820851
end

0 commit comments

Comments
 (0)