Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gpqou-0007DH-T3 for pgsql-hackers@arkaria.postgresql.org; Sat, 02 Feb 2019 08:38:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gpqot-000654-Ec for pgsql-hackers@arkaria.postgresql.org; Sat, 02 Feb 2019 08:38:35 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gpqos-00064w-SL for pgsql-hackers@lists.postgresql.org; Sat, 02 Feb 2019 08:38:35 +0000 Received: from mail-oi1-x242.google.com ([2607:f8b0:4864:20::242]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gpqol-0005ZZ-EO for pgsql-hackers@postgresql.org; Sat, 02 Feb 2019 08:38:33 +0000 Received: by mail-oi1-x242.google.com with SMTP id y23so7848854oia.4 for ; Sat, 02 Feb 2019 00:38:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=leadboat.com; s=google; h=date:from:to:subject:message-id:mime-version:content-disposition :user-agent; bh=NOoFvDIcf7D5XxSpHM+X2y+vobJ0Q/y8lio2fTM3ik0=; b=XxDtuXLl+v39Hrmkt32tRB/EATqB+JNjxH16solxEFYw1Q4f4ZlfkfI1RMjv5iExgA hOhSAFdAllwAkMX0pnSiev/JUf2KUEIOXV+xvnW/p/Uk3jBl1DZ50ES628FT0z9AKKlA PQgtKpphaQywXCLKb/tI6dBbeRy0Y65RelEfM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:subject:message-id:mime-version :content-disposition:user-agent; bh=NOoFvDIcf7D5XxSpHM+X2y+vobJ0Q/y8lio2fTM3ik0=; b=h2B1jA+svFjQ5WhYhftTtSXlvRMe3P5hbUDTfYNYlbzQ0al7S0j9odSFx5BsCJ2EOQ LVnq7kolcFgVn0Mh6NAGlO+a+KICMQt3MVN/ciqaJTcftaZ1CytNq7J6BUt8oE162xal 4Wb+twiVoCLNRWTV1uv6hMc9qxIoMDhqeAWn2WDc7MN2CTpmzjKsyiMul3M4cBvroIPB 3CSGNG6a6lixW5DSmhnqffaP/6j+n+P8xQJValolamIV2oMUg8CdCJrwloagFCYo4tYf bznLywlvIBRxHh/Czkoj8YdqSzRqiBY4OjhsxuH69ozC02HwvAzp4xd4JTp8FS+LlEX1 6WrQ== X-Gm-Message-State: AJcUukdOEgsUbTXD0eNk8FeNEC93FVF754MoBO7fe1osG6CapMxPYm2R ag68VKrPe+ATvPmrzZOlwHs67xGs8wY= X-Google-Smtp-Source: ALg8bN5hSi0KMOb8/Sh6/XpdaYWr6MqDZKrMuyj5rfFNc7hEeo6AP4gf7/tswTC3qVK8GJwOt1xanQ== X-Received: by 2002:aca:e5cb:: with SMTP id c194mr20181502oih.175.1549096705956; Sat, 02 Feb 2019 00:38:25 -0800 (PST) Received: from gust.leadboat.com (108-233-125-46.lightspeed.sntcca.sbcglobal.net. [108.233.125.46]) by smtp.gmail.com with ESMTPSA id h16sm3862996otl.29.2019.02.02.00.38.24 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 02 Feb 2019 00:38:25 -0800 (PST) Date: Sat, 2 Feb 2019 03:38:22 -0500 From: Noah Misch To: pgsql-hackers@postgresql.org Subject: Spurious "apparent wraparound" via SimpleLruTruncate() rounding Message-ID: <20190202083822.GC32531@gust.leadboat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="W/nzBZO5zC0uMSeA" Content-Disposition: inline User-Agent: Mutt/1.5.24 (2015-08-30) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --W/nzBZO5zC0uMSeA Content-Type: text/plain; charset=us-ascii Content-Disposition: inline While testing an xidStopLimit corner case, I got this: 3656710 2019-01-05 00:05:13.910 GMT LOG: automatic aggressive vacuum to prevent wraparound of table "test.pg_toast.pg_toast_826": index scans: 0 3656710 2019-01-05 00:05:16.912 GMT LOG: could not truncate directory "pg_xact": apparent wraparound 3656710 2019-01-05 00:05:16.912 GMT DEBUG: transaction ID wrap limit is 4294486400, limited by database with OID 1 3656710 2019-01-05 00:05:16.912 GMT WARNING: database "template1" must be vacuumed within 481499 transactions 3656710 2019-01-05 00:05:16.912 GMT HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database. I think the WARNING was correct about having 481499 XIDs left before xidWrapLimit, and the spurious "apparent wraparound" arose from this rounding-down in SimpleLruTruncate(): cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT; ... /* * While we are holding the lock, make an important safety check: the * planned cutoff point must be <= the current endpoint page. Otherwise we * have already wrapped around, and proceeding with the truncation would * risk removing the current segment. */ if (ctl->PagePrecedes(shared->latest_page_number, cutoffPage)) { LWLockRelease(shared->ControlLock); ereport(LOG, (errmsg("could not truncate directory \"%s\": apparent wraparound", ctl->Dir))); We round "cutoffPage" to make ctl->PagePrecedes(segpage, cutoffPage) return false for the segment containing the cutoff page. CLOGPagePrecedes() (and most SLRU PagePrecedes methods) implements a circular address space. Hence, the rounding also causes ctl->PagePrecedes(segpage, cutoffPage) to return true for the segment furthest in the future relative to the unrounded cutoffPage (if it exists). That's bad. Such a segment rarely exists, because xidStopLimit protects 1000000 XIDs, and the rounding moves truncation by no more than (BLCKSZ * CLOG_XACTS_PER_BYTE * SLRU_PAGES_PER_SEGMENT - 1) = 1048575 XIDs. Thus, I expect to see this problem at 4.9% of xidStopLimit values. I expect this is easier to see with multiStopLimit, which protects only 100 mxid. The main consequence is the false alarm. A prudent DBA will want to react to true wraparound, but no such wraparound has occurred. Also, we temporarily waste disk space in pg_xact. This feels like a recipe for future bugs. The fix I have in mind, attached, is to change instances of ctl->PagePrecedes(FIRST_PAGE_OF_SEGMENT, ROUNDED_cutoffPage) to ctl->PagePrecedes(LAST_PAGE_OF_SEGMENT, cutoffPage). I'm inclined not to back-patch this; does anyone favor back-patching? Thanks, nm --W/nzBZO5zC0uMSeA Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="slru-truncate-modulo-v1.patch" diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c index 3623352..843486a 100644 --- a/src/backend/access/transam/slru.c +++ b/src/backend/access/transam/slru.c @@ -1171,11 +1171,6 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage) int slotno; /* - * The cutoff point is the start of the segment containing cutoffPage. - */ - cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT; - - /* * Scan shared memory and remove any pages preceding the cutoff page, to * ensure we won't rewrite them later. (Since this is normally called in * or just after a checkpoint, any dirty pages should have been flushed @@ -1320,11 +1315,10 @@ restart: bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data) { + int seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1; int cutoffPage = *(int *) data; - cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT; - - if (ctl->PagePrecedes(segpage, cutoffPage)) + if (ctl->PagePrecedes(seg_last_page, cutoffPage)) return true; /* found one; don't iterate any more */ return false; /* keep going */ @@ -1337,9 +1331,10 @@ SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data) { + int seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1; int cutoffPage = *(int *) data; - if (ctl->PagePrecedes(segpage, cutoffPage)) + if (ctl->PagePrecedes(seg_last_page, cutoffPage)) SlruInternalDeleteSegment(ctl, filename); return false; /* keep going */ --W/nzBZO5zC0uMSeA--