def record_reads(self, app, txid, group_keys): """ Keep track of which entity groups were read in a transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. group_keys: An iterable containing Reference objects. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) insert = self.session.prepare(""" INSERT INTO transactions (txid_hash, operation, namespace, path) VALUES (?, ?, ?, ?) USING TTL {ttl} """.format(ttl=dbconstants.MAX_TX_DURATION * 2)) for group_key in group_keys: if not isinstance(group_key, entity_pb.Reference): group_key = entity_pb.Reference(group_key) args = (tx_partition(app, txid), TxnActions.GET, group_key.name_space(), bytearray(group_key.path().Encode())) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while recording reads in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def delete_entities_tx(self, app, txid, entity_keys): """ Update transaction metadata with new delete operations. Args: app: A string containing an application ID. txid: An integer specifying the transaction ID. entity_keys: A list of entity keys that will be deleted upon commit. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) insert = self.session.prepare(""" INSERT INTO transactions (txid_hash, operation, namespace, path, entity) VALUES (?, ?, ?, ?, ?) USING TTL {ttl} """.format(ttl=dbconstants.MAX_TX_DURATION * 2)) for key in entity_keys: # The None value overwrites previous puts. args = (tx_partition(app, txid), TxnActions.MUTATE, key.name_space(), bytearray(key.path().Encode()), None) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while deleting entities in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def start(self, retries=5): """ Mark the batch as being in progress. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as being started. """ if retries < 0: raise FailedBatch('Retries exhausted while starting batch') insert = SimpleStatement(""" INSERT INTO batch_status (txid_hash, applied, op_id) VALUES (%(txid_hash)s, False, %(op_id)s) IF NOT EXISTS """, retry_policy=NO_RETRIES) parameters = {'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id} try: result = self.session.execute(insert, parameters) except TRANSIENT_CASSANDRA_ERRORS: return self.start(retries=retries - 1) if result.was_applied: return # Make sure this process was responsible for the insert. try: self.is_applied() except (BatchNotOwned, TRANSIENT_CASSANDRA_ERRORS) as batch_failure: raise FailedBatch(str(batch_failure)) except BatchNotFound: return self.start(retries=retries - 1)
def cleanup(self, retries=5): """ Clean up the batch status entry. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as applied. """ if retries < 0: raise FailedBatch('Retries exhausted while cleaning up batch') clear_status = SimpleStatement(""" DELETE FROM batch_status WHERE txid_hash = %(txid_hash)s IF op_id = %(op_id)s """, retry_policy=NO_RETRIES) parameters = {'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id} try: result = yield self.tornado_cassandra.execute(clear_status, parameters) except TRANSIENT_CASSANDRA_ERRORS: yield self.cleanup(retries=retries-1) return if not result.was_applied: raise FailedBatch( 'Unable to clean up batch for {}:{}'.format(self.project, self.txid))
def claim(self): """ Claim a batch so that other processes don't work on it. Raises: FailedBatch if the batch cannot be claimed. """ try: if self.is_applied(): self.applied = True except TRANSIENT_CASSANDRA_ERRORS as error: raise FailedBatch(str(error)) except BatchNotOwned: # This process does not own the batch yet. pass except BatchNotFound: # Make sure another process doesn't try to start. return self.start() update_id = SimpleStatement(""" UPDATE batch_status SET op_id = %(new_op_id)s WHERE txid_hash = %(txid_hash)s IF op_id = %(old_op_id)s """, retry_policy=NO_RETRIES) parameters = {'txid_hash': tx_partition(self.project, self.txid), 'new_op_id': self.op_id, 'old_op_id': self.read_op_id} try: result = self.session.execute(update_id, parameters) assert result.was_applied except (TRANSIENT_CASSANDRA_ERRORS, AssertionError): raise FailedBatch('Unable to claim batch')
def set_applied(self, retries=5): """ Mark the batch as being applied. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as applied. """ if retries < 0: raise FailedBatch('Retries exhausted while updating batch') update_status = SimpleStatement(""" UPDATE batch_status SET applied = True WHERE txid_hash = %(txid_hash)s IF op_id = %(op_id)s """, retry_policy=NO_RETRIES) parameters = {'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id} try: result = self.session.execute(update_status, parameters) if result.was_applied: self.applied = True return except TRANSIENT_CASSANDRA_ERRORS: pass # Application is confirmed below. try: if self.is_applied(): self.applied = True return return self.set_applied(retries=retries - 1) except (BatchNotFound, BatchNotOwned, TRANSIENT_CASSANDRA_ERRORS) as error: raise FailedBatch(str(error))
def delete_entities_tx(self, app, txid, entity_keys): """ Update transaction metadata with new delete operations. Args: app: A string containing an application ID. txid: An integer specifying the transaction ID. entity_keys: A list of entity keys that will be deleted upon commit. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) insert = self.session.prepare(""" INSERT INTO transactions (txid_hash, operation, namespace, path, entity) VALUES (?, ?, ?, ?, ?) USING TTL {ttl} """.format(ttl=dbconstants.MAX_TX_DURATION * 2)) for key in entity_keys: # The None value overwrites previous puts. args = (tx_partition(app, txid), TxnActions.MUTATE, key.name_space(), bytearray(key.path().Encode()), None) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while deleting entities in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def cleanup(self, retries=5): """ Clean up the batch status entry. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as applied. """ if retries < 0: raise FailedBatch('Retries exhausted while cleaning up batch') clear_status = SimpleStatement(""" DELETE FROM batch_status WHERE txid_hash = %(txid_hash)s IF op_id = %(op_id)s """, retry_policy=NO_RETRIES) parameters = { 'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id } try: result = self.session.execute(clear_status, parameters) except TRANSIENT_CASSANDRA_ERRORS: return self.cleanup(retries=retries - 1) if not result.was_applied: raise FailedBatch('Unable to clean up batch for {}:{}'.format( self.project, self.txid))
def record_reads(self, app, txid, group_keys): """ Keep track of which entity groups were read in a transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. group_keys: An iterable containing Reference objects. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) insert = self.session.prepare(""" INSERT INTO transactions (txid_hash, operation, namespace, path) VALUES (?, ?, ?, ?) USING TTL {ttl} """.format(ttl=dbconstants.MAX_TX_DURATION * 2)) for group_key in group_keys: if not isinstance(group_key, entity_pb.Reference): group_key = entity_pb.Reference(group_key) args = (tx_partition(app, txid), TxnActions.GET, group_key.name_space(), bytearray(group_key.path().Encode())) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while recording reads in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def cleanup(self, txid): """ Cleans up the metadata from the finished batch. Args: txid: An integer specifying a transaction ID. """ txid_hash = tx_partition(self.project_id, txid) yield self._delete_mutations(txid) yield self._delete_status(txid_hash)
def cleanup(self, txid): """ Cleans up the metadata from the finished batch. Args: txid: An integer specifying a transaction ID. """ txid_hash = tx_partition(self.project_id, txid) yield self._delete_mutations(txid) yield self._delete_status(txid_hash)
def get_transaction_metadata(self, app, txid): """ Fetch transaction state. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. Returns: A dictionary containing transaction state. """ select = ( 'SELECT namespace, operation, path, start_time, is_xg, in_progress, ' ' entity, task ' 'FROM transactions ' 'WHERE txid_hash = %(txid_hash)s ' ) parameters = {'txid_hash': tx_partition(app, txid)} try: results = yield self.tornado_cassandra.execute(select, parameters) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while inserting entities in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message) metadata = {'puts': {}, 'deletes': [], 'tasks': [], 'reads': set()} for result in results: if result.operation == TxnActions.START: metadata['start'] = result.start_time metadata['is_xg'] = result.is_xg metadata['in_progress'] = set() if metadata['in_progress'] is not None: metadata['in_progress'] = set( struct.unpack('q' * int(len(result.in_progress) / 8), result.in_progress)) if result.operation == TxnActions.MUTATE: key = create_key(app, result.namespace, result.path) if result.entity is None: metadata['deletes'].append(key) else: metadata['puts'][key.Encode()] = result.entity if result.operation == TxnActions.GET: group_key = create_key(app, result.namespace, result.path) metadata['reads'].add(group_key.Encode()) if result.operation == TxnActions.ENQUEUE_TASK: service_id, version_id, task_pb = result.task.split('_', 2) task_metadata = { 'service_id': service_id, 'version_id': version_id, 'task': taskqueue_service_pb.TaskQueueAddRequest(task_pb)} metadata['tasks'].append(task_metadata) raise gen.Return(metadata)
def get_transaction_metadata(self, app, txid): """ Fetch transaction state. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. Returns: A dictionary containing transaction state. """ select = ( 'SELECT namespace, operation, path, start_time, is_xg, in_progress, ' ' entity, task ' 'FROM transactions ' 'WHERE txid_hash = %(txid_hash)s ') parameters = {'txid_hash': tx_partition(app, txid)} try: results = yield self.tornado_cassandra.execute(select, parameters) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while inserting entities in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message) metadata = {'puts': {}, 'deletes': [], 'tasks': [], 'reads': set()} for result in results: if result.operation == TxnActions.START: metadata['start'] = result.start_time metadata['is_xg'] = result.is_xg metadata['in_progress'] = set() if metadata['in_progress'] is not None: metadata['in_progress'] = set( struct.unpack('q' * int(len(result.in_progress) / 8), result.in_progress)) if result.operation == TxnActions.MUTATE: key = create_key(app, result.namespace, result.path) if result.entity is None: metadata['deletes'].append(key) else: metadata['puts'][key.Encode()] = result.entity if result.operation == TxnActions.GET: group_key = create_key(app, result.namespace, result.path) metadata['reads'].add(group_key.Encode()) if result.operation == TxnActions.ENQUEUE_TASK: service_id, version_id, task_pb = result.task.split('_', 2) task_metadata = { 'service_id': service_id, 'version_id': version_id, 'task': taskqueue_service_pb.TaskQueueAddRequest(task_pb) } metadata['tasks'].append(task_metadata) raise gen.Return(metadata)
def is_applied(self, retries=5): """ Fetch the status of the batch. Args: retries: The number of times to retry after failures. Returns: A boolean indicating whether or not the batch has been applied. Raises: BatchNotFound if the batch cannot be found. BatchNotOwned if a different process owns the batch. """ if self.applied: raise gen.Return(True) get_status = """ SELECT applied, op_id FROM batch_status WHERE txid_hash = %(txid_hash)s """ query = SimpleStatement(get_status, retry_policy=BASIC_RETRIES, consistency_level=ConsistencyLevel.SERIAL) parameters = {'txid_hash': tx_partition(self.project, self.txid)} try: results = yield self.tornado_cassandra.execute( query, parameters=parameters) result = results[0] if result.op_id != self.op_id: self.read_op_id = result.op_id raise BatchNotOwned('{} does not match {}'.format( self.op_id, result.op_id)) raise gen.Return(result.applied) except TRANSIENT_CASSANDRA_ERRORS: retries_left = retries - 1 if retries_left < 0: raise logger.debug('Unable to read batch status. Retrying.') is_applied = yield self.is_applied(retries=retries_left) raise gen.Return(is_applied) except IndexError: raise BatchNotFound('Batch for {}:{} not found'.format( self.project, self.txid))
def is_applied(self, retries=5): """ Fetch the status of the batch. Args: retries: The number of times to retry after failures. Returns: A boolean indicating whether or not the batch has been applied. Raises: BatchNotFound if the batch cannot be found. BatchNotOwned if a different process owns the batch. """ if self.applied: raise gen.Return(True) get_status = """ SELECT applied, op_id FROM batch_status WHERE txid_hash = %(txid_hash)s """ query = SimpleStatement(get_status, retry_policy=BASIC_RETRIES, consistency_level=ConsistencyLevel.SERIAL) parameters = {'txid_hash': tx_partition(self.project, self.txid)} try: results = yield self.tornado_cassandra.execute( query, parameters=parameters) result = results[0] if result.op_id != self.op_id: self.read_op_id = result.op_id raise BatchNotOwned( '{} does not match {}'.format(self.op_id, result.op_id)) raise gen.Return(result.applied) except TRANSIENT_CASSANDRA_ERRORS: retries_left = retries - 1 if retries_left < 0: raise logger.debug('Unable to read batch status. Retrying.') is_applied = yield self.is_applied(retries=retries_left) raise gen.Return(is_applied) except IndexError: raise BatchNotFound( 'Batch for {}:{} not found'.format(self.project, self.txid))
def set_applied(self, retries=5): """ Mark the batch as being applied. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as applied. """ if retries < 0: raise FailedBatch('Retries exhausted while updating batch') update_status = SimpleStatement(""" UPDATE batch_status SET applied = True WHERE txid_hash = %(txid_hash)s IF op_id = %(op_id)s """, retry_policy=NO_RETRIES) parameters = { 'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id } try: result = yield self.tornado_cassandra.execute( update_status, parameters) if result.was_applied: self.applied = True return except TRANSIENT_CASSANDRA_ERRORS: pass # Application is confirmed below. try: self.applied = yield self.is_applied() if self.applied: return yield self.set_applied(retries=retries - 1) return except (BatchNotFound, BatchNotOwned, TRANSIENT_CASSANDRA_ERRORS) as error: raise FailedBatch(str(error))
def start(self, retries=5): """ Mark the batch as being in progress. Args: retries: The number of times to retry after failures. Raises: FailedBatch if the batch cannot be marked as being started. """ if retries < 0: raise FailedBatch('Retries exhausted while starting batch') insert = SimpleStatement(""" INSERT INTO batch_status (txid_hash, applied, op_id) VALUES (%(txid_hash)s, False, %(op_id)s) IF NOT EXISTS """, retry_policy=NO_RETRIES) parameters = { 'txid_hash': tx_partition(self.project, self.txid), 'op_id': self.op_id } try: result = yield self.tornado_cassandra.execute(insert, parameters) except TRANSIENT_CASSANDRA_ERRORS: yield self.start(retries=retries - 1) return if result.was_applied: return # Make sure this process was responsible for the insert. try: yield self.is_applied() except (BatchNotOwned, TRANSIENT_CASSANDRA_ERRORS) as batch_failure: raise FailedBatch(str(batch_failure)) except BatchNotFound: yield self.start(retries=retries - 1) return
def add_transactional_tasks(self, app, txid, tasks, service_id, version_id): """ Add tasks to be enqueued upon the completion of a transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. tasks: A list of TaskQueueAddRequest objects. service_id: A string specifying the client's service ID. version_id: A string specifying the client's version ID. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) query_str = ( 'INSERT INTO transactions (txid_hash, operation, namespace, path, task) ' 'VALUES (?, ?, ?, ?, ?) ' 'USING TTL {ttl}' ).format(ttl=dbconstants.MAX_TX_DURATION * 2) insert = self.session.prepare(query_str) for task in tasks: task.clear_transaction() # The path for the task entry doesn't matter as long as it's unique. path = bytearray(str(uuid.uuid4())) task_payload = '_'.join([service_id, version_id, task.Encode()]) args = (tx_partition(app, txid), TxnActions.ENQUEUE_TASK, '', path, task_payload) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while adding tasks in a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def add_transactional_tasks(self, app, txid, tasks, service_id, version_id): """ Add tasks to be enqueued upon the completion of a transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. tasks: A list of TaskQueueAddRequest objects. service_id: A string specifying the client's service ID. version_id: A string specifying the client's version ID. """ batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM, retry_policy=BASIC_RETRIES) query_str = ( 'INSERT INTO transactions (txid_hash, operation, namespace, path, task) ' 'VALUES (?, ?, ?, ?, ?) ' 'USING TTL {ttl}' ).format(ttl=dbconstants.MAX_TX_DURATION * 2) insert = self.session.prepare(query_str) for task in tasks: task.clear_transaction() # The path for the task entry doesn't matter as long as it's unique. path = bytearray(str(uuid.uuid4())) task_payload = '_'.join([service_id, version_id, task.Encode()]) args = (tx_partition(app, txid), TxnActions.ENQUEUE_TASK, '', path, task_payload) batch.add(insert, args) try: yield self.tornado_cassandra.execute(batch) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while adding tasks in a transaction' logging.exception(message) raise AppScaleDBConnectionError(message)
def transactional_tasks_count(self, app, txid): """ Count the number of existing tasks associated with the transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. Returns: An integer specifying the number of existing tasks. """ select = ('SELECT count(*) FROM transactions ' 'WHERE txid_hash = %(txid_hash)s ' 'AND operation = %(operation)s') parameters = { 'txid_hash': tx_partition(app, txid), 'operation': TxnActions.ENQUEUE_TASK } try: result = yield self.tornado_cassandra.execute(select, parameters) raise gen.Return(result[0].count) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while fetching task count' logger.exception(message) raise AppScaleDBConnectionError(message)
def transactional_tasks_count(self, app, txid): """ Count the number of existing tasks associated with the transaction. Args: app: A string specifying an application ID. txid: An integer specifying a transaction ID. Returns: An integer specifying the number of existing tasks. """ select = ( 'SELECT count(*) FROM transactions ' 'WHERE txid_hash = %(txid_hash)s ' 'AND operation = %(operation)s' ) parameters = {'txid_hash': tx_partition(app, txid), 'operation': TxnActions.ENQUEUE_TASK} try: result = yield self.tornado_cassandra.execute(select, parameters) raise gen.Return(result[0].count) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while fetching task count' logger.exception(message) raise AppScaleDBConnectionError(message)
def resolve(self, txid, composite_indexes): """ Resolves a large batch for a given transaction. Args: txid: An integer specifying a transaction ID. composite_indexes: A list of CompositeIndex objects. """ txid_hash = tx_partition(self.project_id, txid) new_op_id = uuid.uuid4() try: batch_status = yield self._get_status(txid_hash) except BatchNotFound: # Make sure another process doesn't try to commit the transaction. yield self._insert(txid_hash, new_op_id) return old_op_id = batch_status.op_id yield self._update_op_id(txid_hash, batch_status.applied, old_op_id, new_op_id) if batch_status.applied: # Make sure all the mutations in the batch have been applied. yield self._apply_mutations(txid, composite_indexes)
def resolve(self, txid, composite_indexes): """ Resolves a large batch for a given transaction. Args: txid: An integer specifying a transaction ID. composite_indexes: A list of CompositeIndex objects. """ txid_hash = tx_partition(self.project_id, txid) new_op_id = uuid.uuid4() try: batch_status = yield self._get_status(txid_hash) except BatchNotFound: # Make sure another process doesn't try to commit the transaction. yield self._insert(txid_hash, new_op_id) return old_op_id = batch_status.op_id yield self._update_op_id(txid_hash, batch_status.applied, old_op_id, new_op_id) if batch_status.applied: # Make sure all the mutations in the batch have been applied. yield self._apply_mutations(txid, composite_indexes)
def start_transaction(self, app, txid, is_xg, in_progress): """ Persist transaction metadata. Args: app: A string containing an application ID. txid: An integer specifying the transaction ID. is_xg: A boolean specifying that the transaction is cross-group. in_progress: An iterable containing transaction IDs. """ if in_progress: in_progress_bin = bytearray( struct.pack('q' * len(in_progress), *in_progress)) else: in_progress_bin = None insert = ( 'INSERT INTO transactions (txid_hash, operation, namespace, path,' ' start_time, is_xg, in_progress)' 'VALUES (%(txid_hash)s, %(operation)s, %(namespace)s, %(path)s,' ' %(start_time)s, %(is_xg)s, %(in_progress)s)' 'USING TTL {ttl}').format(ttl=dbconstants.MAX_TX_DURATION * 2) parameters = { 'txid_hash': tx_partition(app, txid), 'operation': TxnActions.START, 'namespace': '', 'path': bytearray(''), 'start_time': datetime.datetime.utcnow(), 'is_xg': is_xg, 'in_progress': in_progress_bin } try: yield self.tornado_cassandra.execute(insert, parameters) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while starting a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def start_transaction(self, app, txid, is_xg, in_progress): """ Persist transaction metadata. Args: app: A string containing an application ID. txid: An integer specifying the transaction ID. is_xg: A boolean specifying that the transaction is cross-group. in_progress: An iterable containing transaction IDs. """ if in_progress: in_progress_bin = bytearray( struct.pack('q' * len(in_progress), *in_progress)) else: in_progress_bin = None insert = ( 'INSERT INTO transactions (txid_hash, operation, namespace, path,' ' start_time, is_xg, in_progress)' 'VALUES (%(txid_hash)s, %(operation)s, %(namespace)s, %(path)s,' ' %(start_time)s, %(is_xg)s, %(in_progress)s)' 'USING TTL {ttl}' ).format(ttl=dbconstants.MAX_TX_DURATION * 2) parameters = {'txid_hash': tx_partition(app, txid), 'operation': TxnActions.START, 'namespace': '', 'path': bytearray(''), 'start_time': datetime.datetime.utcnow(), 'is_xg': is_xg, 'in_progress': in_progress_bin} try: yield self.tornado_cassandra.execute(insert, parameters) except dbconstants.TRANSIENT_CASSANDRA_ERRORS: message = 'Exception while starting a transaction' logger.exception(message) raise AppScaleDBConnectionError(message)
def claim(self): """ Claim a batch so that other processes don't work on it. Raises: FailedBatch if the batch cannot be claimed. """ try: if self.is_applied(): self.applied = True except TRANSIENT_CASSANDRA_ERRORS as error: raise FailedBatch(str(error)) except BatchNotOwned: # This process does not own the batch yet. pass except BatchNotFound: # Make sure another process doesn't try to start. return self.start() update_id = SimpleStatement(""" UPDATE batch_status SET op_id = %(new_op_id)s WHERE txid_hash = %(txid_hash)s IF op_id = %(old_op_id)s """, retry_policy=NO_RETRIES) parameters = { 'txid_hash': tx_partition(self.project, self.txid), 'new_op_id': self.op_id, 'old_op_id': self.read_op_id } try: result = self.session.execute(update_id, parameters) assert result.was_applied except (TRANSIENT_CASSANDRA_ERRORS, AssertionError): raise FailedBatch('Unable to claim batch')