Eugene Koifman created HIVE-13395:
-------------------------------------
Summary: Lost Update problem in ACID
Key: HIVE-13395
URL: https://issues.apache.org/jira/browse/HIVE-13395
Project: Hive
Issue Type: Bug
Components: Transactions
Affects Versions: 2.0.0, 1.2.0
Reporter: Eugene Koifman
Assignee: Eugene Koifman
Priority: Critical
ACID users can run into Lost Update problem.
In Hive 1.2, Driver.recordValidTxns() (which records the snapshot to use for
the query) is called in Driver.compile().
Now suppose to concurrent "update T set x = x + 1" are executed. (for
simplicity assume there is exactly 1 row in T)
What can happen is that both compile at the same time (more precisely before
acquireLocksAndOpenTxn() in runInternal() is called) and thus will lock in the
same snapshot, say the value of x = 7 in this snapshot.
Now 1 will get the lock on the row, the second will block.
Now 1, makes x = 8 and commits.
Now 2 proceeds and makes x = 8 again since in it's snapshot x is still 7.
This specific issue is solved in Hive 1.3/2.0 (HIVE-11077 which is a large
patch that deals with multi-statement txns) by moving recordValidTxns() after
locks are acquired which reduces the likelihood of this but doesn't eliminate
the problem.
----
Even in 1.3 version of the code, you could have the same issue. Assume the
same 2 queries:
Both start a txn, say txnid 9 and 10. Say 10 gets the lock first, 9 blocks.
10 updates the row (so x = 8) and thus ReaderKey.currentTransactionId=10.
10 commits.
Now 9 can proceed and it will get a snapshot that includes 10, i.e. it will see
x = 8 and it will write x = 9, but it will set ReaderKey.currentTransactionId =
9. Thus when merge logic runs, it will see x = 8 is the later version of this
row, i.e. lost update.
The problem is that locks alone are insufficient for MVCC architecture.
----
At lower level Row ID has (originalTransactionId, rowid, bucket id,
currentTransactionId) and since on update/delete we do a table scan, we could
check that we are about to write a row with currentTransactionId <
(currentTransactionId of row we've read) and fail the query. Currently,
currentTransactionId is not surfaced at higher level where this check can be
made.
This would not work (efficiently) longer term where we want to support fast
update on user defined PK vis streaming ingest.
Also, this would not work with multi statement txns since in that case we'd
lock in the snapshot at the start of the txn, but then 2nd, 3rd etc queries
would use the same snapshot and the locks for these queries would be acquired
after the snapshot is locked in so this would be the same situation as pre
HIVE-11077.
----
A more robust solution (commonly used with MVCC) is to keep track of start and
commit time (logical counter) or each transaction to detect if two txns
overlap. The 2nd part is to keep track of write-set, i.e. which data (rows,
partitions, whatever appropriate level of granularity is) were modified by any
txn and if 2 txns overlap in time and wrote the same element, abort later one.
This is called first-committer-wins rule. This requires a MS DB schema change
It would be most convenient to use the same sequence for txnId, start and
commit time (in which case txnid=start time). In this case we'd need to add 1
filed to TXNS table. The complication here is that we'll be using elements of
the sequence faster and they are used as part of file name of delta and base
dir and currently limited to 7 digits which can be exceeded. So this would
require some thought to handling upgrade/migration.
Also, write-set tracking requires either additional metastore table or keeping
info in HIVE_LOCKS around longer with new state.
----
In the short term, on SQL side of things we could (in auto commit mode only)
acquire the locks first and then open the txn AND update these locks with txn
id.
This implies another Thrift change to pass in lockId to openTxn.
The same would not work for Streaming API since it opens several txns at once
and then acquires locks for each.
(Not sure if that's is an issue or not since Streaming only does Insert).
Either way this feels hacky.
----
Here is one simple example why we need Write-Set tracking for multi-statement
txns
Consider transactions T ~1~ and T ~2~:
T ~1~: r ~1~\[x] -> w ~1~\[y] -> c ~1~
T ~2~: w ~2~\[x] -> w ~2~\[y] -> c ~2~
Suppose the order of operations is r ~1~\[x] w ~2~\[x].... then a conventional
R/W lock manager w/o MVCSS will block the write from T ~2~
With MVCC we don't want readers to interfere with writers and so the following
schedule is possible (because Hive's semi-shared (write) don't conflict with
shared (read) locks) in Hive's current implementation.
r ~1~\[x] w ~2~\[x] w ~2~\[y] c ~2~ w ~1~\[y] c ~1~
By the time w ~1~\[y] happens, T ~2~ has committed and released it's locks.
But this is a lost update if c ~1~ is allowed to commit. That's where
write-set tracking comes in.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)