Skip to content

Conversation

dyemanov
Copy link
Member

@dyemanov dyemanov commented Jan 8, 2019

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.

@AppVeyorBot
Copy link

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.
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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);
Copy link
Contributor

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.

@aafemt
Copy link
Contributor

aafemt commented Jan 10, 2019

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);
Copy link
Contributor

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?..

Copy link
Member Author

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.

@aafemt
Copy link
Contributor

aafemt commented Jan 10, 2019

A lot of changes seems to be completely unrelated to replication. Could they go to a separate PR?

@dyemanov
Copy link
Member Author

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?

Yes, it's expected after Beta 1. You may provide a pull request.

@dyemanov
Copy link
Member Author

A lot of changes seems to be completely unrelated to replication. Could they go to a separate PR?

They're all related, IIRC (even if indirectly).

@aafemt
Copy link
Contributor

aafemt commented Jan 13, 2019

They're all related, IIRC (even if indirectly).

I really cannot see relation between replication and renaming of Win32DirItr into Win32DirIterator for example.

@dyemanov
Copy link
Member Author

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.

@aafemt
Copy link
Contributor

aafemt commented Jan 13, 2019 via email

@dyemanov
Copy link
Member Author

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).

@AlexPeshkoff
Copy link
Member

AlexPeshkoff commented Jan 13, 2019 via email

@aafemt
Copy link
Contributor

aafemt commented Jan 13, 2019 via email

@AppVeyorBot
Copy link

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>
Copy link
Member

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 @?

Copy link
Member Author

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.

Copy link
Member

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 ?

Copy link
Member

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 ?

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

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

@dyemanov dyemanov merged commit 932ca51 into FirebirdSQL:master Jan 25, 2019
@dyemanov
Copy link
Member Author

PR was merged but feel free to post any further reviews here, they will be noted/answered.

@aafemt
Copy link
Contributor

aafemt commented Feb 20, 2019

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()
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

@dyemanov dyemanov Feb 21, 2019

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.

Copy link
Contributor

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:

  1. Transaction stared savepoint 1 - ignored by replication module because transaction is still read-only
  2. select started savepoint 2 - the same
  3. select release savepoint 2 - the same
  4. update started savepoint 3 - still nothing
  5. update changed a record - replication modile at last is initialized, allocating replicator (ReplTransaction in Avalerion) and call updateRecord()
  6. 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.

@dyemanov
Copy link
Member Author

Isn't upgrading of record format on source side just a waste of time, especially in REPL_store()?

If the record is already in the latest format, upgrade is skipped. Are you worried about the redundant MET_current() call?

@aafemt
Copy link
Contributor

aafemt commented Feb 21, 2019

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;
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants