3131
3232if TYPE_CHECKING :
3333 from google .cloud .bigtable .data .mutations import RowMutationEntry
34+ from google .cloud .bigtable .data ._metrics import ActiveOperationMetric
3435
3536 if CrossSync .is_async :
3637 from google .cloud .bigtable_v2 .services .bigtable .async_client import (
@@ -68,6 +69,8 @@ class _MutateRowsOperationAsync:
6869 operation_timeout: the timeout to use for the entire operation, in seconds.
6970 attempt_timeout: the timeout to use for each mutate_rows attempt, in seconds.
7071 If not specified, the request will run until operation_timeout is reached.
72+ metric: the metric object representing the active operation
73+ retryable_exceptions: a list of exceptions that should be retried
7174 """
7275
7376 @CrossSync .convert
@@ -78,6 +81,7 @@ def __init__(
7881 mutation_entries : list ["RowMutationEntry" ],
7982 operation_timeout : float ,
8083 attempt_timeout : float | None ,
84+ metric : ActiveOperationMetric ,
8185 retryable_exceptions : Sequence [type [Exception ]] = (),
8286 ):
8387 # check that mutations are within limits
@@ -97,13 +101,13 @@ def __init__(
97101 # Entry level errors
98102 bt_exceptions ._MutateRowsIncomplete ,
99103 )
100- sleep_generator = retries .exponential_sleep_generator (0.01 , 2 , 60 )
101104 self ._operation = lambda : CrossSync .retry_target (
102105 self ._run_attempt ,
103106 self .is_retryable ,
104- sleep_generator ,
107+ metric . backoff_generator ,
105108 operation_timeout ,
106- exception_factory = _retry_exception_factory ,
109+ exception_factory = metric .track_terminal_error (_retry_exception_factory ),
110+ on_error = metric .track_retryable_error ,
107111 )
108112 # initialize state
109113 self .timeout_generator = _attempt_timeout_generator (
@@ -112,6 +116,8 @@ def __init__(
112116 self .mutations = [_EntryWithProto (m , m ._to_pb ()) for m in mutation_entries ]
113117 self .remaining_indices = list (range (len (self .mutations )))
114118 self .errors : dict [int , list [Exception ]] = {}
119+ # set up metrics
120+ self ._operation_metric = metric
115121
116122 @CrossSync .convert
117123 async def start (self ):
@@ -121,34 +127,35 @@ async def start(self):
121127 Raises:
122128 MutationsExceptionGroup: if any mutations failed
123129 """
124- try :
125- # trigger mutate_rows
126- await self ._operation ()
127- except Exception as exc :
128- # exceptions raised by retryable are added to the list of exceptions for all unfinalized mutations
129- incomplete_indices = self .remaining_indices .copy ()
130- for idx in incomplete_indices :
131- self ._handle_entry_error (idx , exc )
132- finally :
133- # raise exception detailing incomplete mutations
134- all_errors : list [Exception ] = []
135- for idx , exc_list in self .errors .items ():
136- if len (exc_list ) == 0 :
137- raise core_exceptions .ClientError (
138- f"Mutation { idx } failed with no associated errors"
130+ with self ._operation_metric :
131+ try :
132+ # trigger mutate_rows
133+ await self ._operation ()
134+ except Exception as exc :
135+ # exceptions raised by retryable are added to the list of exceptions for all unfinalized mutations
136+ incomplete_indices = self .remaining_indices .copy ()
137+ for idx in incomplete_indices :
138+ self ._handle_entry_error (idx , exc )
139+ finally :
140+ # raise exception detailing incomplete mutations
141+ all_errors : list [Exception ] = []
142+ for idx , exc_list in self .errors .items ():
143+ if len (exc_list ) == 0 :
144+ raise core_exceptions .ClientError (
145+ f"Mutation { idx } failed with no associated errors"
146+ )
147+ elif len (exc_list ) == 1 :
148+ cause_exc = exc_list [0 ]
149+ else :
150+ cause_exc = bt_exceptions .RetryExceptionGroup (exc_list )
151+ entry = self .mutations [idx ].entry
152+ all_errors .append (
153+ bt_exceptions .FailedMutationEntryError (idx , entry , cause_exc )
154+ )
155+ if all_errors :
156+ raise bt_exceptions .MutationsExceptionGroup (
157+ all_errors , len (self .mutations )
139158 )
140- elif len (exc_list ) == 1 :
141- cause_exc = exc_list [0 ]
142- else :
143- cause_exc = bt_exceptions .RetryExceptionGroup (exc_list )
144- entry = self .mutations [idx ].entry
145- all_errors .append (
146- bt_exceptions .FailedMutationEntryError (idx , entry , cause_exc )
147- )
148- if all_errors :
149- raise bt_exceptions .MutationsExceptionGroup (
150- all_errors , len (self .mutations )
151- )
152159
153160 @CrossSync .convert
154161 async def _run_attempt (self ):
@@ -160,6 +167,8 @@ async def _run_attempt(self):
160167 retry after the attempt is complete
161168 GoogleAPICallError: if the gapic rpc fails
162169 """
170+ # register attempt start
171+ self ._operation_metric .start_attempt ()
163172 request_entries = [self .mutations [idx ].proto for idx in self .remaining_indices ]
164173 # track mutations in this request that have not been finalized yet
165174 active_request_indices = {
0 commit comments