2323import google .cloud .bigtable .data .exceptions as bt_exceptions
2424from google .cloud .bigtable .data ._helpers import _attempt_timeout_generator
2525from google .cloud .bigtable .data ._helpers import _retry_exception_factory
26+ from google .cloud .bigtable .data ._helpers import TrackedBackoffGenerator
2627from google .cloud .bigtable .data .mutations import _MUTATE_ROWS_REQUEST_MUTATION_LIMIT
2728from google .cloud .bigtable .data .mutations import _EntryWithProto
2829from google .cloud .bigtable .data ._cross_sync import CrossSync
2930
3031if TYPE_CHECKING :
3132 from google .cloud .bigtable .data .mutations import RowMutationEntry
33+ from google .cloud .bigtable .data ._metrics import ActiveOperationMetric
3234 from google .cloud .bigtable_v2 .services .bigtable .client import (
3335 BigtableClient as GapicClientType ,
3436 )
@@ -54,6 +56,8 @@ class _MutateRowsOperation:
5456 operation_timeout: the timeout to use for the entire operation, in seconds.
5557 attempt_timeout: the timeout to use for each mutate_rows attempt, in seconds.
5658 If not specified, the request will run until operation_timeout is reached.
59+ metric: the metric object representing the active operation
60+ retryable_exceptions: a list of exceptions that should be retried
5761 """
5862
5963 def __init__ (
@@ -63,6 +67,7 @@ def __init__(
6367 mutation_entries : list ["RowMutationEntry" ],
6468 operation_timeout : float ,
6569 attempt_timeout : float | None ,
70+ metric : ActiveOperationMetric ,
6671 retryable_exceptions : Sequence [type [Exception ]] = (),
6772 ):
6873 total_mutations = sum ((len (entry .mutations ) for entry in mutation_entries ))
@@ -75,7 +80,7 @@ def __init__(
7580 self .is_retryable = retries .if_exception_type (
7681 * retryable_exceptions , bt_exceptions ._MutateRowsIncomplete
7782 )
78- sleep_generator = retries . exponential_sleep_generator (0.01 , 2 , 60 )
83+ sleep_generator = TrackedBackoffGenerator (0.01 , 2 , 60 )
7984 self ._operation = lambda : CrossSync ._Sync_Impl .retry_target (
8085 self ._run_attempt ,
8186 self .is_retryable ,
@@ -89,37 +94,40 @@ def __init__(
8994 self .mutations = [_EntryWithProto (m , m ._to_pb ()) for m in mutation_entries ]
9095 self .remaining_indices = list (range (len (self .mutations )))
9196 self .errors : dict [int , list [Exception ]] = {}
97+ metric .backoff_generator = sleep_generator
98+ self ._operation_metric = metric
9299
93100 def start (self ):
94101 """Start the operation, and run until completion
95102
96103 Raises:
97104 MutationsExceptionGroup: if any mutations failed"""
98- try :
99- self ._operation ()
100- except Exception as exc :
101- incomplete_indices = self .remaining_indices .copy ()
102- for idx in incomplete_indices :
103- self ._handle_entry_error (idx , exc )
104- finally :
105- all_errors : list [Exception ] = []
106- for idx , exc_list in self .errors .items ():
107- if len (exc_list ) == 0 :
108- raise core_exceptions .ClientError (
109- f"Mutation { idx } failed with no associated errors"
105+ with self ._operation_metric :
106+ try :
107+ self ._operation ()
108+ except Exception as exc :
109+ incomplete_indices = self .remaining_indices .copy ()
110+ for idx in incomplete_indices :
111+ self ._handle_entry_error (idx , exc )
112+ finally :
113+ all_errors : list [Exception ] = []
114+ for idx , exc_list in self .errors .items ():
115+ if len (exc_list ) == 0 :
116+ raise core_exceptions .ClientError (
117+ f"Mutation { idx } failed with no associated errors"
118+ )
119+ elif len (exc_list ) == 1 :
120+ cause_exc = exc_list [0 ]
121+ else :
122+ cause_exc = bt_exceptions .RetryExceptionGroup (exc_list )
123+ entry = self .mutations [idx ].entry
124+ all_errors .append (
125+ bt_exceptions .FailedMutationEntryError (idx , entry , cause_exc )
126+ )
127+ if all_errors :
128+ raise bt_exceptions .MutationsExceptionGroup (
129+ all_errors , len (self .mutations )
110130 )
111- elif len (exc_list ) == 1 :
112- cause_exc = exc_list [0 ]
113- else :
114- cause_exc = bt_exceptions .RetryExceptionGroup (exc_list )
115- entry = self .mutations [idx ].entry
116- all_errors .append (
117- bt_exceptions .FailedMutationEntryError (idx , entry , cause_exc )
118- )
119- if all_errors :
120- raise bt_exceptions .MutationsExceptionGroup (
121- all_errors , len (self .mutations )
122- )
123131
124132 def _run_attempt (self ):
125133 """Run a single attempt of the mutate_rows rpc.
@@ -128,6 +136,7 @@ def _run_attempt(self):
128136 _MutateRowsIncomplete: if there are failed mutations eligible for
129137 retry after the attempt is complete
130138 GoogleAPICallError: if the gapic rpc fails"""
139+ self ._operation_metric .start_attempt ()
131140 request_entries = [self .mutations [idx ].proto for idx in self .remaining_indices ]
132141 active_request_indices = {
133142 req_idx : orig_idx
0 commit comments