Commit 0dd449a8 authored by Patrick Bair's avatar Patrick Bair Committed by Andreas Brandl

Add migration to swap partitioned audit_events

Add a new migration to swap the partitioned audit_events with the
original table. In preparation for this, also add a prior migration to
recreate the secondary indexes on the partitioned table.
parent 0675d91b
......@@ -4,6 +4,7 @@ class AuditEvent < ApplicationRecord
include CreatedAtFilterable
include BulkInsertSafe
include EachBatch
include PartitionedTable
PARALLEL_PERSISTENCE_COLUMNS = [
:author_name,
......@@ -15,6 +16,8 @@ class AuditEvent < ApplicationRecord
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
......
# frozen_string_literal: true
# This model is not intended to be used.
# It is a temporary reference to the pre-partitioned
# audit_events table.
# Please refer to https://gitlab.com/groups/gitlab-org/-/epics/3206
# for details.
class AuditEventArchived < ApplicationRecord
self.table_name = 'audit_events_archived'
end
# frozen_string_literal: true
# This model is not yet intended to be used.
# It is in a transitioning phase while we are partitioning
# the table on the database-side.
# Please refer to https://gitlab.com/groups/gitlab-org/-/epics/3206
# for details.
class AuditEventPartitioned < ApplicationRecord
include PartitionedTable
self.table_name = 'audit_events_part_5fc467ac26'
partitioned_by :created_at, strategy: :monthly
end
---
title: Add migration to swap partitioned audit_events
merge_request: 47581
author:
type: added
......@@ -3,7 +3,7 @@
# Make sure we have loaded partitioned models here
# (even with eager loading disabled).
Gitlab::Database::Partitioning::PartitionCreator.register(AuditEventPartitioned)
Gitlab::Database::Partitioning::PartitionCreator.register(AuditEvent)
begin
Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions unless ENV['DISABLE_POSTGRES_PARTITION_CREATION_ON_STARTUP']
......
# frozen_string_literal: true
class SwapPartitionedAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::PartitioningMigrationHelpers
DOWNTIME = false
def up
replace_with_partitioned_table :audit_events
end
def down
rollback_replace_with_partitioned_table :audit_events
end
end
a436597e876a6d9efc2c1558e05dc576cbbc6f829dc8059d62fc231bbf0ce2fa
\ No newline at end of file
......@@ -15,46 +15,46 @@ CREATE FUNCTION table_sync_function_2be879775d() RETURNS trigger
AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
DELETE FROM audit_events_part_5fc467ac26 where id = OLD.id;
DELETE FROM audit_events_archived where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE audit_events_part_5fc467ac26
UPDATE audit_events_archived
SET author_id = NEW.author_id,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
created_at = NEW.created_at,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
target_id = NEW.target_id,
created_at = NEW.created_at
WHERE audit_events_part_5fc467ac26.id = NEW.id;
target_id = NEW.target_id
WHERE audit_events_archived.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO audit_events_part_5fc467ac26 (id,
INSERT INTO audit_events_archived (id,
author_id,
entity_id,
entity_type,
details,
created_at,
ip_address,
author_name,
entity_path,
target_details,
target_type,
target_id,
created_at)
target_id)
VALUES (NEW.id,
NEW.author_id,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.created_at,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.target_id,
NEW.created_at);
NEW.target_id);
END IF;
RETURN NULL;
......@@ -63,7 +63,7 @@ $$;
COMMENT ON FUNCTION table_sync_function_2be879775d() IS 'Partitioning migration: table sync for audit_events table';
CREATE TABLE audit_events_part_5fc467ac26 (
CREATE TABLE audit_events (
id bigint NOT NULL,
author_id integer NOT NULL,
entity_id integer NOT NULL,
......@@ -9623,7 +9623,7 @@ CREATE SEQUENCE atlassian_identities_user_id_seq
ALTER SEQUENCE atlassian_identities_user_id_seq OWNED BY atlassian_identities.user_id;
CREATE TABLE audit_events (
CREATE TABLE audit_events_archived (
id integer NOT NULL,
author_id integer NOT NULL,
entity_id integer NOT NULL,
......@@ -19147,11 +19147,11 @@ ALTER TABLE ONLY ar_internal_metadata
ALTER TABLE ONLY atlassian_identities
ADD CONSTRAINT atlassian_identities_pkey PRIMARY KEY (user_id);
ALTER TABLE ONLY audit_events_part_5fc467ac26
ADD CONSTRAINT audit_events_part_5fc467ac26_pkey PRIMARY KEY (id, created_at);
ALTER TABLE ONLY audit_events_archived
ADD CONSTRAINT audit_events_archived_pkey PRIMARY KEY (id);
ALTER TABLE ONLY audit_events
ADD CONSTRAINT audit_events_pkey PRIMARY KEY (id);
ADD CONSTRAINT audit_events_pkey PRIMARY KEY (id, created_at);
ALTER TABLE ONLY authentication_events
ADD CONSTRAINT authentication_events_pkey PRIMARY KEY (id);
......@@ -20521,9 +20521,9 @@ CREATE INDEX product_analytics_events_experi_project_id_collector_tstamp_idx ON
CREATE INDEX active_billable_users ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[NULL::integer, 6, 4]))) AND ((user_type IS NULL) OR (user_type <> ALL ('{2,6,1,3,7,8}'::smallint[]))));
CREATE INDEX analytics_index_audit_events_on_created_at_and_author_id ON audit_events USING btree (created_at, author_id);
CREATE INDEX analytics_index_audit_events_on_created_at_and_author_id ON audit_events_archived USING btree (created_at, author_id);
CREATE INDEX analytics_index_audit_events_part_on_created_at_and_author_id ON ONLY audit_events_part_5fc467ac26 USING btree (created_at, author_id);
CREATE INDEX analytics_index_audit_events_part_on_created_at_and_author_id ON ONLY audit_events USING btree (created_at, author_id);
CREATE INDEX analytics_index_events_on_created_at_and_author_id ON events USING btree (created_at, author_id);
......@@ -20567,9 +20567,9 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions US
CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id);
CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events_archived USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_audit_events_part_on_entity_id_desc_author_id_created_at ON ONLY audit_events_part_5fc467ac26 USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_audit_events_part_on_entity_id_desc_author_id_created_at ON ONLY audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
......
......@@ -19,7 +19,7 @@ RSpec.describe 'Database schema' do
approver_groups: %w[target_id],
approvers: %w[target_id user_id],
audit_events: %w[author_id entity_id target_id],
audit_events_part_5fc467ac26: %w[author_id entity_id target_id],
audit_events_archived: %w[author_id entity_id target_id],
award_emoji: %w[awardable_id user_id],
aws_roles: %w[role_external_id],
boards: %w[milestone_id iteration_id],
......
......@@ -2,35 +2,35 @@
require 'spec_helper'
RSpec.describe AuditEventPartitioned do
RSpec.describe AuditEventArchived do
let(:source_table) { AuditEvent }
let(:partitioned_table) { described_class }
let(:destination_table) { described_class }
it 'has the same columns as the source table' do
column_names_from_source_table = column_names(source_table)
column_names_from_partioned_table = column_names(partitioned_table)
column_names_from_destination_table = column_names(destination_table)
expect(column_names_from_partioned_table).to match_array(column_names_from_source_table)
expect(column_names_from_destination_table).to match_array(column_names_from_source_table)
end
it 'has the same null constraints as the source table' do
constraints_from_source_table = null_constraints(source_table)
constraints_from_partitioned_table = null_constraints(partitioned_table)
constraints_from_destination_table = null_constraints(destination_table)
expect(constraints_from_partitioned_table.to_a).to match_array(constraints_from_source_table.to_a)
expect(constraints_from_destination_table.to_a).to match_array(constraints_from_source_table.to_a)
end
it 'inserts the same record as the one in the source table', :aggregate_failures do
expect { create(:audit_event) }.to change { partitioned_table.count }.by(1)
expect { create(:audit_event) }.to change { destination_table.count }.by(1)
event_from_source_table = source_table.connection.select_one(
"SELECT * FROM #{source_table.table_name} ORDER BY created_at desc LIMIT 1"
)
event_from_partitioned_table = partitioned_table.connection.select_one(
"SELECT * FROM #{partitioned_table.table_name} ORDER BY created_at desc LIMIT 1"
event_from_destination_table = destination_table.connection.select_one(
"SELECT * FROM #{destination_table.table_name} ORDER BY created_at desc LIMIT 1"
)
expect(event_from_partitioned_table).to eq(event_from_source_table)
expect(event_from_destination_table).to eq(event_from_source_table)
end
def column_names(table)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment