← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stub/launchpad/bug-794802-journal into lp:launchpad/db-devel

 

Stuart Bishop has proposed merging lp:~stub/launchpad/bug-794802-journal into lp:launchpad/db-devel with lp:~stub/launchpad/bug-794802 as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~stub/launchpad/bug-794802-journal/+merge/64051

= Summary =

Move to a journaled system where the journaled entries are rolled up out of band.

As of this writing, test_bugsummary.py is passing but bugsummary.txt seems to have triggered an error. This looks like it might be legitimate.

== Proposed fix ==

== Pre-implementation notes ==

== Implementation details ==

== Tests ==

== Demo and Q/A ==


= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  database/schema/patch-2208-63-1.sql
  database/schema/patch-2208-63-4.sql
  database/schema/patch-2208-75-0.sql
  database/schema/patch-2208-63-2.sql
  lib/lp/bugs/doc/bugsummary.txt
  database/schema/security.cfg
  database/schema/patch-2208-63-3.sql
  lib/lp/bugs/model/bugsummary.py
  lib/lp/registry/model/person.py


== Schema ==

./lib/lp/bugs/doc/bugsummary.txt
      89: narrative has trailing whitespace.
     254: 'ProductSeries' imported but unused
-- 
https://code.launchpad.net/~stub/launchpad/bug-794802-journal/+merge/64051
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stub/launchpad/bug-794802-journal into lp:launchpad/db-devel.
=== added file 'database/schema/patch-2208-63-3.sql'
--- database/schema/patch-2208-63-3.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-63-3.sql	2011-06-09 16:45:00 +0000
@@ -0,0 +1,55 @@
+-- Copyright 2011 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+
+-- Create a journal for BugSummary updates.
+-- This is a separate DB patch as the table needs to be created and
+-- added to replication before triggers are created, and we want to
+-- do this live. We discussed not replicating this table, but this
+-- would break our ability to failover to a new master.
+
+CREATE TABLE BugSummaryJournal (
+    id serial PRIMARY KEY,
+    count INTEGER NOT NULL default 0,
+    product INTEGER REFERENCES Product ON DELETE CASCADE,
+    productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE,
+    distribution INTEGER REFERENCES Distribution ON DELETE CASCADE,
+    distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE,
+    sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE,
+    viewed_by INTEGER REFERENCES Person ON DELETE CASCADE,
+    tag TEXT,
+    status INTEGER NOT NULL,
+    milestone INTEGER REFERENCES Milestone ON DELETE CASCADE);
+
+-- Fat index for fast lookups
+CREATE INDEX bugsummaryjournal__full__idx ON BugSummaryJournal (
+    status, product, productseries, distribution, distroseries,
+    sourcepackagename, viewed_by, milestone, tag);
+
+-- Indexes for fast deletions.
+CREATE INDEX bugsummaryjournal__viewed_by__idx
+    ON BugSummaryJournal(viewed_by) WHERE viewed_by IS NOT NULL;
+CREATE INDEX bugsummaryjournal__milestone__idx
+    ON BugSummaryJournal(milestone) WHERE milestone IS NOT NULL;
+
+
+-- Combined view so we don't have to manually collate rows from both tables.
+-- Note that we flip the sign of the id column of BugSummaryJournal to avoid
+-- clashes. This is enough to keep Storm happy as it never needs to update
+-- this table, and there are no other suitable primary keys.
+-- We don't SUM() rows here to ensure PostgreSQL has the most hope of
+-- generating good query plans when we query this view.
+CREATE OR REPLACE VIEW CombinedBugSummary AS (
+    SELECT
+        id, count, product, productseries, distribution, distroseries,
+        sourcepackagename, viewed_by, tag, status, milestone
+    FROM BugSummary
+    UNION ALL
+    SELECT
+        -id as id, count, product, productseries, distribution, distroseries,
+        sourcepackagename, viewed_by, tag, status, milestone
+    FROM BugSummaryJournal);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 3);

=== added file 'database/schema/patch-2208-63-4.sql'
--- database/schema/patch-2208-63-4.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-63-4.sql	2011-06-09 16:45:00 +0000
@@ -0,0 +1,280 @@
+-- Copyright 2011 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+CREATE OR REPLACE FUNCTION bugsummary_journal_ins(d bugsummary)
+RETURNS VOID
+LANGUAGE plpgsql AS
+$$
+BEGIN
+    IF d.count <> 0 THEN
+        INSERT INTO BugSummaryJournal (
+            count, product, productseries, distribution,
+            distroseries, sourcepackagename, viewed_by, tag,
+            status, milestone)
+        VALUES (
+            d.count, d.product, d.productseries, d.distribution,
+            d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+            d.status, d.milestone);
+    END IF;
+END;
+$$;
+
+COMMENT ON FUNCTION bugsummary_journal_ins(bugsummary) IS
+'Add an entry into BugSummaryJournal';
+
+
+CREATE OR REPLACE FUNCTION bugsummary_rollup_journal() RETURNS VOID
+LANGUAGE plpgsql VOLATILE
+SECURITY DEFINER SET search_path TO public AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+    max_id integer;
+BEGIN
+    -- Lock so we don't content with other invokations of this
+    -- function. We can happily lock the BugSummary table for writes
+    -- as this function is the only thing that updates that table.
+    -- BugSummaryJournal remains unlocked so nothing should be blocked.
+    LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
+
+    SELECT MAX(id) INTO max_id FROM BugSummaryJournal;
+
+    FOR d IN
+        SELECT
+            NULL as id,
+            SUM(count),
+            product,
+            productseries,
+            distribution,
+            distroseries,
+            sourcepackagename,
+            viewed_by,
+            tag,
+            status,
+            milestone
+        FROM BugSummaryJournal
+        WHERE id <= max_id
+        GROUP BY
+            product, productseries, distribution, distroseries,
+            sourcepackagename, viewed_by, tag, status, milestone
+        HAVING sum(count) <> 0
+    LOOP
+        IF d.count < 0 THEN
+            PERFORM bug_summary_dec(d);
+        ELSIF d.count > 0 THEN
+            PERFORM bug_summary_inc(d);
+        END IF;
+    END LOOP;
+
+    DELETE FROM BugSummaryJournal WHERE id <= max_id;
+END;
+$$;
+
+COMMENT ON FUNCTION bugsummary_rollup_journal() IS
+'Collate and migrate rows from BugSummaryJournal to BugSummary';
+
+
+CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        d.count = -1;
+        PERFORM bugsummary_journal_ins(d);
+    END LOOP;
+END;
+$$;
+
+
+CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        d.count = 1;
+        PERFORM bugsummary_journal_ins(d);
+    END LOOP;
+END;
+$$;
+
+CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary()
+RETURNS TRIGGER LANGUAGE plpgsql VOLATILE
+SECURITY DEFINER SET search_path TO public AS
+$$
+BEGIN
+    -- This trigger only works if we are inserting, updating or deleting
+    -- a single row per statement.
+    IF TG_OP = 'INSERT' THEN
+        IF NOT (bug_row(NEW.bug)).private THEN
+            -- Public subscriptions are not aggregated.
+            RETURN NEW;
+        END IF;
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        RETURN NEW;
+    ELSIF TG_OP = 'DELETE' THEN
+        IF NOT (bug_row(OLD.bug)).private THEN
+            -- Public subscriptions are not aggregated.
+            RETURN OLD;
+        END IF;
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        RETURN OLD;
+    ELSE
+        IF (OLD.person IS DISTINCT FROM NEW.person
+            OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
+            IF TG_WHEN = 'BEFORE' THEN
+                IF (bug_row(OLD.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM unsummarise_bug(bug_row(OLD.bug));
+                END IF;
+                IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM unsummarise_bug(bug_row(NEW.bug));
+                END IF;
+            ELSE
+                IF (bug_row(OLD.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM summarise_bug(bug_row(OLD.bug));
+                END IF;
+                IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM summarise_bug(bug_row(NEW.bug));
+                END IF;
+            END IF;
+        END IF;
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+
+CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER
+LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
+$$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        RETURN NEW;
+    ELSIF TG_OP = 'DELETE' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        RETURN OLD;
+    ELSE
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+            IF OLD.bug <> NEW.bug THEN
+                PERFORM unsummarise_bug(bug_row(NEW.bug));
+            END IF;
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+            IF OLD.bug <> NEW.bug THEN
+                PERFORM summarise_bug(bug_row(NEW.bug));
+            END IF;
+        END IF;
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+-- fixed to use the journal
+CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER
+LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
+$$
+BEGIN
+    -- There is no INSERT logic, as a bug will not have any summary
+    -- information until BugTask rows have been attached.
+    IF TG_OP = 'UPDATE' THEN
+        IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
+            OR OLD.private IS DISTINCT FROM NEW.private THEN
+            PERFORM unsummarise_bug(OLD);
+            PERFORM summarise_bug(NEW);
+        END IF;
+
+    ELSIF TG_OP = 'DELETE' THEN
+        PERFORM unsummarise_bug(OLD);
+    END IF;
+
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+-- fixed to use the journal
+CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER
+LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
+$$
+BEGIN
+    -- This trigger only works if we are inserting, updating or deleting
+    -- a single row per statement.
+
+    -- Unlike bug_maintain_bug_summary, this trigger does not have access
+    -- to the old bug when invoked as an AFTER trigger. To work around this
+    -- we install this trigger as both a BEFORE and an AFTER trigger.
+    IF TG_OP = 'INSERT' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        RETURN NEW;
+
+    ELSIF TG_OP = 'DELETE' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        RETURN OLD;
+
+    ELSE
+        IF (OLD.product IS DISTINCT FROM NEW.product
+            OR OLD.productseries IS DISTINCT FROM NEW.productseries
+            OR OLD.distribution IS DISTINCT FROM NEW.distribution
+            OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
+            OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
+            OR OLD.status IS DISTINCT FROM NEW.status
+            OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
+            IF TG_WHEN = 'BEFORE' THEN
+                PERFORM unsummarise_bug(bug_row(OLD.bug));
+                IF OLD.bug <> NEW.bug THEN
+                    PERFORM unsummarise_bug(bug_row(NEW.bug));
+                END IF;
+            ELSE
+                PERFORM summarise_bug(bug_row(OLD.bug));
+                IF OLD.bug <> NEW.bug THEN
+                    PERFORM summarise_bug(bug_row(NEW.bug));
+                END IF;
+            END IF;
+        END IF;
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+-- No longer needed - we have a persistent and replicated journal.
+DROP FUNCTION ensure_bugsummary_temp_journal();
+DROP FUNCTION bug_summary_flush_temp_journal();
+DROP FUNCTION bug_summary_temp_journal_dec(d bugsummary);
+DROP FUNCTION bug_summary_temp_journal_inc(d bugsummary);
+
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 4);

=== added file 'database/schema/patch-2208-75-0.sql'
--- database/schema/patch-2208-75-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-75-0.sql	2011-06-09 16:45:00 +0000
@@ -0,0 +1,11 @@
+-- Copyright 2011 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+-- Cleanup of BugSummary deferred until next downtime window.
+
+ALTER TABLE BugSummary ADD CONSTRAINT bugsummary__viewed_by__fk
+    FOREIGN KEY (viewed_by) REFERENCES Person;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 75, 0);

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-06-09 16:44:56 +0000
+++ database/schema/security.cfg	2011-06-09 16:45:00 +0000
@@ -89,11 +89,7 @@
 public.bugsummary_tags(bug)                =
 public.unsummarise_bug(bug)                =
 public.bugsummary_viewers(bug)             =
-public.bug_summary_temp_journal_inc(bugsummary) =
-public.ensure_bugsummary_temp_journal()    =
-public.bug_summary_flush_temp_journal()    =
-public.bug_summary_temp_journal_dec(bugsummary) =
-public.bug_summary_temp_journal_clean_row(bugsummary) =
+public.bugsummary_journal_ins(bugsummary)  =
 
 [ro]
 groups=read
@@ -142,6 +138,8 @@
 public.bugnotificationrecipient         = SELECT, INSERT, UPDATE, DELETE
 public.bugnotificationrecipientarchive  = SELECT, UPDATE
 public.bugsummary                       = SELECT
+public.bugsummaryjournal                = SELECT
+public.bugsummary_rollup_journal()      = EXECUTE
 public.bugtag                           = SELECT, INSERT, DELETE
 public.bugtrackercomponent              = SELECT, INSERT, UPDATE
 public.bugtrackercomponentgroup         = SELECT, INSERT, UPDATE
@@ -156,6 +154,7 @@
 public.codeimportresult                 = SELECT, INSERT, UPDATE, DELETE
 public.codereviewmessage                = SELECT, INSERT, DELETE
 public.codereviewvote                   = SELECT, INSERT, UPDATE, DELETE
+public.combinedbugsummary               = SELECT
 public.commercialsubscription           = SELECT, INSERT, UPDATE, DELETE
 public.continent                        = SELECT
 public.customlanguagecode               = SELECT, INSERT, UPDATE, DELETE
@@ -2145,6 +2144,7 @@
 public.bugsubscriptionfilterimportance  = SELECT
 public.bugsubscriptionfilterstatus      = SELECT
 public.bugsubscriptionfiltertag         = SELECT
+public.bugsummary_rollup_journal()      = EXECUTE
 public.bugtag                           = SELECT
 public.bugwatch                         = SELECT, UPDATE
 public.bugwatchactivity                 = SELECT, DELETE

=== modified file 'lib/lp/bugs/doc/bugsummary.txt'
--- lib/lp/bugs/doc/bugsummary.txt	2011-06-03 10:44:36 +0000
+++ lib/lp/bugs/doc/bugsummary.txt	2011-06-09 16:45:00 +0000
@@ -44,6 +44,11 @@
     ...     # bugs created in previous transactions.
     ...     store.flush()
     ...     store.invalidate()
+    ...
+    ...     # And rollup the BugSummaryJournal into BugSummary
+    ...     # so all the records are in one place.
+    ...     store.execute("SELECT bugsummary_rollup_journal()")
+    ...
     ...     # Make sure our results are in a consistent order.
     ...     ordered_results = bugsummary_resultset.order_by(
     ...         BugSummary.product_id, BugSummary.productseries_id,

=== modified file 'lib/lp/bugs/model/bugsummary.py'
--- lib/lp/bugs/model/bugsummary.py	2011-05-25 14:14:22 +0000
+++ lib/lp/bugs/model/bugsummary.py	2011-06-09 16:45:00 +0000
@@ -31,7 +31,7 @@
 
     implements(IBugSummary)
 
-    __storm_table__ = 'bugsummary'
+    __storm_table__ = 'combinedbugsummary'
 
     id = Int(primary=True)
     count = Int()

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2011-06-03 10:38:25 +0000
+++ lib/lp/registry/model/person.py	2011-06-09 16:45:00 +0000
@@ -3958,6 +3958,9 @@
             ('votecast', 'person'),
             ('vote', 'person'),
             ('translationrelicensingagreement', 'person'),
+            # These are ON DELETE CASCADE and maintained by triggers.
+            ('bugsummary', 'viewed_by'),
+            ('bugsummaryjournal', 'viewed_by'),
             ]
 
         references = list(postgresql.listReferences(cur, 'person', 'id'))