-
-
Notifications
You must be signed in to change notification settings - Fork 228
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor side_by_side materialized view creation
The initial implementation of side_by_side materialized view creation worked but had a couple of issues that needed to be resolved and I wanted to refactor the code for better maintainability. * We had postgres-specific things in the `Scenic::Index` class, which is not part of the adapter API. The code was refactored to not rely on adding the schema name to this object. * Index migration is different from index reapplication, and it felt like we were reusing `IndexReapplication` just to get access to the `SAVEPOINT` functionality in that class. I extracted `IndexCreation` which is now used by `IndexReapplication` and our new class, `IndexMigration`. * Side-by-side logic was moved to a class of its own, `SideBySide`, for encapsulation purposes. * Instead of conditionally hashing the view name in the case where the temporary name overflows the postgres identifier limit, we now always hash the temporary object names. This just keeps the code simpler and observed behavior from the outside identical no matter identifier length. This behavior is tested in the new `TemporaryName` class. * Removed `rename_materialized_view` from the public API on the adapter, as I'd like to make sure that's something we want separate from this before we do something like that. * Added `connection` to the public adapter UI for ease of use from our helper objects. Documented as internal use only. * Require a transaction in order to use `side_by_side`. This prevents leaving the user in a weird state that would be difficult to recover from. * Added `--side-by-side` (and `--side_by_side`) support to the `scenic:view` generator. Also added `--no-data` as an alias for the existing `--no_data` while I was at it. * I added a number of tests for new and previously existing code throughout, including an acceptance level test for `side_by_side`. Our test coverage should be much improved. * Updated README with information on `side_by_side`. Here's a sample of the output from running a `side_by_side` update: ``` == 20250102191533 UpdateSearchesToVersion3: migrating ========================= -- update_view(:searches, {version: 3, revert_to_version: 2, materialized: {side_by_side: true}}) -> temporary materialized view _scenic_sbs_8a03f467c615b126f59617cc510d2abd41296834 has been created -> indexes on 'searches' have been renamed to avoid collisions -> index 'index_searches_on_content' on '_scenic_sbs_8a03f467c615b126f59617cc510d2abd41296834' has been created -> index 'index_searches_on_user_id' on '_scenic_sbs_8a03f467c615b126f59617cc510d2abd41296834' has been created -> materialized view searches has been dropped -> temporary materialized view _scenic_sbs_8a03f467c615b126f59617cc510d2abd41296834 has been renamed to searches -> 0.0299s == 20250102191533 UpdateSearchesToVersion3: migrated (0.0300s) ================ ```
- Loading branch information
1 parent
cc92ba6
commit a2a9be5
Showing
23 changed files
with
644 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
module Scenic | ||
module Adapters | ||
class Postgres | ||
# Used to resiliently create indexes on a materialized view. If the index | ||
# cannot be applied to the view (e.g. the columns don't exist any longer), | ||
# we log that information and continue rather than raising an error. It is | ||
# left to the user to judge whether the index is necessary and recreate | ||
# it. | ||
# | ||
# Used when updating a materialized view to ensure the new version has all | ||
# apprioriate indexes. | ||
# | ||
# @api private | ||
class IndexCreation | ||
# Creates the index creation object. | ||
# | ||
# @param connection [Connection] The connection to execute SQL against. | ||
# @param speaker [#say] (ActiveRecord::Migration) The object used for | ||
# logging the results of creating indexes. | ||
def initialize(connection:, speaker: ActiveRecord::Migration.new) | ||
@connection = connection | ||
@speaker = speaker | ||
end | ||
|
||
# Creates the provided indexes. If an index cannot be created, it is | ||
# logged and the process continues. | ||
# | ||
# @param indexes [Array<Scenic::Index>] The indexes to create. | ||
# | ||
# @return [void] | ||
def try_create(indexes) | ||
Array(indexes).each(&method(:try_index_create)) | ||
end | ||
|
||
private | ||
|
||
attr_reader :connection, :speaker | ||
|
||
def try_index_create(index) | ||
success = with_savepoint(index.index_name) do | ||
connection.execute(index.definition) | ||
end | ||
|
||
if success | ||
say "index '#{index.index_name}' on '#{index.object_name}' has been created" | ||
else | ||
say "index '#{index.index_name}' on '#{index.object_name}' is no longer valid and has been dropped." | ||
end | ||
end | ||
|
||
def with_savepoint(name) | ||
connection.execute("SAVEPOINT #{name}") | ||
yield | ||
connection.execute("RELEASE SAVEPOINT #{name}") | ||
true | ||
rescue | ||
connection.execute("ROLLBACK TO SAVEPOINT #{name}") | ||
false | ||
end | ||
|
||
def say(message) | ||
subitem = true | ||
speaker.say(message, subitem) | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.