Skip to content

Commit fe1b5f0

Browse files
authored
Merge c10142d into 03a8e78
2 parents 03a8e78 + c10142d commit fe1b5f0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+8918
-320
lines changed

builds/install/misc/replication.conf

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#
2+
# Replication configuration
3+
#
4+
5+
database
6+
{
7+
### ORIGIN (PRIMARY) SIDE SETTINGS
8+
9+
# Size of the local buffer used to accumulate changes that can be
10+
# deferred until the transaction commit/rollback. The bigger this value
11+
# the less disk access concurrency (related to log IOPS) happens.
12+
#
13+
# For synchronous replication, it also affects number of network round-trips
14+
# between primary and replica hosts.
15+
# However, a larger buffer costs a longer replication "checkpoints"
16+
# (delay to synchronize the original database with its replica at commit).
17+
#
18+
#buffer_size = 1048576 # 1MB
19+
20+
# Pattern (regular expression) that defines what tables must be included into
21+
# replication. By default, all tables are replicated.
22+
#
23+
#include_filter =
24+
25+
# Pattern (regular expression) that defines what tables must be excluded from
26+
# replication. By default, all tables are replicated.
27+
#
28+
#exclude_filter =
29+
30+
# Directory to store replication log files.
31+
#
32+
#log_directory =
33+
34+
# Prefix for replication log file names. It will be automatically suffixed
35+
# with an ordinal sequential number. If not specified, database filename
36+
# (without path) is used as a prefix.
37+
#
38+
#log_file_prefix
39+
40+
# Maximum allowed size for a single replication segment.
41+
#
42+
#log_segment_size = 16777216 # 16MB
43+
44+
# Maximum allowed number of full replication segments. Once this limit is reached,
45+
# the replication process is temporarily delayed to allow the archiving to catch up.
46+
# If any of the full segments is not archived during one minute,
47+
# the replication fails with an error.
48+
#
49+
# Zero means an unlimited number of segments pending archiving.
50+
#
51+
#log_segment_count = 8
52+
53+
# Delay, in milliseconds, to wait before the changes are synchronously flushed
54+
# to the log (usually at commit time). This allows multiple concurrently committing
55+
# transactions to amortise I/O costs by sharing a single flush operation.
56+
#
57+
# Zero means no delay, i.e. "group flushing" is disabled.
58+
#
59+
#log_group_flush_delay = 0
60+
61+
# Directory for the archived log files.
62+
#
63+
# Directory to store archived replication segments.
64+
# It also defines the $(archpathname) substitution macro (see below).
65+
#
66+
#log_archive_directory =
67+
68+
# Program (complete command line with arguments) that is executed when some
69+
# replication segment gets full and needs archiving.
70+
#
71+
# This program MUST return zero ONLY if archiving has been performed successfully.
72+
# In particular, it MUST return non-zero if the target archive already exists.
73+
#
74+
# Special predefined macros are available:
75+
# $(logfilename) - file name (without path) of the log segment being archived
76+
# $(logpathname) - full path name of the log segment being archived
77+
# same as log_directory + $(logfilename)
78+
# $(archpathname) - suggested full path name for the archived segment
79+
# same as log_archive_directory + $(logfilename)
80+
#
81+
# Simplest configuration is to use standard OS commands for archiving, e.g.:
82+
#
83+
# Linux: "test ! -f $(archpathname) && cp $(logpathname) $(archpathname)"
84+
# or
85+
# Windows: "copy $(logpathname) $(archpathname)"
86+
#
87+
#log_archive_command =
88+
89+
# Timeout, in seconds, to wait until incomplete segment is scheduled for archiving.
90+
# It allows to minimize the replication gap if the database is modified rarely.
91+
#
92+
# Zero means no intermediate archiving, i.e. segments are archived only after
93+
# reaching their maximum size (defined by log_segment_size).
94+
#
95+
#log_archive_timeout = 60
96+
97+
98+
### REPLICA (SECONDARY) SIDE SETTINGS
99+
100+
# Directory to search for the log files to be replicated.
101+
#
102+
#log_source_directory =
103+
104+
# Filter to limit replication to the particular source database (based on its GUID).
105+
# Expected format: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
106+
#
107+
#source_guid =
108+
}

builds/install/posix-common/posixLibrary.sh.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,8 @@ fixFilePermissions() {
929929
cd @FB_LOGDIR@
930930
touch firebird.log
931931
MakeFileFirebirdWritable firebird.log
932+
touch replication.log
933+
MakeFileFirebirdWritable replication.log
932934

933935
# Security database
934936
cd @FB_SECDBDIR@

builds/posix/make.shared.variables

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ AllObjects += $(YValve_Objects)
5454

5555
# Remote
5656
Remote_Common:= $(call dirObjects,remote) $(call dirObjects,auth/SecureRemotePassword)
57-
Remote_Server:= $(call dirObjects,remote/server) $(call dirObjects,auth/SecureRemotePassword/server)
57+
Remote_Server:= $(call dirObjects,remote/server) $(call dirObjects,auth/SecureRemotePassword/server) \
58+
$(call makeObjects,jrd/replication,Config.cpp Utils.cpp)
5859
Remote_Client:= $(call dirObjects,remote/client) $(call dirObjects,auth/SecureRemotePassword/client) \
5960
$(call makeObjects,auth/SecurityDatabase,LegacyClient.cpp) \
6061
$(call dirObjects,plugins/crypt/arc4)
@@ -66,7 +67,7 @@ AllObjects += $(Remote_Common) $(Remote_Server) $(Remote_Client)
6667

6768
# Engine
6869
Engine_Objects:= $(call dirObjects,jrd) $(call dirObjects,dsql) $(call dirObjects,jrd/extds) \
69-
$(call dirObjects,jrd/recsrc) $(call dirObjects,jrd/trace) \
70+
$(call dirObjects,jrd/recsrc) $(call dirObjects,jrd/replication) $(call dirObjects,jrd/trace) \
7071
$(call makeObjects,lock,lock.cpp)
7172

7273
AllObjects += $(Engine_Objects)

builds/win32/msvc12/engine.vcxproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@
129129
<ClCompile Include="..\..\..\src\jrd\recsrc\Union.cpp" />
130130
<ClCompile Include="..\..\..\src\jrd\recsrc\VirtualTableScan.cpp" />
131131
<ClCompile Include="..\..\..\src\jrd\recsrc\WindowedStream.cpp" />
132+
<ClCompile Include="..\..\..\src\jrd\replication\Applier.cpp" />
133+
<ClCompile Include="..\..\..\src\jrd\replication\ChangeLog.cpp" />
134+
<ClCompile Include="..\..\..\src\jrd\replication\Config.cpp" />
135+
<ClCompile Include="..\..\..\src\jrd\replication\Manager.cpp" />
136+
<ClCompile Include="..\..\..\src\jrd\replication\Publisher.cpp" />
137+
<ClCompile Include="..\..\..\src\jrd\replication\Replicator.cpp" />
138+
<ClCompile Include="..\..\..\src\jrd\replication\Utils.cpp" />
132139
<ClCompile Include="..\..\..\src\jrd\Relation.cpp" />
133140
<ClCompile Include="..\..\..\src\jrd\ResultSet.cpp" />
134141
<ClCompile Include="..\..\..\src\jrd\rlck.cpp" />

builds/win32/msvc12/fbserver.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@
218218
<ClCompile Include="..\..\..\src\remote\server\os\win32\srvr_w32.cpp" />
219219
<ClCompile Include="..\..\..\src\remote\server\os\win32\window.cpp" />
220220
<ClCompile Include="..\..\..\src\remote\server\server.cpp" />
221+
<ClCompile Include="..\..\..\src\remote\server\ReplServer.cpp" />
222+
<ClCompile Include="..\..\..\src\jrd\replication\Config.cpp" />
223+
<ClCompile Include="..\..\..\src\jrd\replication\Utils.cpp" />
221224
</ItemGroup>
222225
<ItemGroup>
223226
<None Include="..\..\..\src\remote\server\os\win32\caution.ico" />

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,7 @@ gen/$fb_tgt/firebird/databases.conf:builds/install/misc/databases.conf.in
12561256
gen/$fb_tgt/firebird/fbtrace.conf:src/utilities/ntrace/fbtrace.conf
12571257
gen/$fb_tgt/firebird/intl/fbintl.conf:builds/install/misc/fbintl.conf
12581258
gen/$fb_tgt/firebird/plugins.conf:builds/install/misc/plugins.conf
1259+
gen/$fb_tgt/firebird/replication.conf:builds/install/misc/replication.conf
12591260
])
12601261
done
12611262

doc/README.replication.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Firebird replication
2+
3+
## Concept
4+
5+
Firebird 4 offers built-in support for uni-directional \(aka master-slave\) logical replication. Logical here means record-level replication, as opposed to physical \(page-level\) replication. Implementation is primarily focused on HA \(high-availability\) solutions, but it can be used for other tasks as well.
6+
7+
Events that are tracked include: inserted/updated/deleted records, sequence changes, DDL statements. Replication is transactional, commit order is preserved. Replication can track changes in either all tables, or in some customized subset of tables. Tables to be replicated are required to have either a primary key or at least a unique key.
8+
9+
There are two replication modes available: synchronous and asynchronous.
10+
11+
In synchronous replication, the primary database is permanently connected to the replica database\(s\) and changes are being replicated immediately \(in fact, some recent uncommitted changes may be buffered, but they are transmitted at the commit time\). This effectively means that databases are in sync after every commit. However, this may impact performance due to additional network traffic and round-trips. Multiple synchronous replicas can be configured, if necessary.
12+
13+
In asynchronous replication, changes are being written into the local journal files thatare transferred over the wire and applied to the replica database. This impacts the performance much less, but introduces the delay \(known as replication lag\) when changes are not yet applied to the replica database, i.e. the replica database is always "catching up" the master database.
14+
15+
There are two access modes for replica databases: read-only and read-write. Read-only replica allows to execute any query that does not modify data \(global temporary tables can be modified as they are not replicated\), modifications are limited to the replication process only. Read-write replica allows to execute any query, possible conflicts must be resolved by users.
16+
17+
## Journalling
18+
19+
Asynchronous replication is based on journalling. Replicated changes are written into the journal which consists of multiple files \(known as replication segments\). Firebird server writes segments continuously, one after one. Every segment has a unique number which is generated sequentially. This number \(known as segment sequence\), combined with the database UUID, provide globally unique identification of journal segments. The global sequence counter is stored inside the replicated database and it's never reset \(until the database is restored from backup\).
20+
21+
Segments are regularly rotated, this process is controlled by either maximum segment size or timeout, both thresholds are configurable. Once the active segment reaches the threshold, it's marked as "full" and writing switches to the next available segment. Full segments are archived and then reused for subsequent writes. Archiving basically means copying the segment with a purpose of transferring it to the replica host and applying there. Copying can be done by Firebird server itself or, alternatively, by custom \(user-specified\) command.
22+
23+
On the replica side, journal segments are applied in the replication sequence order. Firebird server periodically scans for the new segments appearing in the configured directory. Once the next segment is found, it gets replicated. Replication state is stored in the local file named {UUID} \(per every replication source\) and contains the following markers: latest segment sequence \(LSS\), oldest segment sequence \(OSS\) and list of active transactions started between OSS and LSS. LSS means the last replicated segment. OSS means the segment that started the earliest transaction that wasn't finished at the time LSS was processed. These markers control two things: \(1\) what segment must be replicated next and \(2\) when segment files can be safely deleted. Segments with numbers between OSS and LSS are preserved for replaying the journal after the replicator disconnects from the replica database \(e.g. due to replication error or idle timeout\). If there are no active transactions pending and LSS was processed without errors, all segments up to \(and including\) LSS are deleted. In the case of any critical error, replication is temporarily suspended and re-attempted after timeout.
24+
25+
## Error reporting
26+
27+
All replication errors and warnings \(e.g. detected conflicts\) are written into the replication.log file stored in the Firebird log directory \(by default this is the root directory of the Firebird installation\). This file may also include the detailed description of the operations performed by the replicator.
28+
29+
## Setting up the master side
30+
31+
Replication is configured using a single configuration file: replication.conf. It allows to define global settings as well as per-database settings. All the possible options are listed inside replication.conf, descriptions are provided as comments there. For per-database configuration, full database name must be specified \(aliases or wildcards are not allowed\) inside the {database} section.
32+
33+
Tables to be replicated can be customized using two settings: include\_filter and exclude\_filter. They are regular expressions that are applied to table names and define rules for inclusion table\(s\) into the replication set or excluding them from the replication set.
34+
35+
Synchronous replication can be turned on using the sync\_replica setting \(multiple entries are allowed\). It must specify a connection string to the replica database, prefixed with username/password. In SuperServer and SuperClassic architectures, replica database is being internally attached when the first user gets connected to the master database and detached when the last user disconnects from the master database. In Classic Server architecture, every server process keeps an active connection to the replica database.
36+
37+
Asynchronous replication requires setting up the journalling mechanism.The primary parameter is log\_directory which defines location of the replication journal. Once this location is specified, asynchronous replication is turned on and Firebird server starts producing.the journal segments.
38+
39+
Minimal configuration looks like this:
40+
41+
database = /data/mydb.fdb
42+
{
43+
log\_directory = /dblogs/mydb/
44+
log\_archive\_directory = /shiplogs/mydb/
45+
}
46+
47+
Archiving is performed by copying the segments from /dblogs/mydb/ to /shiplogs/mydb/, Firebird server copies the segments itself.
48+
49+
The same with user-defined archiving:
50+
51+
database = /data/mydb.fdb
52+
{
53+
log\_directory = /dblogs/mydb/
54+
log\_archive\_directory = /shiplogs/mydb/
55+
log\_archive\_command = "test ! -f $\(archpathname\) && cp $\(logpathname\) $\(archpathname\)"
56+
}
57+
58+
Where $\(logpathname\) and $\(archpathname\) are built-in macros that provide the custom shell command with real file names.
59+
60+
Custom archiving \(log\_archive\_command setting\) allows to use any system shell command \(including scripts / batch files\) to deliver segments to the replica side. It could use compression, FTP, or whatever else available on the server. Actual transport implementation is up to DBA, Firebird just produces segments on the master side and expects them to appear at the replica side. If the replica storage can be remotely attached to the master host, it becomes just a matter of copying the segment files. In other cases, some transport solution is required.
61+
62+
The same with archiving performed every 10 seconds:
63+
64+
database = /data/mydb.fdb
65+
{
66+
log\_directory = /dblogs/mydb/
67+
log\_archive\_directory = /shiplogs/mydb/
68+
log\_archive\_command = "test ! -f $\(archpathname\) && cp $\(logpathname\) $\(archpathname\)"
69+
log\_archive\_timeout = 10
70+
}
71+
72+
Read replication.conf for other possible settings.
73+
74+
## Setting up the replica side
75+
76+
The same replication.conf file is used. Setting log\_source\_directory specifies the location that Firebird server scans for the transmitted segments. Additionally, DBA may explicitly specify what source database is accepted for replication. Setting source\_guid is used for that purpose.
77+
78+
Sample configuration looks like this:
79+
80+
database = /data/mydb.fdb
81+
{
82+
log\_source\_directory = /incominglogs/
83+
source\_guid = {6F9619FF-8B86-D011-B42D-00CF4FC964FF}
84+
}
85+
86+
Read replication.conf for other possible settings.
87+
88+
## Creating the replica database
89+
90+
In the Beta 1 release, any physical copying method can be used:
91+
92+
* File-level copy when Firebird server is shutdown
93+
* ALTER DATABASE BEGIN BACKUP + file-level copy + ALTER DATABASE END BACKUP
94+
* nbackup -l + file-level copy + nbackup -n
95+
* nbackup -b 0
96+
97+
Then the replica mode must be activated for the database copy. Two options are possible:
98+
99+
* gfix -replica read &lt;database&gt; -- set up database as read-only replica
100+
* gfix -replica write &lt;database&gt; -- set up database as read-write replica
101+
102+
Read-only replica means that only the replicator connection can modify the database. This is mostly indended for high availability solutions as the replica database is guaranteed to match the master one and can be used for fast recovery. Regular user connections may perform any operations allowed for read-only transactions: select from tables, execute read-only procedures, write into global temporary tables, etc. Database maintenance such as sweeping, shutdown, monitoring is also allowed. This can be used for moving read-only load \(analytics, etc\) to the replica database. However, read-only connections may potentially conflict with the replication if some DDL statements \(those requiring an exclusing metadata lock\) are performed on the master database.
103+
104+
Read-write replicas allow both the replicator connection and regular user connections to modify the database concurrently. This does not guarantee the replica database to be in sync with the master one, so it's not recommended to use this mode for high availability, unless replica-side user connections modify only tables excluded from replication.
105+
106+
## Converting the replica to a regular database
107+
108+
As simple as this:
109+
110+
* gfix -replica none &lt;database&gt;
111+
112+
This isn't strictly required for read-write replicas, but recommended to avoid unexpected replication flow.
113+

lang_helpers/gds_codes.ftn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,8 @@ C --
18421842
PARAMETER (GDS__invalid_timezone_region = 335545214)
18431843
INTEGER*4 GDS__invalid_timezone_id
18441844
PARAMETER (GDS__invalid_timezone_id = 335545215)
1845+
INTEGER*4 GDS__bad_repl_handle
1846+
PARAMETER (GDS__bad_repl_handle = 335545216)
18451847
INTEGER*4 GDS__gfix_db_name
18461848
PARAMETER (GDS__gfix_db_name = 335740929)
18471849
INTEGER*4 GDS__gfix_invalid_sw

lang_helpers/gds_codes.pas

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,8 @@
18371837
gds_invalid_timezone_region = 335545214;
18381838
isc_invalid_timezone_id = 335545215;
18391839
gds_invalid_timezone_id = 335545215;
1840+
isc_bad_repl_handle = 335545216;
1841+
gds_bad_repl_handle = 335545216;
18401842
isc_gfix_db_name = 335740929;
18411843
gds_gfix_db_name = 335740929;
18421844
isc_gfix_invalid_sw = 335740930;

src/alice/alice.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,22 @@ int alice(Firebird::UtilSvc* uSvc)
448448
}
449449
}
450450

451+
if (table->in_sw_value & sw_replica)
452+
{
453+
if (--argc <= 0)
454+
ALICE_error(135); // msg 135: replica mode (none / read_only / read_write) required
455+
456+
ALICE_upper_case(*argv++, string, sizeof(string));
457+
458+
if (!strcmp(string, "NONE"))
459+
tdgbl->ALICE_data.ua_replica_mode = REPL_NONE;
460+
else if (!strcmp(string, ALICE_SW_MODE_RO))
461+
tdgbl->ALICE_data.ua_replica_mode = REPL_READ_ONLY;
462+
else if (!strcmp(string, ALICE_SW_MODE_RW))
463+
tdgbl->ALICE_data.ua_replica_mode = REPL_READ_WRITE;
464+
else
465+
ALICE_error(135); // msg 135: replica mode (none / read_only / read_write) required
466+
}
451467
}
452468

453469
// put this here since to put it above overly complicates the parsing.

0 commit comments

Comments
 (0)