PostgreSQL 소스 코드 판독 (152) - PG Tools \ # 4 (ReceiveXlogStream)

33528 단어
이 절 은 PostgreSQL 의 백업 도구 pg 를 간단하게 소개 합 니 다.basebackup 소스 코드 에서 백업 논 리 를 실제 실행 하 는 BaseBackup 에서 WAL 데 이 터 를 백업 하 는 실현 함수 StartLogStreamer - > LogStreamer Main 과 그 주요 실현 함수 ReceiveXlogStream.
데이터 구조
logstreamer_param WAL 데이터 스 트 리머 파라미터. typedef struct { //// PGconn *bgconn; // XLogRecPtr startptr; // tar , char xlog[MAXPGPATH]; /* directory or tarfile depending on mode */ // char *sysidentifier; // int timeline; } logstreamer_param; StreamCtl 이 xlog 스 트림 데 이 터 를 받 을 때의 전역 매개 변수 /* * Global parameters when receiving xlog stream. For details about the individual fields, * see the function comment for ReceiveXlogStream(). * xlog . * , ReceiveXlogStream() . */ typedef struct StreamCtl { //streaming XLogRecPtr startpos; /* Start position for streaming */ // TimeLineID timeline; /* Timeline to stream data from */ // char *sysidentifier; /* Validate this system identifier and * timeline */ //standby int standby_message_timeout; /* Send status messages this often */ // ( Flush WAL data) bool synchronous; /* Flush immediately WAL data on write */ // segment bool mark_done; /* Mark segment as done in generated archive */ // ( ) bool do_sync; /* Flush to disk to ensure consistent state of * data */ // T streaming stream_stop_callback stream_stop; /* Stop streaming when returns true */ // , socket stream_stop() pgsocket stop_socket; /* if valid, watch for input on this socket * and check stream_stop() when there is any */ // WAL WalWriteMethod *walmethod; /* How to write the WAL */ // char *partial_suffix; /* Suffix appended to partially received files */ // replication slot, NULL char *replication_slot; /* Replication slot to use, or NULL */ } StreamCtl; 2. 소스 코드 해독
LogStreamerMain WAL 흐름 은 주 함 수 를 복사 하여 fork 후의 하위 프로 세 스 호출 에 사용 합 니 다. static int LogStreamerMain(logstreamer_param *param) { StreamCtl stream;// xlog in_log_streamer = true; // StreamCtl MemSet(&stream, 0, sizeof(stream)); stream.startpos = param->startptr; stream.timeline = param->timeline; stream.sysidentifier = param->sysidentifier; stream.stream_stop = reached_end_position; #ifndef WIN32 stream.stop_socket = bgpipe[0]; #else stream.stop_socket = PGINVALID_SOCKET; #endif stream.standby_message_timeout = standby_message_timeout; stream.synchronous = false; stream.do_sync = do_sync; stream.mark_done = true; stream.partial_suffix = NULL; stream.replication_slot = replication_slot; if (format == 'p') stream.walmethod = CreateWalDirectoryMethod(param->xlog, 0, do_sync); else stream.walmethod = CreateWalTarMethod(param->xlog, compresslevel, do_sync); // if (!ReceiveXlogStream(param->bgconn, &stream)) /* * Any errors will already have been reported in the function process, * but we need to tell the parent that we didn't shutdown in a nice * way. * , * . */ return 1; if (!stream.walmethod->finish()) { fprintf(stderr, _("%s: could not finish writing WAL files: %s
"), progname, strerror(errno)); return 1; } // PQfinish(param->bgconn); // if (format == 'p') FreeWalDirectoryMethod(); else FreeWalTarMethod(); // pg_free(stream.walmethod); return 0; }
ReceiveXlogStream 이 지정 한 시작 위치 에서 log stream 을 받 습 니 다. /* * Receive a log stream starting at the specified position. * log stream * * Individual parameters are passed through the StreamCtl structure. * StreamCtl . * * If sysidentifier is specified, validate that both the system * identifier and the timeline matches the specified ones * (by sending an extra IDENTIFY_SYSTEM command) * , timeline . * ( IDENTIFY_SYSTEM ) * * All received segments will be written to the directory * specified by basedir. This will also fetch any missing timeline history * files. * segments basedir . * timeline history . * * The stream_stop callback will be called every time data * is received, and whenever a segment is completed. If it returns * true, the streaming will stop and the function * return. As long as it returns false, streaming will continue * indefinitely. * stream_stop segment . * T,streaming , . * F,streaming . * * If stream_stop() checks for external input, stop_socket should be set to * the FD it checks. This will allow such input to be detected promptly * rather than after standby_message_timeout (which might be indefinite). * Note that signals will interrupt waits for input as well, but that is * race-y since a signal received while busy won't interrupt the wait. * stream_stop() ,stop_socket FD. * , standby_message_timeout ( ). * , , . * * standby_message_timeout controls how often we send a message * back to the master letting it know our progress, in milliseconds. * Zero means no messages are sent. * This message will only contain the write location, and never * flush or replay. * standby_message_timeout master , ms. * 0 . * , flush replay. * * If 'partial_suffix' is not NULL, files are initially created with the * given suffix, and the suffix is removed once the file is finished. That * allows you to tell the difference between partial and completed files, * so that you can continue later where you left. * 'partial_suffix' NULL, suffix , * , suffix . * , . * * If 'synchronous' is true, the received WAL is flushed as soon as written, * otherwise only when the WAL file is closed. * 'synchronous' T, WAL , WAL file . * * Note: The WAL location *must* be at a log segment start! * :WAL log segment . */ bool ReceiveXlogStream(PGconn *conn, StreamCtl *stream) { char query[128]; char slotcmd[128]; PGresult *res; XLogRecPtr stoppos; /* * The caller should've checked the server version already, but doesn't do * any harm to check it here too. * , . */ if (!CheckServerVersionForStreaming(conn)) return false; /* * Decide whether we want to report the flush position. If we report the * flush position, the primary will know what WAL we'll possibly * re-request, and it can then remove older WAL safely. We must always do * that when we are using slots. * flush . * flush , WAL file, * WAL. * slots, . * * Reporting the flush position makes one eligible as a synchronous * replica. People shouldn't include generic names in * synchronous_standby_names, but we've protected them against it so far, * so let's continue to do so unless specifically requested. * flush . * DBA synchronous_standby_names , , * . */ if (stream->replication_slot != NULL) { // slot reportFlushPosition = true; sprintf(slotcmd, "SLOT \"%s\" ", stream->replication_slot); } else { if (stream->synchronous) reportFlushPosition = true;// else reportFlushPosition = false;// slotcmd[0] = 0;//ASCII 0 } if (stream->sysidentifier != NULL) { // NULL /* Validate system identifier hasn't changed */ // // IDENTIFY_SYSTEM res = PQexec(conn, "IDENTIFY_SYSTEM"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, _("%s: could not send replication command \"%s\": %s"), progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn)); PQclear(res); return false; } if (PQntuples(res) != 1 || PQnfields(res) < 3) { fprintf(stderr, _("%s: could not identify system: got %d rows and %d fields, expected %d rows and %d r more fields
"), progname, PQntuples(res), PQnfields(res), 1, 3); PQclear(res); return false; } if (strcmp(stream->sysidentifier, PQgetvalue(res, 0, 0)) != 0) { fprintf(stderr, _("%s: system identifier does not match between base backup and streaming onnection
"), progname); PQclear(res); return false; } if (stream->timeline > atoi(PQgetvalue(res, 0, 1))) { fprintf(stderr, _("%s: starting timeline %u is not present in the server
"), progname, stream->timeline); PQclear(res); return false; } PQclear(res); } /* * initialize flush position to starting point, it's the caller's * responsibility that that's sane. * flush , . */ lastFlushPosition = stream->startpos; while (1) { /* * Fetch the timeline history file for this timeline, if we don't have * it already. When streaming log to tar, this will always return * false, as we are never streaming into an existing file and * therefore there can be no pre-existing timeline history file. * timeline timeline history, . * streaming tar , F, streaming , * timeline history . */ if (!existsTimeLineHistoryFile(stream)) { // history snprintf(query, sizeof(query), "TIMELINE_HISTORY %u", stream->timeline); // TIMELINE_HISTORY res = PQexec(conn, query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { /* FIXME: we might send it ok, but get an error */ fprintf(stderr, _("%s: could not send replication command \"%s\": %s"), progname, "TIMELINE_HISTORY", PQresultErrorMessage(res)); PQclear(res); return false; } /* * The response to TIMELINE_HISTORY is a single row result set * with two fields: filename and content * TIMELINE_HISTORY , :filename content */ if (PQnfields(res) != 2 || PQntuples(res) != 1) { fprintf(stderr, _("%s: unexpected response to TIMELINE_HISTORY command: got %d rows and %d ields, expected %d rows and %d fields
"), progname, PQntuples(res), PQnfields(res), 1, 2); } /* Write the history file to disk */ // history writeTimeLineHistoryFile(stream, PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 1)); PQclear(res); } /* * Before we start streaming from the requested location, check if the * callback tells us to stop here. * streaming , */ if (stream->stream_stop(stream->startpos, stream->timeline, false)) return true; /* Initiate the replication stream at specified location */ // snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u", slotcmd, (uint32) (stream->startpos >> 32), (uint32) stream->startpos, stream->timeline); // START_REPLICATION res = PQexec(conn, query); if (PQresultStatus(res) != PGRES_COPY_BOTH) { fprintf(stderr, _("%s: could not send replication command \"%s\": %s"), progname, "START_REPLICATION", PQresultErrorMessage(res)); PQclear(res); return false; } PQclear(res); /* Stream the WAL */ // WAL res = HandleCopyStream(conn, stream, &stoppos); if (res == NULL) goto error; /* * Streaming finished. * * There are two possible reasons for that: a controlled shutdown, or * we reached the end of the current timeline. In case of * end-of-timeline, the server sends a result set after Copy has * finished, containing information about the next timeline. Read * that, and restart streaming from the next timeline. In case of * controlled shutdown, stop here. * Streaming . * : shutdown . * end-of-timeline , Copy , * . * , streaming. * , . */ if (PQresultStatus(res) == PGRES_TUPLES_OK) { /* * End-of-timeline. Read the next timeline's ID and starting * position. Usually, the starting position will match the end of * the previous timeline, but there are corner cases like if the * server had sent us half of a WAL record, when it was promoted. * The new timeline will begin at the end of the last complete * record in that case, overlapping the partial WAL record on the * old timeline. * End-of-timeline . * ID . , , * WAL Record . * , , WAL Record . */ uint32 newtimeline;// bool parsed;// // parsed = ReadEndOfStreamingResult(res, &stream->startpos, &newtimeline); PQclear(res); if (!parsed) goto error; /* Sanity check the values the server gave us */ // if (newtimeline <= stream->timeline) { // stream fprintf(stderr, _("%s: server reported unexpected next timeline %u, following timeline %u
"), progname, newtimeline, stream->timeline); goto error; } if (stream->startpos > stoppos) { // fprintf(stderr, _("%s: server stopped streaming timeline %u at %X/%X, but reported next timeline u to begin at %X/%X
"), progname, stream->timeline, (uint32) (stoppos >> 32), (uint32) stoppos, newtimeline, (uint32) (stream->startpos >> 32), (uint32) stream->startpos); goto error; } /* Read the final result, which should be CommandComplete. */ // , res = PQgetResult(conn); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, _("%s: unexpected termination of replication stream: %s"), progname, PQresultErrorMessage(res)); PQclear(res); goto error; } PQclear(res); /* * Loop back to start streaming from the new timeline. Always * start streaming at the beginning of a segment. * , segment streaming */ stream->timeline = newtimeline; stream->startpos = stream->startpos - XLogSegmentOffset(stream->startpos, WalSegSz); continue;// } else if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); /* * End of replication (ie. controlled shut down of the server). * replication ( ) * * Check if the callback thinks it's OK to stop here. If not, * complain. * OK , , . */ if (stream->stream_stop(stoppos, stream->timeline, false)) return true; else { fprintf(stderr, _("%s: replication stream was terminated before stop point
"), progname); goto error; } } else { /* Server returned an error. */ // fprintf(stderr, _("%s: unexpected termination of replication stream: %s"), progname, PQresultErrorMessage(res)); PQclear(res); goto error; } } error: if (walfile != NULL && stream->walmethod->close(walfile, CLOSE_NO_RENAME) != 0) fprintf(stderr, _("%s: could not close file \"%s\": %s
"), progname, current_walfile_name, stream->walmethod->getlasterror()); walfile = NULL; return false; } /* * The main loop of ReceiveXlogStream. Handles the COPY stream after * initiating streaming with the START_REPLICATION command. * ReceiveXlogStream . * START_REPLICATION streaming COPY stream. * * If the COPY ends (not necessarily successfully) due a message from the * server, returns a PGresult and sets *stoppos to the last byte written. * On any other sort of error, returns NULL. * COPY , PGresult *stoppos . * , NULL. */ static PGresult * HandleCopyStream(PGconn *conn, StreamCtl *stream, XLogRecPtr *stoppos) { char *copybuf = NULL; TimestampTz last_status = -1; XLogRecPtr blockpos = stream->startpos; still_sending = true; while (1) { // int r; TimestampTz now;// long sleeptime; /* * Check if we should continue streaming, or abort at this point. * streaming, */ if (!CheckCopyStreamStop(conn, stream, blockpos, stoppos)) goto error; now = feGetCurrentTimestamp(); /* * If synchronous option is true, issue sync command as soon as there * are WAL data which has not been flushed yet. * T, flushed WAL data, sync . */ if (stream->synchronous && lastFlushPosition < blockpos && walfile != NULL) { if (stream->walmethod->sync(walfile) != 0) { fprintf(stderr, _("%s: could not fsync file \"%s\": %s
"), progname, current_walfile_name, stream->walmethod->getlasterror()); goto error; } lastFlushPosition = blockpos; /* * Send feedback so that the server sees the latest WAL locations * immediately. * WAL . */ if (!sendFeedback(conn, blockpos, now, false)) goto error; last_status = now; } /* * Potentially send a status message to the master * */ if (still_sending && stream->standby_message_timeout > 0 && feTimestampDifferenceExceeds(last_status, now, stream->standby_message_timeout)) { /* Time to send feedback! */ // . if (!sendFeedback(conn, blockpos, now, false)) goto error; last_status = now; } /* * Calculate how long send/receive loops should sleep * send/receive */ sleeptime = CalculateCopyStreamSleeptime(now, stream->standby_message_timeout, last_status); // stream r = CopyStreamReceive(conn, sleeptime, stream->stop_socket, ©buf); while (r != 0) { if (r == -1) goto error;// if (r == -2) { // PGresult *res = HandleEndOfCopyStream(conn, stream, copybuf, blockpos, stoppos); if (res == NULL) goto error; else return res; } /* Check the message type. */ // if (copybuf[0] == 'k') { if (!ProcessKeepaliveMsg(conn, stream, copybuf, r, blockpos, &last_status)) goto error; } else if (copybuf[0] == 'w') { if (!ProcessXLogDataMsg(conn, stream, copybuf, r, &blockpos)) goto error; /* * Check if we should continue streaming, or abort at this * point. * streaming */ if (!CheckCopyStreamStop(conn, stream, blockpos, stoppos)) goto error; } else { fprintf(stderr, _("%s: unrecognized streaming header: \"%c\"
"), progname, copybuf[0]); goto error; } /* * Process the received data, and any subsequent data we can read * without blocking. * , . */ r = CopyStreamReceive(conn, 0, stream->stop_socket, ©buf); } } error: if (copybuf != NULL) PQfreemem(copybuf); return NULL; } /* * Check if we should continue streaming, or abort at this point. */ static bool CheckCopyStreamStop(PGconn *conn, StreamCtl *stream, XLogRecPtr blockpos, XLogRecPtr *stoppos) { if (still_sending && stream->stream_stop(blockpos, stream->timeline, false)) { if (!close_walfile(stream, blockpos)) { /* Potential error message is written by close_walfile */ return false; } if (PQputCopyEnd(conn, NULL) <= 0 || PQflush(conn)) { fprintf(stderr, _("%s: could not send copy-end packet: %s"), progname, PQerrorMessage(conn)); return false; } still_sending = false; } return true; } /* * Receive CopyData message available from XLOG stream, blocking for * maximum of 'timeout' ms. * XLOG stream CopyData , 'timeout' , . * * If data was received, returns the length of the data. *buffer is set to * point to a buffer holding the received message. The buffer is only valid * until the next CopyStreamReceive call. * , . * *buffer buffer.buffer CopyStreamReceive . * * Returns 0 if no data was available within timeout, or if wait was * interrupted by signal or stop_socket input. * -1 on error. -2 if the server ended the COPY. * timeout , /stop_socket , 0. * -1: .-2 COPY */ static int CopyStreamReceive(PGconn *conn, long timeout, pgsocket stop_socket, char **buffer) { char *copybuf = NULL; int rawlen; if (*buffer != NULL) PQfreemem(*buffer); *buffer = NULL; /* Try to receive a CopyData message */ rawlen = PQgetCopyData(conn, ©buf, 1); if (rawlen == 0) { int ret; /* * No data available. Wait for some to appear, but not longer than * the specified timeout, so that we can ping the server. Also stop * waiting if input appears on stop_socket. */ ret = CopyStreamPoll(conn, timeout, stop_socket); if (ret <= 0) return ret; /* Now there is actually data on the socket */ if (PQconsumeInput(conn) == 0) { fprintf(stderr, _("%s: could not receive data from WAL stream: %s"), progname, PQerrorMessage(conn)); return -1; } /* Now that we've consumed some input, try again */ rawlen = PQgetCopyData(conn, ©buf, 1); if (rawlen == 0) return 0; } if (rawlen == -1) /* end-of-streaming or error */ return -2; if (rawlen == -2) { fprintf(stderr, _("%s: could not read COPY data: %s"), progname, PQerrorMessage(conn)); return -1; } /* Return received messages to caller */ *buffer = copybuf; return rawlen; }
3. 추적 분석
백업 명령 pg_basebackup -h 192.168.26.25 -U replicator -p 5432 -D /data/backup -P -Xs -R -v gdb 추적 시작 (fork 의 하위 프로 세 스 추적) [xdb@localhost ~]$ gdb pg_basebackup GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: ... Reading symbols from /appdb/xdb/pg11.2/bin/pg_basebackup...done. (gdb) set args -h 192.168.26.25 -U replicator -p 5432 -D /data/backup -P -Xs -R -v (gdb) set follow-fork-mode child (gdb) b LogStreamerMain Breakpoint 1 at 0x403c51: file pg_basebackup.c, line 490. (gdb) r Starting program: /appdb/xdb/pg11.2/bin/pg_basebackup -h 192.168.26.25 -U replicator -p 5432 -D /data/backup -P -Xs -R -v [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Password: pg_basebackup: initiating base backup, waiting for checkpoint to complete pg_basebackup: checkpoint completed pg_basebackup: write-ahead log start point: 0/5A000028 on timeline 16 pg_basebackup: starting background WAL receiver pg_basebackup: created temporary replication slot "pg_basebackup_1604" [New process 2036] [Thread debugging using libthread_db enabled]backup/backup_label ) Using host libthread_db library "/lib64/libthread_db.so.1". [Switching to Thread 0x7ffff7fe7840 (LWP 2036)] Breakpoint 1, LogStreamerMain (param=0x629db0) at pg_basebackup.c:490 490 in_log_streamer = true; 305153/305153 kB (100%), 1/1 tablespace ) pg_basebackup: write-ahead log end point: 0/5A0000F8 pg_basebackup: waiting for background process to finish streaming ... (gdb) 입력 매개 변수 (gdb) n 492 MemSet(&stream, 0, sizeof(stream)); (gdb) p *param $1 = {bgconn = 0x62a280, startptr = 1509949440, xlog = "/data/backup/pg_wal", '\000' , sysidentifier = 0x61f1a0 "6666964067616600474", timeline = 16} (gdb) StreamCtl 구조 체 설정 (gdb) n 493 stream.startpos = param->startptr; (gdb) 494 stream.timeline = param->timeline; (gdb) 495 stream.sysidentifier = param->sysidentifier; (gdb) 496 stream.stream_stop = reached_end_position; (gdb) 498 stream.stop_socket = bgpipe[0]; (gdb) 502 stream.standby_message_timeout = standby_message_timeout; (gdb) 503 stream.synchronous = false; (gdb) 504 stream.do_sync = do_sync; (gdb) 505 stream.mark_done = true; (gdb) 506 stream.partial_suffix = NULL; (gdb) 507 stream.replication_slot = replication_slot; (gdb) 509 if (format == 'p') (gdb) 510 stream.walmethod = CreateWalDirectoryMethod(param->xlog, 0, do_sync); (gdb) ReceiveXlogStream 함수 들 어가 기 (gdb) 514 if (!ReceiveXlogStream(param->bgconn, &stream)) (gdb) step ReceiveXlogStream (conn=0x62a280, stream=0x7fffffffda30) at receivelog.c:458 458 if (!CheckServerVersionForStreaming(conn)) (gdb) (gdb) n 472 if (stream->replication_slot != NULL) (gdb) p *stream $2 = {startpos = 1509949440, timeline = 16, sysidentifier = 0x61f1a0 "6666964067616600474", standby_message_timeout = 10000, synchronous = false, mark_done = true, do_sync = true, stream_stop = 0x403953 , stop_socket = 8, walmethod = 0x632b10, partial_suffix = 0x0, replication_slot = 0x62a1e0 "pg_basebackup_1604"} (gdb) 판단 시스템 식별 자 와 타임 라인 (gdb) n 474 reportFlushPosition = true; (gdb) 475 sprintf(slotcmd, "SLOT \"%s\" ", stream->replication_slot); (gdb) 486 if (stream->sysidentifier != NULL) (gdb) 489 res = PQexec(conn, "IDENTIFY_SYSTEM"); (gdb) 490 if (PQresultStatus(res) != PGRES_TUPLES_OK) (gdb) 498 if (PQntuples(res) != 1 || PQnfields(res) < 3) (gdb) 506 if (strcmp(stream->sysidentifier, PQgetvalue(res, 0, 0)) != 0) (gdb) p PQgetvalue(res, 0, 0) $3 = 0x633500 "6666964067616600474" (gdb) n 514 if (stream->timeline > atoi(PQgetvalue(res, 0, 1))) (gdb) 522 PQclear(res); (gdb) p PQgetvalue(res, 0, 1) $4 = 0x633514 "16" (gdb) 타임 라인 history 파일 이 존재 하지 않 습 니 다. history 파일 생 성 (gdb) n 529 lastFlushPosition = stream->startpos; (gdb) 539 if (!existsTimeLineHistoryFile(stream)) (gdb) 541 snprintf(query, sizeof(query), "TIMELINE_HISTORY %u", stream->timeline); (gdb) 542 res = PQexec(conn, query); (gdb) 543 if (PQresultStatus(res) != PGRES_TUPLES_OK) (gdb) 556 if (PQnfields(res) != 2 || PQntuples(res) != 1) (gdb) 564 writeTimeLineHistoryFile(stream, (gdb) 568 PQclear(res); (gdb) START 호출REPLICATION 명령 초기 화 (gdb) 575 if (stream->stream_stop(stream->startpos, stream->timeline, false)) (gdb) n 579 snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u", (gdb) 581 (uint32) (stream->startpos >> 32), (uint32) stream->startpos, (gdb) 579 snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u", (gdb) 581 (uint32) (stream->startpos >> 32), (uint32) stream->startpos, (gdb) 579 snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u", (gdb) 583 res = PQexec(conn, query); (gdb) 584 if (PQresultStatus(res) != PGRES_COPY_BOTH) (gdb) 591 PQclear(res); (gdb) 명령 실행, stream WAL 처리, 호출 완료 595 if (res == NULL) (gdb) p *res $5 = {ntups = 0, numAttributes = 0, attDescs = 0x0, tuples = 0x0, tupArrSize = 0, numParameters = 0, paramDescs = 0x0, resultStatus = PGRES_COMMAND_OK, cmdStatus = "START_STREAMING\000\000\000\000\000\270\027u\367\377\177\000\000P/c\000\000\000\000\000CT\000\000\001", '\000' , "\200\000\000", binary = 0, noticeHooks = {noticeRec = 0x7ffff7b9eaa4 , noticeRecArg = 0x0, noticeProc = 0x7ffff7b9eaf9 , noticeProcArg = 0x0}, events = 0x0, nEvents = 0, client_encoding = 0, errMsg = 0x0, errFields = 0x0, errQuery = 0x0, null_field = "", curBlock = 0x0, curOffset = 0, spaceLeft = 0} (gdb) n 608 if (PQresultStatus(res) == PGRES_TUPLES_OK) (gdb) 666 else if (PQresultStatus(res) == PGRES_COMMAND_OK) (gdb) 668 PQclear(res); (gdb) 676 if (stream->stream_stop(stoppos, stream->timeline, false)) (gdb) 677 return true; (gdb) 702 } (gdb) LogStreamerMain (param=0x629db0) at pg_basebackup.c:523 523 if (!stream.walmethod->finish()) (gdb) DONE!
참고 자료
PG Source Code
"ITPUB 블 로그" 에서 왔 습 니 다. 링크:http://blog.itpub.net/6906/viewspace-2638760/전재 가 필요 하 다 면 출처 를 밝 혀 주 십시오. 그렇지 않 으 면 법 적 책임 을 추궁 할 것 입 니 다.
다음으로 전송:http://blog.itpub.net/6906/viewspace-2638760/

좋은 웹페이지 즐겨찾기