-
-
Notifications
You must be signed in to change notification settings - Fork 256
Replication support #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replication support #182
Conversation
❌ Build firebird 1.0.387 failed (commit fe1b5f00eb by @dyemanov) |
|
||
## Setting up the master side | ||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why separate config file with the same syntax as databases.conf instead of databases.conf itself? It has no sense to distribute database's parameters into several files.
I would understand if it was plugin's config file, but now it AFAIU is read by engine itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reasons are partially historical (initial implementation was for v2.5 without per-database configuration) and partially intentional - to separate purely optional feature from the "core" configuration. But it can be changed if your suggestion is supported by others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that for users would be more convenient to have all database-related settings in one place.
|
||
// Replication interfaces | ||
|
||
interface ReplicatedRecord : Versioned |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no method to get record's format or any other way to get separate fields values. Does it exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't exist yet, but I plan to add it after Beta. Your suggestions are appreciated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avalerion uses this:
class IField
{
public:
virtual bool hasData() = 0;
virtual const char* getName() = 0;
virtual int getType() = 0;
virtual int getScale() = 0;
virtual int getLength() = 0;
virtual int getCharset() = 0;
virtual bool isNull() = 0;
virtual void* getData() = 0;
virtual bool next() = 0;
};
class IRecord
{
public:
virtual const char* getSchemaName() = 0;
virtual const char* getTableName() = 0;
virtual long long getNumber() = 0;
virtual IField* getField() = 0;
};
Where IField is actually enumerator. I would like to have also "bool isKey()" in it.
Status getStatus(); | ||
|
||
ReplicatedTransaction startTransaction(int64 number); | ||
boolean cleanupTransaction(int64 number); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments about "what this method is supposed to do and when is called" would be very appreciated.
I see interfaces for CDC, but I don't see a new plugin type for CDC. It was supposed to be a plugin, wasn't it? |
|
||
boolean storeBlob(ISC_QUAD blobId, ReplicatedBlob blob); | ||
|
||
boolean executeSql(const string sql); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is character set of this string? Could it be fixed UTF-8, please?..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fixed UTF-8.
A lot of changes seems to be completely unrelated to replication. Could they go to a separate PR? |
Yes, it's expected after Beta 1. You may provide a pull request. |
They're all related, IIRC (even if indirectly). |
I really cannot see relation between replication and renaming of Win32DirItr into Win32DirIterator for example. |
Yep, sorry for that. I used to code a different class in other forks and discovered Win32DirItr only during backporting. As it wasn't used in our codebase before replication, I've made some adjustments (including better readability). I understand they may complicate the review process, but I'd rather prefer to keep them together with replication. |
It wasn't used because the codebase contains TWO classes for directory iteration.
|
The one inside PathUtils was created first (circa FB 1.5), then Jim ignored it and created ScanDir, then I ignored both and created DirectoryWalker ;-) I've fixed my mistake now, but I'm not going to fix the remaining duplication (at least not in this PR). |
On 1/13/19 6:50 PM, Dmitry Yemanov wrote:
It wasn't used because the codebase contains TWO classes for
directory iteration.
The one inside PathUtils was created first (circa FB 1.5), then Jim
ignored it and created ScanDir, then I ignored both and created
DirectoryWalker ;-) I've fixed my mistake now, but I'm not going to
fix the remaining duplication (at least not in this PR).
Yes, that dup definitely has nothing to do with replication.
|
And that's exactly what I said: this cleanup and renaming should better go to a separate PR.
|
❌ Build firebird 1.0.396 failed (commit 86bbe9a5a1 by @dyemanov) |
# Connection string to the replica database (used for synchronous replication only). | ||
# Expected format: | ||
# | ||
# [<login>:<password>@]<database connection string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about login and password with "special" characters as :
and @
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, you may omit both and use ISC_USER/ISC_PASSWORD instead. I can also add support for the quotes, i.e. "myn@me":"my:pwd". But speaking honestly, I'm not satisfied with the current solution, maybe you guys will raise some clever idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... is it setting with value only, no name ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, i see it below. It is "sync_replica".
Why not break it into 3 parts: login\password\connection string ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hvlad, do you mean three different settings? How to define multiple replicas in this case? Now you can specify:
sync_replica user1:pwd1@/my/first/replica
sync_replica user2:pwd2@/my/second/replica
sync_replica user3:pwd3@/my/third/replica
This may be not very elegant, but with three settings per each replica it's going to be a mess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as idea, maybe we could use nested sections:
database = /my/db
{
sync_replica = /my/first/replica
{
user = user1
password = pwd1
## may be other connection options
}
}
but I don't know whether such nesting is supported by our config classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, nested sections should work
PR was merged but feel free to post any further reviews here, they will be noted/answered. |
Isn't upgrading of record format on source side just a waste of time, especially in REPL_store()? |
return m_replicator->startSavepoint(this) ? FB_TRUE : FB_FALSE; | ||
} | ||
|
||
FB_BOOLEAN releaseSavepoint() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firebird 2.5 uses "relative" comit/rollback of savepoints which caused some bugs. Firebird 4 uses "absolute" rollback to savepoint with given number. Shouldn't this piece of code be changed accordingly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIU, you speak about looper savepoints. But these methods behave as SAVEPOINT, RELEASE SAVEPOINT and ROLLBACK TO SAVEPOINT statements. How they're implemented internally is irrelevant, provided that all changes between "start" and "rollback" are undone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just see that in ensureSavepoint() you start whole stack of savepoints that is unnecessary, IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Priorly replicated savepoints are not replicated again. The rest of the stack is needed to release/rollback them later. I'm open to any optimization ideas here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of the stack is needed to release/rollback them later.
Nope. It is easy to handle release of "never started savepoints" by full rollback. Here is Avalerion's sequence of calls:
- Transaction stared savepoint 1 - ignored by replication module because transaction is still read-only
- select started savepoint 2 - the same
- select release savepoint 2 - the same
- update started savepoint 3 - still nothing
- update changed a record - replication modile at last is initialized, allocating replicator (ReplTransaction in Avalerion) and call updateRecord()
- update rollback or release savepoint 3 - plugin::rollback/releaseSavepoint() is called and replication plugin scan internal list of savepoints (in asynchronous mode only) looking for number 3. When not found, it assumes that whole work from beginning is done or rolled back and act accordingly.
In your case at point 5 (AFAIU) startSavepoint() in replication module will be called several times which is avoidable. Savepoint is replicated at moment when plugin::releaseSavepoint() has been successfully called so additional marking of it as "replicated" is unnecesary because the flag is immediatelly cleaned up by Savepoint::release() which is called right after that.
If the record is already in the latest format, upgrade is skipped. Are you worried about the redundant MET_current() call? |
No, it seems to be lightweight enough, but I'm worried about contrary case in which "internal" part of replication does work that may be not necessary for "external" part (i.e. plugin). I understand, that this code is oriented to your particular implementation of capture plugin, but there can be others. Some of them may be even interested in old BLOB content (or not interested at all). |
// Replicate the entire stack of active savepoints (excluding priorly replicated), | ||
// starting with the oldest ones | ||
|
||
HalfStaticArray<Savepoint*, 16> stack; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stack has no point: because of "relative" savepoint handling you need only count of them but not order. On success result doesn't matter and in case of any error complete replicator is destroyed and "replicated" flag in savepoint is irrelevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed about the count only (the very-first implementation was passing savepoint IDs, the stack still remains). The "replicated" flag is relevant to avoid starting priorly started (upper level) savepoints. Imagine the first update executing inside savepoint frame {1, 2, 4, 10}, then savepoint 10 is released and the next update is executed inside savepoint frame {1, 2, 4, 12}. Savepoints {1, 2, 4} were already started and should not be replicated again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I said that it is irrelevant in the case if error happen.
No error retirned from startSavepoint() - whole savepoint stack is marked as "replicated" and no problem here.
On error all savepoints are rolled back and dropped.
Still work in progress, but suitable as feature preview for Beta 1. Discussion will follow in fb-devel, questions may be raised here as well.