aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG.md23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb64
-rw-r--r--activerecord/lib/active_record/railtie.rb17
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb15
-rw-r--r--railties/lib/rails/application/configuration.rb3
-rw-r--r--railties/test/application/initializers/frameworks_test.rb26
-rw-r--r--railties/test/application/rake_test.rb19
9 files changed, 173 insertions, 18 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 92e0b47469..21f2b97f5d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,28 @@
## Rails 4.0.0 (unreleased) ##
+* Added the schema cache dump feature.
+
+ `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
+ because we want to boot rails more quickly when we have many models.
+
+ Usage notes:
+
+ 1) execute rake task.
+ RAILS_ENV=production bundle exec rake db:schema:cache:dump
+ => generate db/schema_cache.dump
+
+ 2) add config.use_schema_cache_dump = true in config/production.rb. BTW, true is default.
+
+ 3) boot rails.
+ RAILS_ENV=production bundle exec rails server
+ => use db/schema_cache.db
+
+ 4) If you remove clear dumped cache, execute rake task.
+ RAILS_ENV=production bundle exec rake db:schema:cache:clear
+ => remove db/schema_cache.dump
+
+ *kennyj*
+
* Added support for partial indices to PostgreSQL adapter
The `add_index` method now supports a `where` option that receives a
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 594649189d..767c99de4c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -86,6 +86,11 @@ module ActiveRecord
end
end
+ def schema_cache=(cache)
+ cache.connection = self
+ @schema_cache = cache
+ end
+
def expire
@in_use = false
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 962718da56..aad1f9a7ef 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,26 +1,17 @@
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :columns, :columns_hash, :primary_keys, :tables
- attr_reader :connection
+ attr_reader :columns, :columns_hash, :primary_keys, :tables, :version
+ attr_accessor :connection
def initialize(conn)
@connection = conn
- @tables = {}
- @columns = Hash.new do |h, table_name|
- h[table_name] = conn.columns(table_name)
- end
-
- @columns_hash = Hash.new do |h, table_name|
- h[table_name] = Hash[columns[table_name].map { |col|
- [col.name, col]
- }]
- end
-
- @primary_keys = Hash.new do |h, table_name|
- h[table_name] = table_exists?(table_name) ? conn.primary_key(table_name) : nil
- end
+ @columns = {}
+ @columns_hash = {}
+ @primary_keys = {}
+ @tables = {}
+ prepare_default_proc
end
# A cached lookup for table existence.
@@ -30,12 +21,22 @@ module ActiveRecord
@tables[name] = connection.table_exists?(name)
end
+ # Add internal cache for table with +table_name+.
+ def add(table_name)
+ if table_exists?(table_name)
+ @primary_keys[table_name]
+ @columns[table_name]
+ @columns_hash[table_name]
+ end
+ end
+
# Clears out internal caches
def clear!
@columns.clear
@columns_hash.clear
@primary_keys.clear
@tables.clear
+ @version = nil
end
# Clear out internal caches for table with +table_name+.
@@ -45,6 +46,37 @@ module ActiveRecord
@primary_keys.delete table_name
@tables.delete table_name
end
+
+ def marshal_dump
+ # if we get current version during initialization, it happens stack over flow.
+ @version = ActiveRecord::Migrator.current_version
+ [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
+ self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
+ end
+ end
+
+ def marshal_load(array)
+ @version, @columns, @columns_hash, @primary_keys, @tables = array
+ prepare_default_proc
+ end
+
+ private
+
+ def prepare_default_proc
+ @columns.default_proc = Proc.new do |h, table_name|
+ h[table_name] = connection.columns(table_name)
+ end
+
+ @columns_hash.default_proc = Proc.new do |h, table_name|
+ h[table_name] = Hash[columns[table_name].map { |col|
+ [col.name, col]
+ }]
+ end
+
+ @primary_keys.default_proc = Proc.new do |h, table_name|
+ h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 058dd58efb..516c450f85 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -107,7 +107,7 @@ module ActiveRecord
config.watchable_files.concat ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"]
end
- config.after_initialize do
+ config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.instantiate_observers
@@ -115,6 +115,21 @@ module ActiveRecord
ActiveRecord::Base.instantiate_observers
end
end
+
+ ActiveSupport.on_load(:active_record) do
+ if app.config.use_schema_cache_dump
+ filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+ if File.file?(filename)
+ cache = Marshal.load(open(filename, 'rb') { |f| f.read })
+ if cache.version == ActiveRecord::Migrator.current_version
+ ActiveRecord::Base.connection.schema_cache = cache
+ else
+ warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
+ end
+ end
+ end
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index cf0092e0e3..f26989ae57 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -372,6 +372,25 @@ db_namespace = namespace :db do
task :load_if_ruby => 'db:create' do
db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby
end
+
+ namespace :cache do
+ desc 'Create a db/schema_cache.dump file.'
+ task :dump => :environment do
+ con = ActiveRecord::Base.connection
+ filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+
+ con.schema_cache.clear!
+ con.tables.each { |table| con.schema_cache.add(table) }
+ open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
+ end
+
+ desc 'Clear a db/schema_cache.dump file.'
+ task :clear => :environment do
+ filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ FileUtils.rm(filename) if File.exists?(filename)
+ end
+ end
+
end
namespace :structure do
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 42e39d534c..541e983758 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -39,6 +39,21 @@ module ActiveRecord
assert_equal 0, @cache.tables.size
assert_equal 0, @cache.primary_keys.size
end
+
+ def test_dump_and_load
+ @cache.columns['posts']
+ @cache.columns_hash['posts']
+ @cache.tables['posts']
+ @cache.primary_keys['posts']
+
+ @cache = Marshal.load(Marshal.dump(@cache))
+
+ assert_equal 12, @cache.columns['posts'].size
+ assert_equal 12, @cache.columns_hash['posts'].size
+ assert @cache.tables['posts']
+ assert_equal 'id', @cache.primary_keys['posts']
+ end
+
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 1e424d9b4a..0f5fc2b7bc 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -11,7 +11,7 @@ module Rails
:force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks,
:railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
- :time_zone, :reload_classes_only_on_change
+ :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump
attr_writer :log_level
attr_reader :encoding
@@ -41,6 +41,7 @@ module Rails
@file_watcher = ActiveSupport::FileUpdateChecker
@exceptions_app = nil
@autoflush_log = true
+ @use_schema_cache_dump = true
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 8812685620..a0e88cd0f0 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -193,5 +193,31 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert_nil defined?(ActiveRecord::Base)
end
+
+ test "use schema cache dump" do
+ Dir.chdir(app_path) do
+ `rails generate model post title:string`
+ `bundle exec rake db:migrate`
+ `bundle exec rake db:schema:cache:dump`
+ end
+ require "#{app_path}/config/environment"
+ ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
+ assert ActiveRecord::Base.connection.schema_cache.tables["posts"]
+ end
+
+ test "expire schema cache dump" do
+ Dir.chdir(app_path) do
+ `rails generate model post title:string`
+ `bundle exec rake db:migrate`
+ `bundle exec rake db:schema:cache:dump`
+
+ `bundle exec rake db:rollback`
+ end
+ silence_warnings {
+ require "#{app_path}/config/environment"
+ assert !ActiveRecord::Base.connection.schema_cache.tables["posts"]
+ }
+ end
+
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index ff12b3e9fc..9515e40b6e 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -138,5 +138,24 @@ module ApplicationTests
end
assert File.exists?(File.join(app_path, 'db', 'my_structure.sql'))
end
+
+ def test_rake_dump_schema_cache
+ Dir.chdir(app_path) do
+ `rails generate model post title:string`
+ `rails generate model product name:string`
+ `bundle exec rake db:migrate`
+ `bundle exec rake db:schema:cache:dump`
+ end
+ assert File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
+ end
+
+ def test_rake_clear_schema_cache
+ Dir.chdir(app_path) do
+ `bundle exec rake db:schema:cache:dump`
+ `bundle exec rake db:schema:cache:clear`
+ end
+ assert !File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
+ end
+
end
end