|
9 | 9 | */
|
10 | 10 |
|
11 | 11 | #include "pg_probackup.h"
|
| 12 | +#include "access/timeline.h" |
12 | 13 |
|
13 | 14 | #include <dirent.h>
|
14 | 15 | #include <signal.h>
|
|
18 | 19 | #include "utils/file.h"
|
19 | 20 | #include "utils/configuration.h"
|
20 | 21 |
|
| 22 | +static pgBackup* get_closest_backup(timelineInfo *tlinfo, parray *backup_list); |
| 23 | +static pgBackup* get_oldest_backup(timelineInfo *tlinfo, parray *backup_list); |
21 | 24 | static const char *backupModes[] = {"", "PAGE", "PTRACK", "DELTA", "FULL"};
|
22 | 25 | static pgBackup *readBackupControlFile(const char *path);
|
23 | 26 |
|
24 | 27 | static bool exit_hook_registered = false;
|
25 | 28 | static parray *lock_files = NULL;
|
26 | 29 |
|
| 30 | +static timelineInfo * |
| 31 | +timelineInfoNew(TimeLineID tli) |
| 32 | +{ |
| 33 | + timelineInfo *tlinfo = (timelineInfo *) pgut_malloc(sizeof(timelineInfo)); |
| 34 | + MemSet(tlinfo, 0, sizeof(timelineInfo)); |
| 35 | + tlinfo->tli = tli; |
| 36 | + tlinfo->switchpoint = InvalidXLogRecPtr; |
| 37 | + tlinfo->parent_link = NULL; |
| 38 | + return tlinfo; |
| 39 | +} |
| 40 | + |
27 | 41 | /* Iterate over locked backups and delete locks files */
|
28 | 42 | static void
|
29 | 43 | unlink_lock_atexit(void)
|
@@ -665,6 +679,280 @@ pgBackupCreateDir(pgBackup *backup)
|
665 | 679 | return 0;
|
666 | 680 | }
|
667 | 681 |
|
| 682 | +/* |
| 683 | + * Create list of timelines |
| 684 | + */ |
| 685 | +parray * |
| 686 | +catalog_get_timelines(InstanceConfig *instance) |
| 687 | +{ |
| 688 | + parray *xlog_files_list = parray_new(); |
| 689 | + parray *timelineinfos; |
| 690 | + parray *backups; |
| 691 | + timelineInfo *tlinfo; |
| 692 | + char arclog_path[MAXPGPATH]; |
| 693 | + |
| 694 | + /* read all xlog files that belong to this archive */ |
| 695 | + sprintf(arclog_path, "%s/%s/%s", backup_path, "wal", instance->name); |
| 696 | + dir_list_file(xlog_files_list, arclog_path, false, false, false, 0, FIO_BACKUP_HOST); |
| 697 | + parray_qsort(xlog_files_list, pgFileComparePath); |
| 698 | + |
| 699 | + timelineinfos = parray_new(); |
| 700 | + tlinfo = NULL; |
| 701 | + |
| 702 | + /* walk through files and collect info about timelines */ |
| 703 | + for (int i = 0; i < parray_num(xlog_files_list); i++) |
| 704 | + { |
| 705 | + pgFile *file = (pgFile *) parray_get(xlog_files_list, i); |
| 706 | + TimeLineID tli; |
| 707 | + parray *timelines; |
| 708 | + |
| 709 | + /* regular WAL file */ |
| 710 | + if (strspn(file->name, "0123456789ABCDEF") == XLOG_FNAME_LEN) |
| 711 | + { |
| 712 | + int result = 0; |
| 713 | + uint32 log, seg; |
| 714 | + XLogSegNo segno; |
| 715 | + char suffix[MAXPGPATH]; |
| 716 | + |
| 717 | + result = sscanf(file->name, "%08X%08X%08X.%s", |
| 718 | + &tli, &log, &seg, (char *) &suffix); |
| 719 | + |
| 720 | + if (result < 3) |
| 721 | + { |
| 722 | + elog(WARNING, "unexpected WAL file name \"%s\"", file->name); |
| 723 | + continue; |
| 724 | + } |
| 725 | + |
| 726 | + segno = log * instance->xlog_seg_size + seg; |
| 727 | + |
| 728 | + /* regular WAL file with suffix */ |
| 729 | + if (result == 4) |
| 730 | + { |
| 731 | + /* backup history file. Currently we don't use them */ |
| 732 | + if (IsBackupHistoryFileName(file->name)) |
| 733 | + { |
| 734 | + elog(VERBOSE, "backup history file \"%s\". do nothing", file->name); |
| 735 | + continue; |
| 736 | + } |
| 737 | + /* we only expect compressed wal files with .gz suffix */ |
| 738 | + else if (strcmp(suffix, "gz") != 0) |
| 739 | + { |
| 740 | + elog(WARNING, "unexpected WAL file name \"%s\"", file->name); |
| 741 | + continue; |
| 742 | + } |
| 743 | + } |
| 744 | + |
| 745 | + /* new file belongs to new timeline */ |
| 746 | + if (!tlinfo || tlinfo->tli != tli) |
| 747 | + { |
| 748 | + tlinfo = timelineInfoNew(tli); |
| 749 | + parray_append(timelineinfos, tlinfo); |
| 750 | + } |
| 751 | + /* |
| 752 | + * As it is impossible to detect if segments before segno are lost, |
| 753 | + * or just do not exist, do not report them as lost. |
| 754 | + */ |
| 755 | + else if (tlinfo->n_xlog_files != 0) |
| 756 | + { |
| 757 | + /* check, if segments are consequent */ |
| 758 | + XLogSegNo expected_segno = tlinfo->end_segno + 1; |
| 759 | + |
| 760 | + /* some segments are missing. remember them in lost_segments to report */ |
| 761 | + if (segno != expected_segno) |
| 762 | + { |
| 763 | + xlogInterval *interval = palloc(sizeof(xlogInterval));; |
| 764 | + interval->begin_segno = expected_segno; |
| 765 | + interval->end_segno = segno - 1; |
| 766 | + |
| 767 | + if (tlinfo->lost_segments == NULL) |
| 768 | + tlinfo->lost_segments = parray_new(); |
| 769 | + |
| 770 | + parray_append(tlinfo->lost_segments, interval); |
| 771 | + } |
| 772 | + } |
| 773 | + |
| 774 | + if (tlinfo->begin_segno == 0) |
| 775 | + tlinfo->begin_segno = segno; |
| 776 | + |
| 777 | + /* this file is the last for this timeline so far */ |
| 778 | + tlinfo->end_segno = segno; |
| 779 | + /* update counters */ |
| 780 | + tlinfo->n_xlog_files++; |
| 781 | + tlinfo->size += file->size; |
| 782 | + } |
| 783 | + /* timeline history file */ |
| 784 | + else if (IsTLHistoryFileName(file->name)) |
| 785 | + { |
| 786 | + TimeLineHistoryEntry *tln; |
| 787 | + |
| 788 | + sscanf(file->name, "%08X.history", &tli); |
| 789 | + timelines = read_timeline_history(arclog_path, tli); |
| 790 | + |
| 791 | + if (!tlinfo || tlinfo->tli != tli) |
| 792 | + { |
| 793 | + tlinfo = timelineInfoNew(tli); |
| 794 | + parray_append(timelineinfos, tlinfo); |
| 795 | + /* |
| 796 | + * 1 is the latest timeline in the timelines list. |
| 797 | + * 0 - is our timeline, which is of no interest here |
| 798 | + */ |
| 799 | + tln = (TimeLineHistoryEntry *) parray_get(timelines, 1); |
| 800 | + tlinfo->switchpoint = tln->end; |
| 801 | + tlinfo->parent_tli = tln->tli; |
| 802 | + |
| 803 | + /* find parent timeline to link it with this one */ |
| 804 | + for (int i = 0; i < parray_num(timelineinfos); i++) |
| 805 | + { |
| 806 | + timelineInfo *cur = (timelineInfo *) parray_get(timelineinfos, i); |
| 807 | + if (cur->tli == tlinfo->parent_tli) |
| 808 | + { |
| 809 | + tlinfo->parent_link = cur; |
| 810 | + break; |
| 811 | + } |
| 812 | + } |
| 813 | + } |
| 814 | + |
| 815 | + parray_walk(timelines, pfree); |
| 816 | + parray_free(timelines); |
| 817 | + } |
| 818 | + else |
| 819 | + elog(WARNING, "unexpected WAL file name \"%s\"", file->name); |
| 820 | + } |
| 821 | + |
| 822 | + /* save information about backups belonging to each timeline */ |
| 823 | + backups = catalog_get_backup_list(instance->name, INVALID_BACKUP_ID); |
| 824 | + |
| 825 | + for (int i = 0; i < parray_num(timelineinfos); i++) |
| 826 | + { |
| 827 | + timelineInfo *tlinfo = parray_get(timelineinfos, i); |
| 828 | + for (int j = 0; j < parray_num(backups); j++) |
| 829 | + { |
| 830 | + pgBackup *backup = parray_get(backups, j); |
| 831 | + if (tlinfo->tli == backup->tli) |
| 832 | + { |
| 833 | + if (tlinfo->backups == NULL) |
| 834 | + tlinfo->backups = parray_new(); |
| 835 | + |
| 836 | + parray_append(tlinfo->backups, backup); |
| 837 | + } |
| 838 | + } |
| 839 | + } |
| 840 | + |
| 841 | + /* determine oldest backup and closest backup for every timeline */ |
| 842 | + for (int i = 0; i < parray_num(timelineinfos); i++) |
| 843 | + { |
| 844 | + timelineInfo *tlinfo = parray_get(timelineinfos, i); |
| 845 | + |
| 846 | + tlinfo->oldest_backup = get_oldest_backup(tlinfo, backups); |
| 847 | + tlinfo->closest_backup = get_closest_backup(tlinfo, backups); |
| 848 | + } |
| 849 | + |
| 850 | + parray_walk(xlog_files_list, pfree); |
| 851 | + parray_free(xlog_files_list); |
| 852 | + |
| 853 | + return timelineinfos; |
| 854 | +} |
| 855 | + |
| 856 | +/* |
| 857 | + * Iterate over parent timelines of a given timeline and look |
| 858 | + * for valid backup closest to given timeline switchpoint. |
| 859 | + * |
| 860 | + * Returns NULL if such backup is not found. |
| 861 | + */ |
| 862 | +pgBackup* |
| 863 | +get_closest_backup(timelineInfo *tlinfo, parray *backup_list) |
| 864 | +{ |
| 865 | + pgBackup *closest_backup = NULL; |
| 866 | + int i; |
| 867 | + |
| 868 | + /* Only timeline with switchpoint can possibly have closest backup */ |
| 869 | + if (XLogRecPtrIsInvalid(tlinfo->switchpoint)) |
| 870 | + return NULL; |
| 871 | + |
| 872 | + /* Only timeline with parent timeline can possibly have closest backup */ |
| 873 | + if (!tlinfo->parent_link) |
| 874 | + return NULL; |
| 875 | + |
| 876 | + while (tlinfo->parent_link) |
| 877 | + { |
| 878 | + /* |
| 879 | + * Iterate over backups belonging to parent timeline and look |
| 880 | + * for candidates. |
| 881 | + */ |
| 882 | + for (i = 0; i < parray_num(backup_list); i++) |
| 883 | + { |
| 884 | + pgBackup *backup = parray_get(backup_list, i); |
| 885 | + |
| 886 | + /* Backups belonging to timelines other than parent timeline can be safely skipped */ |
| 887 | + if (backup->tli != tlinfo->parent_tli) |
| 888 | + continue; |
| 889 | + |
| 890 | + /* Backups in future can be safely skipped */ |
| 891 | + if (backup->stop_lsn > tlinfo->switchpoint) |
| 892 | + continue; |
| 893 | + |
| 894 | + /* Backups with invalid STOP LSN can be safely skipped */ |
| 895 | + if (XLogRecPtrIsInvalid(backup->stop_lsn) || |
| 896 | + !XRecOffIsValid(backup->stop_lsn)) |
| 897 | + continue; |
| 898 | + |
| 899 | + /* Only valid backups closest to switchpoint should be considered */ |
| 900 | + if (backup->stop_lsn <= tlinfo->switchpoint && |
| 901 | + (backup->status == BACKUP_STATUS_OK || |
| 902 | + backup->status == BACKUP_STATUS_DONE)) |
| 903 | + { |
| 904 | + /* Check if backup is closer to switchpoint than current candidate */ |
| 905 | + if (!closest_backup || backup->stop_lsn > closest_backup->stop_lsn) |
| 906 | + closest_backup = backup; |
| 907 | + } |
| 908 | + } |
| 909 | + |
| 910 | + /* Closest backup is found */ |
| 911 | + if (closest_backup) |
| 912 | + break; |
| 913 | + |
| 914 | + /* Switch to parent */ |
| 915 | + tlinfo = tlinfo->parent_link; |
| 916 | + } |
| 917 | + |
| 918 | + return closest_backup; |
| 919 | +} |
| 920 | + |
| 921 | +/* |
| 922 | + * Iterate over timelines and look for oldest backup on each timeline |
| 923 | + * Returns NULL if such backup is not found. |
| 924 | + */ |
| 925 | +pgBackup* |
| 926 | +get_oldest_backup(timelineInfo *tlinfo, parray *backup_list) |
| 927 | +{ |
| 928 | + pgBackup *oldest_backup = NULL; |
| 929 | + int i; |
| 930 | + |
| 931 | + /* |
| 932 | + * Iterate over backups belonging to timeline and look |
| 933 | + * for candidates. |
| 934 | + */ |
| 935 | + for (i = 0; i < parray_num(backup_list); i++) |
| 936 | + { |
| 937 | + pgBackup *backup = parray_get(backup_list, i); |
| 938 | + |
| 939 | + /* Backups belonging to other timelines can be safely skipped */ |
| 940 | + if (backup->tli != tlinfo->tli) |
| 941 | + continue; |
| 942 | + |
| 943 | + /* Backups with invalid START LSN can be safely skipped */ |
| 944 | + if (XLogRecPtrIsInvalid(backup->start_lsn) || |
| 945 | + !XRecOffIsValid(backup->start_lsn)) |
| 946 | + continue; |
| 947 | + |
| 948 | + /* Check if backup is older than current candidate */ |
| 949 | + if (!oldest_backup || backup->start_lsn < oldest_backup->start_lsn) |
| 950 | + oldest_backup = backup; |
| 951 | + } |
| 952 | + |
| 953 | + return oldest_backup; |
| 954 | +} |
| 955 | + |
668 | 956 | /*
|
669 | 957 | * Write information about backup.in to stream "out".
|
670 | 958 | */
|
|
0 commit comments