Hi Michael,
Your idea would not just allow us to run transactions in parallel on
the slave, but would also split transactions into pieces that could be
run in parallel. Which would be pretty cool!
But some things may be too complicated to allow for implementation in
a reasonable time.
For example, preventing "consistent read from looking at TXN1 updates
when TXN1 is committed but TXN2 is not", would probably have to be
implemented in the engine, and could be quite tricky.
This could also be a problem: "The recovery layer should then ask the
SE to roll back TXN1 and reapply TXN", where TXN1 was a committed
transaction.
I know of no way to rollback an already committed transaction, unless
maybe it was the last transaction committed.
In general I would say that a solution that requires little, or no,
changes to the storage engines would be best, because they are already
so complicated.
Sorry, I am just pointing out problems without being constructive! :)
Best regards,
Paul
On May 12, 2009, at 2:28 AM, Michael Izioumtchenko wrote:
Clint Byrum wrote:
On May 9, 2009, at 5:53 AM, Paul McCullagh wrote:
As far as I know, the start time of a transaction is not
significant.
Or do you maybe have an example of where this is the case?
If you focus on statement replication, then READ UNCOMMITTED would
possibly cause problems..
However, row based is the hot new thing, right? Row based
replication tells us which rows to update. Thinking about things in
terms of rows has made me think that we can actually do this w/o
rolling back. Of course, row based means it needs to be logged
before knowing the commit TX ID, but that is actually ok. We'll
figure it out, and in the process, we can figure out what ranges of
IDs each transaction modified. This will allow us to decide not to
start transactions that might modify the same rows as currently
running transactions,
I think with row replication the deal is we shouldn't really be
worrying about this.
Imagine the following approach, not necessarily reflecting any
existing replication code, of course,
and assuming something like InnoDB as the storage engine.
- master logs events in the order the rows are updated.
By some magic it also throws away the updates that have been rolled
back.
The update order is determined [mostly] by the storage engine which
has logs, transactions, all this good stuff.
There's no real need to replay the locks on the slave, it has all
already been decided upon and done on the master.
All we need to know is that if we apply the events one by one we'll
have the correct [in every sense] result at any given moment.
The challenge is to apply the events in parallel without losing most
of correctness.
- on the slave the event stream is split into one thread per X where
X is either:
- table
- a logical partition by unique key such as 'all records with
primary_key <X' and 'all records with primary_key >= X' being
separate threads
- with InnoDB it's probably not entirely unfeasible to update
indexes of the same table separately.
- each stream is fed into the engine by a separate thread using
normal interface.
This means a single master transaction is split into a few different
slave transactions which are all committed independently. TXN =>
TXN1 TXN2 ... TXNn.
The engine does locking as usual even though it shouldn't be
necessary.
Let's address some challenges:
1. update order preservation. It seems to be a non issue because
each given record is processed by a single thread which receives the
updates
in the right order. This means that we don't have to preserve the
order of master commits as long as we preserve the order of updates
for each row. This is the case when each table is processed by only
one thread, processing the same table by multiple threads
is considered separately.
2. crash recovery. Standard SE recovery could well leave us with
TXN1 being committed while TXN2 being rolled back.
The recovery layer should then ask the SE to roll back TXN1 and
reapply TXN. The SE should therefore be able to understand
and satisfy such request.
3. consistent read. Something should prevent consistent read from
looking at TXN1 updates when TXN1 is committed but TXN2 is not.
The replication layer could instruct the server to instruct the SE
to ignore certain committed transactions
for consistent read purposes. So again the SE interface has to be
enhanced a bit. If the SE has an idea of transactions and consistent
read, it should be able to accept advice from the server as to which
transactions to exclude from the view.
4. for the logical partitioning case, updates that move records from
one partition to another, for the example above:
UPDATE t SET primary_key = X - 1 where primary_key = X.
Abstractly speaking, if each replication even has an increasing
event id, and also carries the event id from the previous
update of the same row, and we attach to each row the latest event
it has been updated with, then the problem is solved
by comparing row and event labels before applying the event, and
waiting for the proper update to arrive if necessary.
More practically, if we consider the following order of execution on
master:
TXN-A: BEGIN
(1) TXN-A: UPDATE t SET primary_key = X - 1 where primary_key = X
TXN-A: COMMIT
TXN-B: BEGIN
(2) TXN-B: DELETE FROM t where primary_k = X - 1;
(3) TXN-B: INSERT INTO t (primary_key...) VALUES (X - 1,...
TXN-B: COMMIT
with parallelization, the order of TXN-A then TXN-B can easily be
reversed because both TXN-B statements go to one thread,
and the TXN-A statement, to another. Obviously native SE locking on
the slave isn't going to help here unless we also coordinate between
two slave threads to preserve the commit order which defy the whole
parallelization idea.
I'm not 100% sure but what should help here is the replication layer
replacing the UPDATE in the replication stream with
(1a) DELETE FROM t WHERE primary_key = X;
(1b) INSERT INTO t (primary_key...) VALUES (X-1...);
In this case replication thread 1 (primary key <X) gets statements
1b, then 2, then 3;
replication thread 2(primary key >= X) gets statement 1a, and the
end result will be correct even if thread 1 lags behind thread 2.
5. Consistent read again. It looks like if we don't preserve master
commit order, some consistent views read on the slave
will see the database as it has never been in any consistent read
view on the master. In the example above, if thread 2 lags behind,
then there may be a view where we see TXN-B committed but not TXN-A.
One possible answer to this is 'so what?'.
If we care, we shouldn't deliver this view to the user but rather
present him a view from just before TXN-A started.
The problem is passing that
information to the SE, in this case it would be 'for this SELECT,
don't include txn-B to the consistent read view even though it's
committed'.
6. parallel update of indexes of the same table? This could speed up
the inserts considerably but
is hardly possible without serious cooperation from the SE.
7. Multimaster replication and logically equivalent problem of local
updates. I don't know how to address it
but I think if you have multiple masters updating the same records
it's a different ballgame where there surely
must be some forms of addressing the relevant problems.
8. anything else I've missed?
Thank you,
Michael
The opinion(s) stated above, if any, are mine and don't necessarily
reflect those of my employer
thereby achieving what
Robert was suggesting, finding smaller slices than "table" to
parallelize on:
(T1) update foo set x=1 where id=1
(T2) update foo set x=2 where id=5
(T1) update foo set x=1 where id=5 (blocks)
(T2) commit (unblocks T1)
(T1) update foo set x=1 where id=100
(T1) update bar set z=9 where id=1
(T3) update foo set x=3 where id=50
(T3) update foo set x=3 where id=101
(T4) update foo set x=3 where id=100
(T1) commit
(T4) commit
(T3) commit
So, we start T1 and know that it changed row 1, so the range is
1:1 , then start looking forward for its commit id, all the while
queueing up T2-T4's row changes but not starting them. Then we find
row 5 changed by T2. We keep track of its range too. Now we find
that T2 is committed. Next thing we find is that T1 has changed row
5. Its range is now 1:5. Next we should find that it changed row
100, so now 1:100. While T1 was updating table bar, T3 came along
and modified id = 50. This hits the range, so we queue it to start
after T1 commits. We now set T3's range to 50:101. T4 is queued for
starting after T3, because its range it fits inside that range.
Now once T1 commits, T3 is run and committed, then T4.
This means that some things that don't *have* to wait do wait.
However, I'm having a hard time imagining a situation where it
starts a transaction that might end up conflicting. And most of the
time we'll be able to start a lot of operations in parallel.
_______________________________________________
Mailing list: https://launchpad.net/~drizzle-discuss
Post to : [email protected]
Unsubscribe : https://launchpad.net/~drizzle-discuss
More help : https://help.launchpad.net/ListHelp
--
Paul McCullagh
PrimeBase Technologies
www.primebase.org
www.blobstreaming.org
pbxt.blogspot.com
_______________________________________________
Mailing list: https://launchpad.net/~drizzle-discuss
Post to : [email protected]
Unsubscribe : https://launchpad.net/~drizzle-discuss
More help : https://help.launchpad.net/ListHelp