← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~lifeless/launchpad/bug-794802 into lp:launchpad

 

Robert Collins has proposed merging lp:~lifeless/launchpad/bug-794802 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #794802 in Launchpad itself: "many bug activities timing out due to contention on bugsummary"
  https://bugs.launchpad.net/launchpad/+bug/794802

For more details, see:
https://code.launchpad.net/~lifeless/launchpad/bug-794802/+merge/63972

This should unbreak bug status-changing by changing from a big lock to per-row locks.
-- 
https://code.launchpad.net/~lifeless/launchpad/bug-794802/+merge/63972
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/bug-794802 into lp:launchpad.
=== added file 'database/schema/patch-2208-63-1.sql'
--- database/schema/patch-2208-63-1.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-63-1.sql	2011-06-09 09:37:37 +0000
@@ -0,0 +1,361 @@
+-- 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;
+
+-- bad comment fixup
+COMMENT ON FUNCTION bug_summary_dec(bugsummary) IS
+'UPSERT into bugsummary incrementing one row';
+
+CREATE OR REPLACE FUNCTION ensure_bugsummary_temp_journal() RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+BEGIN
+    CREATE TEMPORARY TABLE bugsummary_temp_journal (
+        LIKE bugsummary ) ON COMMIT DROP;
+    ALTER TABLE bugsummary_temp_journal ALTER COLUMN id DROP NOT NULL;
+    -- For safety use a unique index.
+    CREATE UNIQUE INDEX bugsummary__temp_journal__dimensions__unique ON bugsummary_temp_journal (
+        status,
+        COALESCE(product, (-1)),
+        COALESCE(productseries, (-1)),
+        COALESCE(distribution, (-1)),
+        COALESCE(distroseries, (-1)),
+        COALESCE(sourcepackagename, (-1)),
+        COALESCE(viewed_by, (-1)),
+        COALESCE(milestone, (-1)),
+        COALESCE(tag, ('')));
+EXCEPTION
+    WHEN duplicate_table THEN
+        NULL;
+END;
+$$;
+
+COMMENT ON FUNCTION ensure_bugsummary_temp_journal() IS
+'Create a temporary table bugsummary_temp_journal if it does not exist.';
+
+CREATE OR REPLACE FUNCTION bug_summary_temp_journal_clean_row(d bugsummary) RETURNS VOID
+LANGUAGE plpgsql AS
+$$
+BEGIN
+    -- maybe gc the row (perhaps should be garbo but easy enough to add here:
+    DELETE FROM bugsummary_temp_journal
+    WHERE
+        count=0
+        AND product IS NOT DISTINCT FROM d.product
+        AND productseries IS NOT DISTINCT FROM d.productseries
+        AND distribution IS NOT DISTINCT FROM d.distribution
+        AND distroseries IS NOT DISTINCT FROM d.distroseries
+        AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
+        AND viewed_by IS NOT DISTINCT FROM d.viewed_by
+        AND tag IS NOT DISTINCT FROM d.tag
+        AND status IS NOT DISTINCT FROM d.status
+        AND milestone IS NOT DISTINCT FROM d.milestone;
+END;
+$$;
+
+COMMENT ON FUNCTION bug_summary_temp_journal_clean_row(bugsummary) IS
+'Remove a row from the temp journal if its count is 0';
+
+CREATE OR REPLACE FUNCTION bug_summary_temp_journal_dec(d bugsummary) RETURNS VOID
+LANGUAGE plpgsql AS
+$$
+BEGIN
+    -- We own the row reference, so in the absence of bugs this cannot
+    -- fail - just decrement the row.
+    UPDATE BugSummary_Temp_Journal SET count = count - 1
+    WHERE
+        product IS NOT DISTINCT FROM d.product
+        AND productseries IS NOT DISTINCT FROM d.productseries
+        AND distribution IS NOT DISTINCT FROM d.distribution
+        AND distroseries IS NOT DISTINCT FROM d.distroseries
+        AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
+        AND viewed_by IS NOT DISTINCT FROM d.viewed_by
+        AND tag IS NOT DISTINCT FROM d.tag
+        AND status IS NOT DISTINCT FROM d.status
+        AND milestone IS NOT DISTINCT FROM d.milestone;
+    IF found THEN
+        PERFORM bug_summary_temp_journal_clean_row(d);
+        RETURN;
+    END IF;
+    -- not there, so try to insert the key
+    INSERT INTO BugSummary_Temp_Journal(
+        count, product, productseries, distribution,
+        distroseries, sourcepackagename, viewed_by, tag,
+        status, milestone)
+    VALUES (
+        -1, d.product, d.productseries, d.distribution,
+        d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+        d.status, d.milestone);
+    RETURN;
+END;
+$$;
+
+COMMENT ON FUNCTION bug_summary_temp_journal_dec(bugsummary) IS
+'UPSERT into bugsummary_temp_journal decrementing one row';
+
+CREATE OR REPLACE FUNCTION bug_summary_temp_journal_inc(d bugsummary) RETURNS VOID
+LANGUAGE plpgsql AS
+$$
+BEGIN
+    -- first try to update the row
+    UPDATE BugSummary_Temp_Journal SET count = count + 1
+    WHERE
+        product IS NOT DISTINCT FROM d.product
+        AND productseries IS NOT DISTINCT FROM d.productseries
+        AND distribution IS NOT DISTINCT FROM d.distribution
+        AND distroseries IS NOT DISTINCT FROM d.distroseries
+        AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
+        AND viewed_by IS NOT DISTINCT FROM d.viewed_by
+        AND tag IS NOT DISTINCT FROM d.tag
+        AND status IS NOT DISTINCT FROM d.status
+        AND milestone IS NOT DISTINCT FROM d.milestone;
+    IF found THEN
+        PERFORM bug_summary_temp_journal_clean_row($1);
+        RETURN;
+    END IF;
+    -- not there, so try to insert the key
+    INSERT INTO BugSummary_Temp_Journal(
+        count, product, productseries, distribution,
+        distroseries, sourcepackagename, viewed_by, tag,
+        status, milestone)
+    VALUES (
+        1, d.product, d.productseries, d.distribution,
+        d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+        d.status, d.milestone);
+    RETURN;
+END;
+$$;
+
+COMMENT ON FUNCTION bug_summary_temp_journal_inc(bugsummary) IS
+'UPSERT into bugsummary incrementing one row';
+
+CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    -- may get called even though no summaries were made (for simplicity in the
+    -- callers)
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_temp_journal 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 bugsummary_temp_journal;
+END;
+$$;
+
+COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS
+'flush the temporary bugsummary journal into the bugsummary table';
+
+CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        PERFORM bug_summary_temp_journal_dec(d);
+    END LOOP;
+END;
+$$;
+
+CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
+LANGUAGE plpgsql VOLATILE AS
+$$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        PERFORM bug_summary_temp_journal_inc(d);
+    END LOOP;
+END;
+$$;
+
+-- fixed to summarise less often and use the journal.
+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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+-- fixed to use the journal
+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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+
+    PERFORM bug_summary_flush_temp_journal();
+    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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        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;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 1);

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-06-06 15:19:37 +0000
+++ database/schema/security.cfg	2011-06-09 09:37:37 +0000
@@ -89,6 +89,11 @@
 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) =
 
 [ro]
 groups=read