Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit a6f16ec

Browse files
committed
Merge branch 'csm_3_handlers->instrumentation' into csm_3_handlers
2 parents 18afdb7 + 71f2125 commit a6f16ec

25 files changed

Lines changed: 4927 additions & 788 deletions

google/cloud/bigtable/data/_async/_mutate_rows.py

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
if 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

Comments
 (0)