mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Introduce framework for parallelizing various pg_upgrade tasks.
A number of pg_upgrade steps require connecting to every database in the cluster and running the same query in each one. When there are many databases, these steps are particularly time-consuming, especially since they are performed sequentially, i.e., we connect to a database, run the query, and process the results before moving on to the next database. This commit introduces a new framework that makes it easy to parallelize most of these once-in-each-database tasks by processing multiple databases concurrently. This framework manages a set of slots that follow a simple state machine, and it uses libpq's asynchronous APIs to establish the connections and run the queries. The --jobs option is used to determine the number of slots to use. To use this new task framework, callers simply need to provide the query and a callback function to process its results, and the framework takes care of the rest. A more complete description is provided at the top of the new task.c file. None of the eligible once-in-each-database tasks are converted to use this new framework in this commit. That will be done via several follow-up commits. Reviewed-by: Jeff Davis, Robert Haas, Daniel Gustafsson, Ilya Gladyshev, Corey Huinker Discussion: https://postgr.es/m/20240516211638.GA1688936%40nathanxps13
This commit is contained in:
parent
d891c49286
commit
40e2e5e92b
@ -118,7 +118,7 @@ PostgreSQL documentation
|
|||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>-j <replaceable class="parameter">njobs</replaceable></option></term>
|
<term><option>-j <replaceable class="parameter">njobs</replaceable></option></term>
|
||||||
<term><option>--jobs=<replaceable class="parameter">njobs</replaceable></option></term>
|
<term><option>--jobs=<replaceable class="parameter">njobs</replaceable></option></term>
|
||||||
<listitem><para>number of simultaneous processes or threads to use
|
<listitem><para>number of simultaneous connections and processes/threads to use
|
||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
@ -587,8 +587,8 @@ NET STOP postgresql-&majorversion;
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
The <option>--jobs</option> option allows multiple CPU cores to be used
|
The <option>--jobs</option> option allows multiple CPU cores to be used
|
||||||
for copying/linking of files and to dump and restore database schemas
|
for copying/linking of files, dumping and restoring database schemas
|
||||||
in parallel; a good place to start is the maximum of the number of
|
in parallel, etc.; a good place to start is the maximum of the number of
|
||||||
CPU cores and tablespaces. This option can dramatically reduce the
|
CPU cores and tablespaces. This option can dramatically reduce the
|
||||||
time to upgrade a multi-database server running on a multiprocessor
|
time to upgrade a multi-database server running on a multiprocessor
|
||||||
machine.
|
machine.
|
||||||
|
@ -25,6 +25,7 @@ OBJS = \
|
|||||||
relfilenumber.o \
|
relfilenumber.o \
|
||||||
server.o \
|
server.o \
|
||||||
tablespace.o \
|
tablespace.o \
|
||||||
|
task.o \
|
||||||
util.o \
|
util.o \
|
||||||
version.o
|
version.o
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ pg_upgrade_sources = files(
|
|||||||
'relfilenumber.c',
|
'relfilenumber.c',
|
||||||
'server.c',
|
'server.c',
|
||||||
'tablespace.c',
|
'tablespace.c',
|
||||||
|
'task.c',
|
||||||
'util.c',
|
'util.c',
|
||||||
'version.c',
|
'version.c',
|
||||||
)
|
)
|
||||||
|
@ -494,3 +494,24 @@ void parallel_transfer_all_new_dbs(DbInfoArr *old_db_arr, DbInfoArr *new_db_arr
|
|||||||
char *old_pgdata, char *new_pgdata,
|
char *old_pgdata, char *new_pgdata,
|
||||||
char *old_tablespace);
|
char *old_tablespace);
|
||||||
bool reap_child(bool wait_for_child);
|
bool reap_child(bool wait_for_child);
|
||||||
|
|
||||||
|
/* task.c */
|
||||||
|
|
||||||
|
typedef void (*UpgradeTaskProcessCB) (DbInfo *dbinfo, PGresult *res, void *arg);
|
||||||
|
|
||||||
|
/* struct definition is private to task.c */
|
||||||
|
typedef struct UpgradeTask UpgradeTask;
|
||||||
|
|
||||||
|
UpgradeTask *upgrade_task_create(void);
|
||||||
|
void upgrade_task_add_step(UpgradeTask *task, const char *query,
|
||||||
|
UpgradeTaskProcessCB process_cb, bool free_result,
|
||||||
|
void *arg);
|
||||||
|
void upgrade_task_run(const UpgradeTask *task, const ClusterInfo *cluster);
|
||||||
|
void upgrade_task_free(UpgradeTask *task);
|
||||||
|
|
||||||
|
/* convenient type for common private data needed by several tasks */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
FILE *file;
|
||||||
|
char path[MAXPGPATH];
|
||||||
|
} UpgradeTaskReport;
|
||||||
|
443
src/bin/pg_upgrade/task.c
Normal file
443
src/bin/pg_upgrade/task.c
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* task.c
|
||||||
|
* framework for parallelizing pg_upgrade's once-in-each-database tasks
|
||||||
|
*
|
||||||
|
* This framework provides an efficient way of running the various
|
||||||
|
* once-in-each-database tasks required by pg_upgrade. Specifically, it
|
||||||
|
* parallelizes these tasks by managing a set of slots that follow a simple
|
||||||
|
* state machine and by using libpq's asynchronous APIs to establish the
|
||||||
|
* connections and run the queries. Callers simply need to create a callback
|
||||||
|
* function and build/execute an UpgradeTask. A simple example follows:
|
||||||
|
*
|
||||||
|
* static void
|
||||||
|
* my_process_cb(DbInfo *dbinfo, PGresult *res, void *arg)
|
||||||
|
* {
|
||||||
|
* for (int i = 0; i < PQntuples(res); i++)
|
||||||
|
* {
|
||||||
|
* ... process results ...
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void
|
||||||
|
* my_task(ClusterInfo *cluster)
|
||||||
|
* {
|
||||||
|
* UpgradeTask *task = upgrade_task_create();
|
||||||
|
*
|
||||||
|
* upgrade_task_add_step(task,
|
||||||
|
* "... query text ...",
|
||||||
|
* my_process_cb,
|
||||||
|
* true, // let the task free the PGresult
|
||||||
|
* NULL); // "arg" pointer for callback
|
||||||
|
* upgrade_task_run(task, cluster);
|
||||||
|
* upgrade_task_free(task);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Note that multiple steps can be added to a given task. When there are
|
||||||
|
* multiple steps, the task will run all of the steps consecutively in the same
|
||||||
|
* database connection before freeing the connection and moving on. In other
|
||||||
|
* words, it only ever initiates one connection to each database in the
|
||||||
|
* cluster for a given run.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024, PostgreSQL Global Development Group
|
||||||
|
* src/bin/pg_upgrade/task.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres_fe.h"
|
||||||
|
|
||||||
|
#include "common/connect.h"
|
||||||
|
#include "fe_utils/string_utils.h"
|
||||||
|
#include "pg_upgrade.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dbs_complete stores the number of databases that we have completed
|
||||||
|
* processing. When this value equals the number of databases in the cluster,
|
||||||
|
* the task is finished.
|
||||||
|
*/
|
||||||
|
static int dbs_complete;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dbs_processing stores the index of the next database in the cluster's array
|
||||||
|
* of databases that will be picked up for processing. It will always be
|
||||||
|
* greater than or equal to dbs_complete.
|
||||||
|
*/
|
||||||
|
static int dbs_processing;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct stores the information for a single step of a task. Note that
|
||||||
|
* the query string is stored in the "queries" PQExpBuffer for the UpgradeTask.
|
||||||
|
* All steps in a task are run in a single connection before moving on to the
|
||||||
|
* next database (which requires a new connection).
|
||||||
|
*/
|
||||||
|
typedef struct UpgradeTaskStep
|
||||||
|
{
|
||||||
|
UpgradeTaskProcessCB process_cb; /* processes the results of the query */
|
||||||
|
bool free_result; /* should we free the result? */
|
||||||
|
void *arg; /* pointer passed to process_cb */
|
||||||
|
} UpgradeTaskStep;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct is a thin wrapper around an array of steps, i.e.,
|
||||||
|
* UpgradeTaskStep, plus a PQExpBuffer for all the query strings.
|
||||||
|
*/
|
||||||
|
typedef struct UpgradeTask
|
||||||
|
{
|
||||||
|
UpgradeTaskStep *steps;
|
||||||
|
int num_steps;
|
||||||
|
PQExpBuffer queries;
|
||||||
|
} UpgradeTask;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The different states for a parallel slot.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
FREE, /* slot available for use in a new database */
|
||||||
|
CONNECTING, /* waiting for connection to be established */
|
||||||
|
RUNNING_QUERIES, /* running/processing queries in the task */
|
||||||
|
} UpgradeTaskSlotState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We maintain an array of user_opts.jobs slots to execute the task.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
UpgradeTaskSlotState state; /* state of the slot */
|
||||||
|
int db_idx; /* index of the database assigned to slot */
|
||||||
|
int step_idx; /* index of the current step of task */
|
||||||
|
PGconn *conn; /* current connection managed by slot */
|
||||||
|
bool ready; /* slot is ready for processing */
|
||||||
|
bool select_mode; /* select() mode: true->read, false->write */
|
||||||
|
int sock; /* file descriptor for connection's socket */
|
||||||
|
} UpgradeTaskSlot;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes an UpgradeTask.
|
||||||
|
*/
|
||||||
|
UpgradeTask *
|
||||||
|
upgrade_task_create(void)
|
||||||
|
{
|
||||||
|
UpgradeTask *task = pg_malloc0(sizeof(UpgradeTask));
|
||||||
|
|
||||||
|
task->queries = createPQExpBuffer();
|
||||||
|
|
||||||
|
/* All tasks must first set a secure search_path. */
|
||||||
|
upgrade_task_add_step(task, ALWAYS_SECURE_SEARCH_PATH_SQL, NULL, true, NULL);
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Frees all storage associated with an UpgradeTask.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
upgrade_task_free(UpgradeTask *task)
|
||||||
|
{
|
||||||
|
destroyPQExpBuffer(task->queries);
|
||||||
|
pg_free(task->steps);
|
||||||
|
pg_free(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adds a step to an UpgradeTask. The steps will be executed in each database
|
||||||
|
* in the order in which they are added.
|
||||||
|
*
|
||||||
|
* task: task object that must have been initialized via upgrade_task_create()
|
||||||
|
* query: the query text
|
||||||
|
* process_cb: function that processes the results of the query
|
||||||
|
* free_result: should we free the PGresult, or leave it to the caller?
|
||||||
|
* arg: pointer to task-specific data that is passed to each callback
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
upgrade_task_add_step(UpgradeTask *task, const char *query,
|
||||||
|
UpgradeTaskProcessCB process_cb, bool free_result,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
UpgradeTaskStep *new_step;
|
||||||
|
|
||||||
|
task->steps = pg_realloc(task->steps,
|
||||||
|
++task->num_steps * sizeof(UpgradeTaskStep));
|
||||||
|
|
||||||
|
new_step = &task->steps[task->num_steps - 1];
|
||||||
|
new_step->process_cb = process_cb;
|
||||||
|
new_step->free_result = free_result;
|
||||||
|
new_step->arg = arg;
|
||||||
|
|
||||||
|
appendPQExpBuffer(task->queries, "%s;", query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build a connection string for the slot's current database and asynchronously
|
||||||
|
* start a new connection, but do not wait for the connection to be
|
||||||
|
* established.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
start_conn(const ClusterInfo *cluster, UpgradeTaskSlot *slot)
|
||||||
|
{
|
||||||
|
PQExpBufferData conn_opts;
|
||||||
|
DbInfo *dbinfo = &cluster->dbarr.dbs[slot->db_idx];
|
||||||
|
|
||||||
|
/* Build connection string with proper quoting */
|
||||||
|
initPQExpBuffer(&conn_opts);
|
||||||
|
appendPQExpBufferStr(&conn_opts, "dbname=");
|
||||||
|
appendConnStrVal(&conn_opts, dbinfo->db_name);
|
||||||
|
appendPQExpBufferStr(&conn_opts, " user=");
|
||||||
|
appendConnStrVal(&conn_opts, os_info.user);
|
||||||
|
appendPQExpBuffer(&conn_opts, " port=%d", cluster->port);
|
||||||
|
if (cluster->sockdir)
|
||||||
|
{
|
||||||
|
appendPQExpBufferStr(&conn_opts, " host=");
|
||||||
|
appendConnStrVal(&conn_opts, cluster->sockdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
slot->conn = PQconnectStart(conn_opts.data);
|
||||||
|
|
||||||
|
if (!slot->conn)
|
||||||
|
pg_fatal("failed to create connection with connection string: \"%s\"",
|
||||||
|
conn_opts.data);
|
||||||
|
|
||||||
|
termPQExpBuffer(&conn_opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run the process_cb callback function to process the result of a query, and
|
||||||
|
* free the result if the caller indicated we should do so.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
process_query_result(const ClusterInfo *cluster, UpgradeTaskSlot *slot,
|
||||||
|
const UpgradeTask *task)
|
||||||
|
{
|
||||||
|
UpgradeTaskStep *steps = &task->steps[slot->step_idx];
|
||||||
|
UpgradeTaskProcessCB process_cb = steps->process_cb;
|
||||||
|
DbInfo *dbinfo = &cluster->dbarr.dbs[slot->db_idx];
|
||||||
|
PGresult *res = PQgetResult(slot->conn);
|
||||||
|
|
||||||
|
if (PQstatus(slot->conn) == CONNECTION_BAD ||
|
||||||
|
(PQresultStatus(res) != PGRES_TUPLES_OK &&
|
||||||
|
PQresultStatus(res) != PGRES_COMMAND_OK))
|
||||||
|
pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We assume that a NULL process_cb callback function means there's
|
||||||
|
* nothing to process. This is primarily intended for the initial step in
|
||||||
|
* every task that sets a safe search_path.
|
||||||
|
*/
|
||||||
|
if (process_cb)
|
||||||
|
(*process_cb) (dbinfo, res, steps->arg);
|
||||||
|
|
||||||
|
if (steps->free_result)
|
||||||
|
PQclear(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Advances the state machine for a given slot as necessary.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
process_slot(const ClusterInfo *cluster, UpgradeTaskSlot *slot, const UpgradeTask *task)
|
||||||
|
{
|
||||||
|
PostgresPollingStatusType status;
|
||||||
|
|
||||||
|
if (!slot->ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (slot->state)
|
||||||
|
{
|
||||||
|
case FREE:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If all of the databases in the cluster have been processed or
|
||||||
|
* are currently being processed by other slots, we are done.
|
||||||
|
*/
|
||||||
|
if (dbs_processing >= cluster->dbarr.ndbs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Claim the next database in the cluster's array and initiate a
|
||||||
|
* new connection.
|
||||||
|
*/
|
||||||
|
slot->db_idx = dbs_processing++;
|
||||||
|
slot->state = CONNECTING;
|
||||||
|
start_conn(cluster, slot);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CONNECTING:
|
||||||
|
|
||||||
|
/* Check for connection failure. */
|
||||||
|
status = PQconnectPoll(slot->conn);
|
||||||
|
if (status == PGRES_POLLING_FAILED)
|
||||||
|
pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
|
||||||
|
|
||||||
|
/* Check whether the connection is still establishing. */
|
||||||
|
if (status != PGRES_POLLING_OK)
|
||||||
|
{
|
||||||
|
slot->select_mode = (status == PGRES_POLLING_READING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move on to running/processing the queries in the task.
|
||||||
|
*/
|
||||||
|
slot->state = RUNNING_QUERIES;
|
||||||
|
slot->select_mode = true; /* wait until ready for reading */
|
||||||
|
if (!PQsendQuery(slot->conn, task->queries->data))
|
||||||
|
pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
case RUNNING_QUERIES:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Consume any available data and clear the read-ready indicator
|
||||||
|
* for the connection.
|
||||||
|
*/
|
||||||
|
if (!PQconsumeInput(slot->conn))
|
||||||
|
pg_fatal("connection failure: %s", PQerrorMessage(slot->conn));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process any results that are ready so that we can free up this
|
||||||
|
* slot for another database as soon as possible.
|
||||||
|
*/
|
||||||
|
for (; slot->step_idx < task->num_steps; slot->step_idx++)
|
||||||
|
{
|
||||||
|
/* If no more results are available yet, move on. */
|
||||||
|
if (PQisBusy(slot->conn))
|
||||||
|
return;
|
||||||
|
|
||||||
|
process_query_result(cluster, slot, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we just finished processing the result of the last step in
|
||||||
|
* the task, free the slot. We recursively call this function on
|
||||||
|
* the newly-freed slot so that we can start initiating the next
|
||||||
|
* connection immediately instead of waiting for the next loop
|
||||||
|
* through the slots.
|
||||||
|
*/
|
||||||
|
dbs_complete++;
|
||||||
|
PQfinish(slot->conn);
|
||||||
|
memset(slot, 0, sizeof(UpgradeTaskSlot));
|
||||||
|
slot->ready = true;
|
||||||
|
|
||||||
|
process_slot(cluster, slot, task);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns -1 on error, else the number of ready descriptors.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
select_loop(int maxFd, fd_set *input, fd_set *output)
|
||||||
|
{
|
||||||
|
fd_set save_input = *input;
|
||||||
|
fd_set save_output = *output;
|
||||||
|
|
||||||
|
if (maxFd == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
*input = save_input;
|
||||||
|
*output = save_output;
|
||||||
|
|
||||||
|
i = select(maxFd + 1, input, output, NULL, NULL);
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
if (i < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
#else
|
||||||
|
if (i == SOCKET_ERROR && WSAGetLastError() == WSAEINTR)
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait on the slots to either finish connecting or to receive query results if
|
||||||
|
* possible. This avoids a tight loop in upgrade_task_run().
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
wait_on_slots(UpgradeTaskSlot *slots, int numslots)
|
||||||
|
{
|
||||||
|
fd_set input;
|
||||||
|
fd_set output;
|
||||||
|
int maxFd = 0;
|
||||||
|
|
||||||
|
FD_ZERO(&input);
|
||||||
|
FD_ZERO(&output);
|
||||||
|
|
||||||
|
for (int i = 0; i < numslots; i++)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We assume the previous call to process_slot() handled everything
|
||||||
|
* that was marked ready in the previous call to wait_on_slots(), if
|
||||||
|
* any.
|
||||||
|
*/
|
||||||
|
slots[i].ready = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function should only ever see free slots as we are finishing
|
||||||
|
* processing the last few databases, at which point we don't have any
|
||||||
|
* databases left for them to process. We'll never use these slots
|
||||||
|
* again, so we can safely ignore them.
|
||||||
|
*/
|
||||||
|
if (slots[i].state == FREE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the socket to the set.
|
||||||
|
*/
|
||||||
|
slots[i].sock = PQsocket(slots[i].conn);
|
||||||
|
if (slots[i].sock < 0)
|
||||||
|
pg_fatal("invalid socket");
|
||||||
|
FD_SET(slots[i].sock, slots[i].select_mode ? &input : &output);
|
||||||
|
maxFd = Max(maxFd, slots[i].sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we found socket(s) to wait on, wait.
|
||||||
|
*/
|
||||||
|
if (select_loop(maxFd, &input, &output) == -1)
|
||||||
|
pg_fatal("select() failed: %m");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark which sockets appear to be ready.
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < numslots; i++)
|
||||||
|
slots[i].ready |= (FD_ISSET(slots[i].sock, &input) ||
|
||||||
|
FD_ISSET(slots[i].sock, &output));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Runs all the steps of the task in every database in the cluster using
|
||||||
|
* user_opts.jobs parallel slots.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
upgrade_task_run(const UpgradeTask *task, const ClusterInfo *cluster)
|
||||||
|
{
|
||||||
|
int jobs = Max(1, user_opts.jobs);
|
||||||
|
UpgradeTaskSlot *slots = pg_malloc0(sizeof(UpgradeTaskSlot) * jobs);
|
||||||
|
|
||||||
|
dbs_complete = 0;
|
||||||
|
dbs_processing = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process every slot the first time round.
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < jobs; i++)
|
||||||
|
slots[i].ready = true;
|
||||||
|
|
||||||
|
while (dbs_complete < cluster->dbarr.ndbs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < jobs; i++)
|
||||||
|
process_slot(cluster, &slots[i], task);
|
||||||
|
|
||||||
|
wait_on_slots(slots, jobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_free(slots);
|
||||||
|
}
|
@ -3040,6 +3040,11 @@ UnresolvedTup
|
|||||||
UnresolvedTupData
|
UnresolvedTupData
|
||||||
UpdateContext
|
UpdateContext
|
||||||
UpdateStmt
|
UpdateStmt
|
||||||
|
UpgradeTask
|
||||||
|
UpgradeTaskReport
|
||||||
|
UpgradeTaskSlot
|
||||||
|
UpgradeTaskSlotState
|
||||||
|
UpgradeTaskStep
|
||||||
UploadManifestCmd
|
UploadManifestCmd
|
||||||
UpperRelationKind
|
UpperRelationKind
|
||||||
UpperUniquePath
|
UpperUniquePath
|
||||||
|
Loading…
x
Reference in New Issue
Block a user