Eli Mesika has uploaded a new change for review. Change subject: core: DB - Snapshot Materialized Views support ......................................................................
core: DB - Snapshot Materialized Views support Adding Snapshot Materialized Views support to the database. A Snapshot Materialized View is actually a table built on top of a real view that enables to select data from the Snapshot Materialized View easily and efficiently. Candidates for Snapshot Materialized Views are views that are based on slowly-changing data. The Snapshot Materialized Views is actually functioning as a cache. The Snapshot Materialized View is refreshed per request. The Snapshot Materialized View definitions are stored in the materialized_views table. Change-Id: I17333db996f0e3b1856e2df02a621810c3ebaa53 Signed-off-by: Eli Mesika <emes...@redhat.com> --- A backend/manager/dbscripts/materialized_views_sp.sql A backend/manager/dbscripts/upgrade/pre_upgrade/0030_add_materialized_views_table.sql 2 files changed, 285 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/05/10105/1 diff --git a/backend/manager/dbscripts/materialized_views_sp.sql b/backend/manager/dbscripts/materialized_views_sp.sql new file mode 100644 index 0000000..6694326 --- /dev/null +++ b/backend/manager/dbscripts/materialized_views_sp.sql @@ -0,0 +1,265 @@ +/* +This file adds Snapshot Materialized Views support to Postgres. +A Snapshot Materialized View is actually a table built on top of +a real view that enables to select data from the Snapshot Materialized View +easily and efficiently. +Candidates for Snapshot Materialized Views are views that are based on +slowly-changing data. The Snapshot Materialized Views is actually +functioning as a cache. +The Snapshot Materialized View is refreshed per request. +The Snapshot Materialized View definitions are stored in the materialized_views table. + +Flow: +1) Create the Materialized View by calling: + CreateMaterializedView - if you are creating a new view + CreateMaterializedViewAs - If you want to preserve the original view name + in this case the original view will be renamed + and the new Materialized View will have the original + view name. +2) If your Snapshot Materialized View is my_mt you should create Stored Procedures: + MtDropmy_mtIndexes - Drops indexes on my_mt + MtCreatemy_mtIndexes - Creates needed indexes on my_mt + Those indexes should be defined in the "Snapshot Materialized Views Index Definitions Section" + in this file. + + Those SP are called automatically when a Snapshot Materialized View is refreshed + to boost refresh performance. + +3) You can call IsMaterializedViewRefreshed to check if it is time to refresh the view + and if yes call RefreshMaterializedView manually. + or + You can define a cron job that calls RefreshAllMaterializedViews that loops over + all Snapshot Materialized Views and refreshes it automatically + +There are 2 additional functions : + CreateAllMaterializedViewsiIndexes - Creates indexes for all Snapshot Materialized views + Drop MaterializedView - Drops the Materialized View + UpdateMaterializedViewRefreshRate - Updates the Materialized View refresh rate +*/ +---------------------------------------- +-- Materialized Views Support -- +---------------------------------------- + +-- Helper Function : converts an integer value to INTERVAL +CREATE OR REPLACE FUNCTION to_interval (sec INTEGER) RETURNS INTERVAL AS $$ +BEGIN + RETURN (sec || ' seconds')::INTERVAL; +END; +$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; + +-- CreateMaterializedViewAsCreates a new Materialized View +CREATE OR REPLACE FUNCTION CreateMaterializedView(v_matview NAME, v_view_name NAME, v_refresh_rate_in_sec INTEGER) + RETURNS VOID +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + BEGIN + SELECT * INTO v_entry FROM materialized_views WHERE mv_name = v_matview; + + IF FOUND THEN + RAISE EXCEPTION 'Materialized view % already exists.', + v_matview; + END IF; + + EXECUTE 'REVOKE ALL ON ' || v_view_name || ' FROM PUBLIC'; + + EXECUTE 'GRANT SELECT ON ' || v_view_name || ' TO PUBLIC'; + + EXECUTE 'CREATE TABLE ' || v_matview || ' AS SELECT * FROM ' || v_view_name; + + EXECUTE 'REVOKE ALL ON ' || v_matview || ' FROM PUBLIC'; + + EXECUTE 'GRANT SELECT ON ' || v_matview || ' TO PUBLIC'; + + INSERT INTO materialized_views (mv_name, v_name, refresh_rate_in_sec, last_refresh) + VALUES (v_matview, v_view_name, v_refresh_rate_in_sec, CURRENT_TIMESTAMP); + + RETURN; + END; $procedure$ + LANGUAGE plpgsql; + +-- Enables to create a New materialized view with a name of existing view +-- This is done in order to solve cases where we are forced to use the old view name for the new createed +-- Materialized View because it is used from dynamic SQL and we have to send only a DB patch without forcing +-- recompilation of engine code +CREATE OR REPLACE FUNCTION CreateMaterializedViewAs(v_view_name NAME, v_refresh_rate_in_sec INTEGER) + RETURNS VOID +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + v_renamed_view NAME; + BEGIN + IF FOUND THEN + RAISE EXCEPTION 'Materialized view % already exists.', + v_view_name; + END IF; + + v_renamed_view := v_view_name || '_mt_base'; + EXECUTE 'ALTER VIEW ' || v_view_name || ' RENAME TO ' || v_renamed_view; + perform CreateMaterializedView(v_view_name, v_renamed_view, v_refresh_rate_in_sec); + + RETURN; + END; $procedure$ + LANGUAGE plpgsql; + +-- Drops a Materialized View +CREATE OR REPLACE FUNCTION DropMaterializedView(v_matview NAME) + RETURNS VOID +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + BEGIN + + SELECT * INTO v_entry FROM materialized_views WHERE mv_name = v_matview; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Materialized view % does not exist.', v_matview; + END IF; + + EXECUTE 'DROP TABLE ' || v_matview || ' CASCADE'; + DELETE FROM materialized_views WHERE mv_name=v_matview; + + RETURN; + END; $procedure$ + LANGUAGE plpgsql; + +-- Checks if Materialized View should be refreshed +CREATE OR REPLACE FUNCTION IsMaterializedViewRefreshed(v_matview NAME) + RETURNS boolean +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + v_is_refreshed boolean; + BEGIN + SELECT * INTO v_entry FROM materialized_views WHERE mv_name = v_matview; + IF NOT FOUND THEN + RAISE EXCEPTION 'Materialized view % does not exist.', v_matview; + END IF; + + -- check if materialized View should refresh + v_is_refreshed := (CURRENT_TIMESTAMP - to_interval(refresh_rate_in_sec)) <= last_refresh from materialized_views + where mv_name = v_matview; + RETURN v_is_refreshed; + END; $procedure$ + LANGUAGE plpgsql; + +-- Refreshes a Materialized View +CREATE OR REPLACE FUNCTION RefreshMaterializedView(v_matview NAME) + RETURNS VOID +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + v_drop_index_sp NAME; + v_create_index_sp NAME; + BEGIN + SELECT * INTO v_entry FROM materialized_views WHERE mv_name = v_matview; + IF NOT FOUND THEN + RAISE EXCEPTION 'Materialized view % does not exist.', v_matview; + END IF; + + -- SP for Drop / create Index should follow naming convention Mt[Drop|Create]<v_matview>Indexes + v_drop_index_sp := 'MtDrop' || v_matview || 'Indexes'; + v_create_index_sp := 'MtCreate' || v_matview || 'Indexes'; + + IF NOT EXISTS (select 1 from information_schema.routines where routine_name ilike v_drop_index_sp) THEN + v_drop_index_sp := NULL; + END IF; + + IF NOT EXISTS (select 1 from information_schema.routines where routine_name ilike v_create_index_sp) THEN + v_create_index_sp := NULL; + END IF; + + /* update last refreshed time */ + + -- Lock materialized_views table until refresh completes to prevent duplicate refreshes by other threads + LOCK TABLE materialized_views; + update materialized_views set last_refresh = CURRENT_TIMESTAMP where mv_name = v_matview; + -- taking a lock on the snapshot materialized view until it refreshed + EXECUTE 'LOCK TABLE ' || v_matview; + -- drop indexes on the snapshot materialized view if exists + IF (v_drop_index_sp IS NOT NULL) THEN + EXECUTE 'select ' || v_drop_index_sp || '()'; + END IF; + -- refresh the view + EXECUTE 'DELETE FROM ' || v_matview; + EXECUTE 'INSERT INTO ' || v_matview + || ' SELECT * FROM ' || v_entry.v_name; + -- restore indexes on the snapshot materialized view if exists + IF (v_create_index_sp IS NOT NULL) THEN + EXECUTE 'select ' || v_create_index_sp || '()'; + END IF; + RETURN; + END; $procedure$ + LANGUAGE plpgsql; + +-- Refresh all materialized views (if needed) +CREATE OR REPLACE FUNCTION RefreshAllMaterializedViews() +RETURNS void +AS $procedure$ +DECLARE + v_cur CURSOR FOR SELECT * FROM materialized_views; + v_record materialized_views%ROWTYPE; +BEGIN + OPEN v_cur; + -- loop on all entries in materialized_views and refresh only needed snapshots + LOOP + FETCH v_cur INTO v_record; + EXIT WHEN NOT FOUND; + IF (not IsMaterializedViewRefreshed(v_record.mv_name)) THEN + perform RefreshMaterializedView(v_record.mv_name); + END IF; + END LOOP; + CLOSE v_cur; +END; $procedure$ +LANGUAGE plpgsql; + +-- Creates all materialized views indexes +CREATE OR REPLACE FUNCTION CreateAllMaterializedViewsiIndexes() +RETURNS void +AS $procedure$ +DECLARE + v_cur CURSOR FOR SELECT * FROM materialized_views; + v_record materialized_views%ROWTYPE; + v_create_index_sp NAME; +BEGIN + OPEN v_cur; + -- loop on all entries in materialized_views and create indexes(if defined) + LOOP + FETCH v_cur INTO v_record; + EXIT WHEN NOT FOUND; + v_create_index_sp := 'MtCreate' || v_record.mv_name || 'Indexes'; + -- Check if SP that creates the indexes exists + IF NOT EXISTS (select 1 from information_schema.routines where routine_name ilike v_create_index_sp) THEN + v_create_index_sp := NULL; + END IF; + IF (v_create_index_sp IS NOT NULL) THEN + EXECUTE 'select ' || v_create_index_sp || '()'; + END IF; + END LOOP; + CLOSE v_cur; +END; $procedure$ +LANGUAGE plpgsql; + +-- Updates a Materialized View refresh rate +CREATE OR REPLACE FUNCTION UpdateMaterializedViewRefreshRate(v_matview NAME, v_refresh_rate INTEGER) + RETURNS VOID +AS $procedure$ +DECLARE + v_entry materialized_views%ROWTYPE; + BEGIN + SELECT * INTO v_entry FROM materialized_views WHERE mv_name = v_matview; + IF NOT FOUND THEN + RAISE EXCEPTION 'Materialized view % does not exist.', v_matview; + END IF; + + update materialized_views set refresh_rate_in_sec = v_refresh_rate + where mv_name = v_matview; + RETURN; + END; $procedure$ + LANGUAGE plpgsql; + + +/****************************************************************************************************** + Snapshot Materialized Views Index Definitions Section +******************************************************************************************************/ + diff --git a/backend/manager/dbscripts/upgrade/pre_upgrade/0030_add_materialized_views_table.sql b/backend/manager/dbscripts/upgrade/pre_upgrade/0030_add_materialized_views_table.sql new file mode 100644 index 0000000..f338b21 --- /dev/null +++ b/backend/manager/dbscripts/upgrade/pre_upgrade/0030_add_materialized_views_table.sql @@ -0,0 +1,20 @@ +-- Create materialized_views table +CREATE FUNCTION __temp__0030_add_materialized_views_table() +RETURNS VOID +AS $procedure$ +BEGIN + IF not exists (select 1 from information_schema.tables where table_name ='materialized_views') then + CREATE TABLE materialized_views ( + mv_name NAME NOT NULL PRIMARY KEY, + v_name NAME NOT NULL, + refresh_rate_in_sec INTEGER, + last_refresh TIMESTAMP WITH TIME ZONE + ); + END IF; +END; $procedure$ +LANGUAGE plpgsql; + +select __temp__0030_add_materialized_views_table(); +DROP FUNCTION __temp__0030_add_materialized_views_table(); + + -- To view, visit http://gerrit.ovirt.org/10105 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I17333db996f0e3b1856e2df02a621810c3ebaa53 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Eli Mesika <emes...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches