Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1slcxn-000GhE-CS for pgsql-hackers@arkaria.postgresql.org; Tue, 03 Sep 2024 23:29:31 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1slcwn-001FxT-D5 for pgsql-hackers@arkaria.postgresql.org; Tue, 03 Sep 2024 23:28:29 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1slcwn-001FwQ-0D for pgsql-hackers@lists.postgresql.org; Tue, 03 Sep 2024 23:28:29 +0000 Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1slcwk-0001nO-IJ for pgsql-hackers@postgresql.org; Tue, 03 Sep 2024 23:28:28 +0000 Received: by mail-wm1-x32c.google.com with SMTP id 5b1f17b1804b1-42bbdf7f860so40327725e9.3 for ; Tue, 03 Sep 2024 16:28:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725406105; x=1726010905; darn=postgresql.org; h=in-reply-to:from:content-language:references:cc:to:subject :user-agent:mime-version:date:message-id:from:to:cc:subject:date :message-id:reply-to; bh=IXv24/o++ukXDTaq8ihyIXOY04+wu+0oX9+g+wVFefE=; b=PxMTXzkvyYBmOO5rBM7lFfO50v7LUZ1b+P4OxqAay1buqSAhMyi3rRiJBC4AwbECXa LF+7/iKhTgmoPZQe+ut+Kp+Jw9Gg10YSpBBRB2L85TC3wWeqV1WIA8xEJdEvoUTUKFwy aTjfHy8XLDj1fJxijdUjvfvDAOUzboLCBejGAFlUCistxXrR4PPgHADg9yov8tnPvcAf fQnNY4vomPaovV8oCx3ii6nt2NIOEFNzurGmSEsUWFsxR5eEB2RR41HDP7AeMunxJfPt KJT+9JZqDAdR7dGvrU574udhWyKZTOr70Q+usUlvv0jJLB53WVsrt/sdAzdsxD2j3yh1 16ag== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725406105; x=1726010905; h=in-reply-to:from:content-language:references:cc:to:subject :user-agent:mime-version:date:message-id:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=IXv24/o++ukXDTaq8ihyIXOY04+wu+0oX9+g+wVFefE=; b=BBdeuGt4OoBqZ4dXRIvyh9Rl5JnZJOhInTZsxXZMTU+DzGMLdUJwsvHyL0Ib91nvhk DBEu9IoB6ivjk1+paXEanA1Tm1Py6RjCnu+D4YlT6ZNde2s6MAI2EPTE5CwG/RVw2azX vavJ0t/9BHskeJnX/j/4q+EJQmy2jTMG90aTVy7dR/j/ui7B8TmTvktHO/WILaTwxugM KTz25HxJDuVlhYylldlCiCJrer6jicSBgJZOrymmiEQJdRa2n9LuqmN7H4eoUCav0KTU aW613ZC5y+GrdGPueFRfBKU+pNVzlfXXwLu5rNimkRklkcXdTs3EH8NrNVgVPjVUzp4b zqxw== X-Forwarded-Encrypted: i=1; AJvYcCUGyfI7BfrOw7mGUiveAFFWlw8WyEJpL0LlNk9qgZE6QwZMsx1lnwcxP7vTvs/9utx3OUvXd4Ry0h0eMY5x@postgresql.org X-Gm-Message-State: AOJu0YxFydS+QOMCOvaJ9Xo6VU9RLEYeqBwj7cr3/mNi0geMeh6pQqAh 6lXpbU/RwLeF9DdHql1cerv/yoA/+IJ+6wf3LufpBPkt9ppHZ5BuJLz55jWZ X-Google-Smtp-Source: AGHT+IGUcJW/IOrr5aEV8Gx+KuUtvCJUJsBNg3uKLEvq+PQafmeTs/QrkVriROyEjszHNjJKxP9L2Q== X-Received: by 2002:a05:600c:310b:b0:426:627d:5542 with SMTP id 5b1f17b1804b1-42bb01e6c05mr138300945e9.28.1725406104502; Tue, 03 Sep 2024 16:28:24 -0700 (PDT) Received: from ?IPV6:2a01:4b00:c00c:a500:5fda:93a6:e563:2421? ([2a01:4b00:c00c:a500:5fda:93a6:e563:2421]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42bb6df0a4dsm183664265e9.12.2024.09.03.16.28.23 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 03 Sep 2024 16:28:24 -0700 (PDT) Content-Type: multipart/alternative; boundary="------------00A7Q47w0GRtTfWpyJEpMMx7" Message-ID: <0fe740dd-cd23-45a6-b116-c777349acbf1@gmail.com> Date: Wed, 4 Sep 2024 00:28:23 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: optimizing pg_upgrade's once-in-each-database steps To: Nathan Bossart Cc: Corey Huinker , Daniel Gustafsson , Robert Haas , Jeff Davis , PostgreSQL Hackers References: <60cbc507-4c36-4037-9af7-028b1c8455b5@gmail.com> <3A3553B1-123D-4F11-9426-277F2E920AD7@gmail.com> Content-Language: en-US From: Ilya Gladyshev In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --------------00A7Q47w0GRtTfWpyJEpMMx7 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 01.09.2024 22:05, Nathan Bossart wrote: > I think we can actually just use PQstatus() here. But furthermore, I think > the way I was initiating connections was completely bogus. IIUC before > calling PQconnectPoll() the first time, we should wait for a write > indicator from select(), and then we should only call PQconnectPoll() after > subsequent indicators from select(). After spending quite a bit of time > staring at the PQconnectPoll() code, I'm quite surprised I haven't seen any > problems thus far. If I had to guess, I'd say that either PQconnectPoll() > is more resilient than I think it is, or I've just gotten lucky because > pg_upgrade establishes connections quickly. Good catch, I didn't look so closely at this. > Anyway, to fix this, I've added some more fields to the slot struct to > keep track of the information we need to properly establish connections, > and we now pay careful attention to the return value of select() so that we > know which slots are ready for processing. This seemed like a nice little > optimization independent of fixing connection establishment. I was worried > this was going to require a lot more code, but I think it only added ~50 > lines or so. The fix looks right to me, but I got confused by the skip_wait and this `if`: +            if (PQstatus(slot->conn) != CONNECTION_OK) +                return; This branch checks connection status that hasn't been refreshed after the select. When we go back to wait_slots after this, PQconnectPoll will refresh the connection status and run select with skip_wait=true, I believe, we could simplify this by moving the PQconnectPoll back to the process_slots, so that we can process connection right after polling, if it's ready. Something like this: diff --git a/src/bin/pg_upgrade/task.c b/src/bin/pg_upgrade/task.c index 3618dc08ff..73e987febf 100644 --- a/src/bin/pg_upgrade/task.c +++ b/src/bin/pg_upgrade/task.c @@ -237,6 +237,8 @@ process_query_result(const ClusterInfo *cluster, UpgradeTaskSlot *slot,  static void  process_slot(const ClusterInfo *cluster, UpgradeTaskSlot *slot, const UpgradeTask *task)  { +    PostgresPollingStatusType status; +      if (!slot->ready)          return; @@ -260,26 +262,26 @@ process_slot(const ClusterInfo *cluster, UpgradeTaskSlot *slot, const UpgradeTas              start_conn(cluster, slot);              return; -          case CONNECTING: -            /* Check for connection failure. */ -            if (PQstatus(slot->conn) == CONNECTION_BAD) -                pg_fatal("connection failure: %s", PQerrorMessage(slot->conn)); - -            /* Check whether the connection is still establishing. */ -            if (PQstatus(slot->conn) != CONNECTION_OK) -                return; - -            /* -             * Move on to running/processing the queries in the task. -             */ -            slot->state = RUNNING_QUERIES; -            if (!PQsendQuery(slot->conn, task->queries->data)) +            status = PQconnectPoll(slot->conn); +            if (status == PGRES_POLLING_READING) +                slot->select_mode = true; +            else if (status == PGRES_POLLING_WRITING) +                slot->select_mode = false; +            else if (status == PGRES_POLLING_FAILED)                  pg_fatal("connection failure: %s", PQerrorMessage(slot->conn)); +            else +            { +                /* +                 * Move on to running/processing the queries in the task. +                 */ +                slot->state = RUNNING_QUERIES; +                if (!PQsendQuery(slot->conn, task->queries->data)) +                    pg_fatal("connection failure: %s", PQerrorMessage(slot->conn)); +            }              return; -          case RUNNING_QUERIES:              /* @@ -370,8 +372,6 @@ wait_on_slots(UpgradeTaskSlot *slots, int numslots)      for (int i = 0; i < numslots; i++)      { -        PostgresPollingStatusType status; -          switch (slots[i].state)          {              case FREE: @@ -386,33 +386,7 @@ wait_on_slots(UpgradeTaskSlot *slots, int numslots)                  continue;              case CONNECTING: - -                /* -                 * Don't call PQconnectPoll() again for this slot until -                 * select() tells us something is ready.  Be sure to use the -                 * previous poll mode in this case. -                 */ -                if (!slots[i].ready) -                    break; - -                /* -                 * If we are waiting for the connection to establish, choose -                 * whether to wait for reading or for writing on the socket as -                 * appropriate.  If neither apply, mark the slot as ready and -                 * skip waiting so that it is handled ASAP (we assume this -                 * means the connection is either bad or fully ready). -                 */ -                status = PQconnectPoll(slots[i].conn); -                if (status == PGRES_POLLING_READING) -                    slots[i].select_mode = true; -                else if (status == PGRES_POLLING_WRITING) -                    slots[i].select_mode = false; -                else -                { -                    slots[i].ready = true; -                    skip_wait = true; -                    continue; -                } +                /* All the slot metadata was already setup in process_slots() */                  break; -- 2.43.0 skip_wait can be removed in this case as well. This is up to you, I think the v12 is good and commitable in any case. --------------00A7Q47w0GRtTfWpyJEpMMx7 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit


On 01.09.2024 22:05, Nathan Bossart wrote:
I think we can actually just use PQstatus() here.  But furthermore, I think
the way I was initiating connections was completely bogus.  IIUC before
calling PQconnectPoll() the first time, we should wait for a write
indicator from select(), and then we should only call PQconnectPoll() after
subsequent indicators from select().  After spending quite a bit of time
staring at the PQconnectPoll() code, I'm quite surprised I haven't seen any
problems thus far.  If I had to guess, I'd say that either PQconnectPoll()
is more resilient than I think it is, or I've just gotten lucky because
pg_upgrade establishes connections quickly.
Good catch, I didn't look so closely at this.
Anyway, to fix this, I've added some more fields to the slot struct to
keep track of the information we need to properly establish connections,
and we now pay careful attention to the return value of select() so that we
know which slots are ready for processing.  This seemed like a nice little
optimization independent of fixing connection establishment.  I was worried
this was going to require a lot more code, but I think it only added ~50
lines or so.
The fix looks right to me, but I got confused by the skip_wait and this `if`:

+            if (PQstatus(slot->conn) != CONNECTION_OK)
+                return;

This branch checks connection status that hasn't been refreshed after the select. When we go back to wait_slots after this, PQconnectPoll will refresh the connection status and run select with skip_wait=true, I believe, we could simplify this by moving the PQconnectPoll back to the process_slots, so that we can process connection right after polling, if it's ready. Something like this:

diff --git a/src/bin/pg_upgrade/task.c b/src/bin/pg_upgrade/task.c
index 3618dc08ff..73e987febf 100644
--- a/src/bin/pg_upgrade/task.c
+++ b/src/bin/pg_upgrade/task.c
@@ -237,6 +237,8 @@ process_query_result(const ClusterInfo *cluster, UpgradeTaskSlot *slot,
 static void
 process_slot(const ClusterInfo *cluster, UpgradeTaskSlot *slot, const UpgradeTask *task)
 {
+    PostgresPollingStatusType status;
+
     if (!slot->ready)
         return;
 
@@ -260,26 +262,26 @@ process_slot(const ClusterInfo *cluster, UpgradeTaskSlot *slot, const UpgradeTas
             start_conn(cluster, slot);
 
             return;
-
         case CONNECTING:
 
-            /* Check for connection failure. */
-            if (PQstatus(slot->conn) == CONNECTION_BAD)
-                pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
-
-            /* Check whether the connection is still establishing. */
-            if (PQstatus(slot->conn) != CONNECTION_OK)
-                return;
-
-            /*
-             * Move on to running/processing the queries in the task.
-             */
-            slot->state = RUNNING_QUERIES;
-            if (!PQsendQuery(slot->conn, task->queries->data))
+            status = PQconnectPoll(slot->conn);
+            if (status == PGRES_POLLING_READING)
+                slot->select_mode = true;
+            else if (status == PGRES_POLLING_WRITING)
+                slot->select_mode = false;
+            else if (status == PGRES_POLLING_FAILED)
                 pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
+            else
+            {
+                /*
+                 * Move on to running/processing the queries in the task.
+                 */
+                slot->state = RUNNING_QUERIES;
+                if (!PQsendQuery(slot->conn, task->queries->data))
+                    pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
 
+            }
             return;
-
         case RUNNING_QUERIES:
 
             /*
@@ -370,8 +372,6 @@ wait_on_slots(UpgradeTaskSlot *slots, int numslots)
 
     for (int i = 0; i < numslots; i++)
     {
-        PostgresPollingStatusType status;
-
         switch (slots[i].state)
         {
             case FREE:
@@ -386,33 +386,7 @@ wait_on_slots(UpgradeTaskSlot *slots, int numslots)
                 continue;
 
             case CONNECTING:
-
-                /*
-                 * Don't call PQconnectPoll() again for this slot until
-                 * select() tells us something is ready.  Be sure to use the
-                 * previous poll mode in this case.
-                 */
-                if (!slots[i].ready)
-                    break;
-
-                /*
-                 * If we are waiting for the connection to establish, choose
-                 * whether to wait for reading or for writing on the socket as
-                 * appropriate.  If neither apply, mark the slot as ready and
-                 * skip waiting so that it is handled ASAP (we assume this
-                 * means the connection is either bad or fully ready).
-                 */
-                status = PQconnectPoll(slots[i].conn);
-                if (status == PGRES_POLLING_READING)
-                    slots[i].select_mode = true;
-                else if (status == PGRES_POLLING_WRITING)
-                    slots[i].select_mode = false;
-                else
-                {
-                    slots[i].ready = true;
-                    skip_wait = true;
-                    continue;
-                }
+                /* All the slot metadata was already setup in process_slots() */
 
                 break;
 
--
2.43.0

skip_wait can be removed in this case as well.

This is up to you, I think the v12 is good and commitable in any case.

--------------00A7Q47w0GRtTfWpyJEpMMx7--