google-cloud-spanner 3.55.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google/cloud/spanner.py +47 -0
- google/cloud/spanner_admin_database_v1/__init__.py +146 -0
- google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
- google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_database_v1/py.typed +2 -0
- google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
- google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
- google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
- google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
- google/cloud/spanner_admin_database_v1/types/common.py +180 -0
- google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
- google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
- google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
- google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_instance_v1/py.typed +2 -0
- google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
- google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
- google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
- google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
- google/cloud/spanner_dbapi/__init__.py +93 -0
- google/cloud/spanner_dbapi/_helpers.py +113 -0
- google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
- google/cloud/spanner_dbapi/checksum.py +80 -0
- google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
- google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
- google/cloud/spanner_dbapi/connection.py +818 -0
- google/cloud/spanner_dbapi/cursor.py +609 -0
- google/cloud/spanner_dbapi/exceptions.py +172 -0
- google/cloud/spanner_dbapi/parse_utils.py +392 -0
- google/cloud/spanner_dbapi/parsed_statement.py +63 -0
- google/cloud/spanner_dbapi/parser.py +258 -0
- google/cloud/spanner_dbapi/partition_helper.py +41 -0
- google/cloud/spanner_dbapi/transaction_helper.py +294 -0
- google/cloud/spanner_dbapi/types.py +106 -0
- google/cloud/spanner_dbapi/utils.py +147 -0
- google/cloud/spanner_dbapi/version.py +20 -0
- google/cloud/spanner_v1/__init__.py +154 -0
- google/cloud/spanner_v1/_helpers.py +751 -0
- google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
- google/cloud/spanner_v1/backup.py +397 -0
- google/cloud/spanner_v1/batch.py +433 -0
- google/cloud/spanner_v1/client.py +538 -0
- google/cloud/spanner_v1/data_types.py +350 -0
- google/cloud/spanner_v1/database.py +1968 -0
- google/cloud/spanner_v1/database_sessions_manager.py +249 -0
- google/cloud/spanner_v1/gapic_metadata.json +268 -0
- google/cloud/spanner_v1/gapic_version.py +16 -0
- google/cloud/spanner_v1/instance.py +735 -0
- google/cloud/spanner_v1/keyset.py +193 -0
- google/cloud/spanner_v1/merged_result_set.py +146 -0
- google/cloud/spanner_v1/metrics/constants.py +71 -0
- google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
- google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
- google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
- google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
- google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
- google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
- google/cloud/spanner_v1/param_types.py +110 -0
- google/cloud/spanner_v1/pool.py +813 -0
- google/cloud/spanner_v1/py.typed +2 -0
- google/cloud/spanner_v1/request_id_header.py +64 -0
- google/cloud/spanner_v1/services/__init__.py +15 -0
- google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
- google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
- google/cloud/spanner_v1/services/spanner/client.py +2624 -0
- google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
- google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
- google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
- google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
- google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
- google/cloud/spanner_v1/session.py +631 -0
- google/cloud/spanner_v1/session_options.py +133 -0
- google/cloud/spanner_v1/snapshot.py +1057 -0
- google/cloud/spanner_v1/streamed.py +402 -0
- google/cloud/spanner_v1/table.py +181 -0
- google/cloud/spanner_v1/testing/__init__.py +0 -0
- google/cloud/spanner_v1/testing/database_test.py +121 -0
- google/cloud/spanner_v1/testing/interceptors.py +118 -0
- google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
- google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
- google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
- google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
- google/cloud/spanner_v1/transaction.py +747 -0
- google/cloud/spanner_v1/types/__init__.py +118 -0
- google/cloud/spanner_v1/types/commit_response.py +94 -0
- google/cloud/spanner_v1/types/keys.py +248 -0
- google/cloud/spanner_v1/types/mutation.py +201 -0
- google/cloud/spanner_v1/types/query_plan.py +220 -0
- google/cloud/spanner_v1/types/result_set.py +379 -0
- google/cloud/spanner_v1/types/spanner.py +1815 -0
- google/cloud/spanner_v1/types/transaction.py +818 -0
- google/cloud/spanner_v1/types/type.py +288 -0
- google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
- google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
- google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
- google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
- google_cloud_spanner-3.55.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright 2016 Google LLC All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Wrap representation of Spanner keys / ranges."""
|
|
16
|
+
|
|
17
|
+
from google.cloud.spanner_v1 import KeyRangePB
|
|
18
|
+
from google.cloud.spanner_v1 import KeySetPB
|
|
19
|
+
|
|
20
|
+
from google.cloud.spanner_v1._helpers import _make_list_value_pb
|
|
21
|
+
from google.cloud.spanner_v1._helpers import _make_list_value_pbs
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class KeyRange(object):
|
|
25
|
+
"""Identify range of table rows via start / end points.
|
|
26
|
+
|
|
27
|
+
Specify either a `start_open` or `start_closed` key, or defaults to
|
|
28
|
+
`start_closed = []`. Specify either an `end_open` or `end_closed` key,
|
|
29
|
+
or defaults to `end_closed = []`. However, at least one key has to be
|
|
30
|
+
specified. If no keys are specified, ValueError is raised.
|
|
31
|
+
|
|
32
|
+
:type start_open: list of scalars
|
|
33
|
+
:param start_open: keys identifying start of range (this key excluded)
|
|
34
|
+
|
|
35
|
+
:type start_closed: list of scalars
|
|
36
|
+
:param start_closed: keys identifying start of range (this key included)
|
|
37
|
+
|
|
38
|
+
:type end_open: list of scalars
|
|
39
|
+
:param end_open: keys identifying end of range (this key excluded)
|
|
40
|
+
|
|
41
|
+
:type end_closed: list of scalars
|
|
42
|
+
:param end_closed: keys identifying end of range (this key included)
|
|
43
|
+
|
|
44
|
+
:raises ValueError: if no keys are specified
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self, start_open=None, start_closed=None, end_open=None, end_closed=None
|
|
49
|
+
):
|
|
50
|
+
if not any([start_open, start_closed, end_open, end_closed]):
|
|
51
|
+
raise ValueError("Must specify at least a start or end row.")
|
|
52
|
+
|
|
53
|
+
if start_open and start_closed:
|
|
54
|
+
raise ValueError("Specify one of 'start_open' / 'start_closed'.")
|
|
55
|
+
elif start_open is None and start_closed is None:
|
|
56
|
+
start_closed = []
|
|
57
|
+
|
|
58
|
+
if end_open and end_closed:
|
|
59
|
+
raise ValueError("Specify one of 'end_open' / 'end_closed'.")
|
|
60
|
+
elif end_open is None and end_closed is None:
|
|
61
|
+
end_closed = []
|
|
62
|
+
|
|
63
|
+
self.start_open = start_open
|
|
64
|
+
self.start_closed = start_closed
|
|
65
|
+
self.end_open = end_open
|
|
66
|
+
self.end_closed = end_closed
|
|
67
|
+
|
|
68
|
+
def _to_pb(self):
|
|
69
|
+
"""Construct a KeyRange protobuf.
|
|
70
|
+
|
|
71
|
+
:rtype: :class:`~google.cloud.spanner_v1.types.KeyRange`
|
|
72
|
+
:returns: protobuf corresponding to this instance.
|
|
73
|
+
"""
|
|
74
|
+
kwargs = {}
|
|
75
|
+
|
|
76
|
+
if self.start_open is not None:
|
|
77
|
+
kwargs["start_open"] = _make_list_value_pb(self.start_open)
|
|
78
|
+
|
|
79
|
+
if self.start_closed is not None:
|
|
80
|
+
kwargs["start_closed"] = _make_list_value_pb(self.start_closed)
|
|
81
|
+
|
|
82
|
+
if self.end_open is not None:
|
|
83
|
+
kwargs["end_open"] = _make_list_value_pb(self.end_open)
|
|
84
|
+
|
|
85
|
+
if self.end_closed is not None:
|
|
86
|
+
kwargs["end_closed"] = _make_list_value_pb(self.end_closed)
|
|
87
|
+
|
|
88
|
+
return KeyRangePB(**kwargs)
|
|
89
|
+
|
|
90
|
+
def _to_dict(self):
|
|
91
|
+
"""Return the state of the keyrange as a dict.
|
|
92
|
+
|
|
93
|
+
:rtype: dict
|
|
94
|
+
:returns: state of this instance.
|
|
95
|
+
"""
|
|
96
|
+
mapping = {}
|
|
97
|
+
|
|
98
|
+
if self.start_open:
|
|
99
|
+
mapping["start_open"] = self.start_open
|
|
100
|
+
|
|
101
|
+
if self.start_closed:
|
|
102
|
+
mapping["start_closed"] = self.start_closed
|
|
103
|
+
|
|
104
|
+
if self.end_open:
|
|
105
|
+
mapping["end_open"] = self.end_open
|
|
106
|
+
|
|
107
|
+
if self.end_closed:
|
|
108
|
+
mapping["end_closed"] = self.end_closed
|
|
109
|
+
|
|
110
|
+
return mapping
|
|
111
|
+
|
|
112
|
+
def __eq__(self, other):
|
|
113
|
+
"""Compare by serialized state."""
|
|
114
|
+
if not isinstance(other, self.__class__):
|
|
115
|
+
return NotImplemented
|
|
116
|
+
return self._to_dict() == other._to_dict()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class KeySet(object):
|
|
120
|
+
"""Identify table rows via keys / ranges.
|
|
121
|
+
|
|
122
|
+
:type keys: list of list of scalars
|
|
123
|
+
:param keys: keys identifying individual rows within a table.
|
|
124
|
+
|
|
125
|
+
:type ranges: list of :class:`KeyRange`
|
|
126
|
+
:param ranges: ranges identifying rows within a table.
|
|
127
|
+
|
|
128
|
+
:type all_: boolean
|
|
129
|
+
:param all_: if True, identify all rows within a table
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(self, keys=(), ranges=(), all_=False):
|
|
133
|
+
if all_ and (keys or ranges):
|
|
134
|
+
raise ValueError("'all_' is exclusive of 'keys' / 'ranges'.")
|
|
135
|
+
self.keys = list(keys)
|
|
136
|
+
self.ranges = list(ranges)
|
|
137
|
+
self.all_ = all_
|
|
138
|
+
|
|
139
|
+
def _to_pb(self):
|
|
140
|
+
"""Construct a KeySet protobuf.
|
|
141
|
+
|
|
142
|
+
:rtype: :class:`~google.cloud.spanner_v1.types.KeySet`
|
|
143
|
+
:returns: protobuf corresponding to this instance.
|
|
144
|
+
"""
|
|
145
|
+
if self.all_:
|
|
146
|
+
return KeySetPB(all_=True)
|
|
147
|
+
kwargs = {}
|
|
148
|
+
|
|
149
|
+
if self.keys:
|
|
150
|
+
kwargs["keys"] = _make_list_value_pbs(self.keys)
|
|
151
|
+
|
|
152
|
+
if self.ranges:
|
|
153
|
+
kwargs["ranges"] = [krange._to_pb() for krange in self.ranges]
|
|
154
|
+
|
|
155
|
+
return KeySetPB(**kwargs)
|
|
156
|
+
|
|
157
|
+
def _to_dict(self):
|
|
158
|
+
"""Return the state of the keyset as a dict.
|
|
159
|
+
|
|
160
|
+
The result can be used to serialize the instance and reconstitute
|
|
161
|
+
it later using :meth:`_from_dict`.
|
|
162
|
+
|
|
163
|
+
:rtype: dict
|
|
164
|
+
:returns: state of this instance.
|
|
165
|
+
"""
|
|
166
|
+
if self.all_:
|
|
167
|
+
return {"all": True}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"keys": self.keys,
|
|
171
|
+
"ranges": [keyrange._to_dict() for keyrange in self.ranges],
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def __eq__(self, other):
|
|
175
|
+
"""Compare by serialized state."""
|
|
176
|
+
if not isinstance(other, self.__class__):
|
|
177
|
+
return NotImplemented
|
|
178
|
+
return self._to_dict() == other._to_dict()
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def _from_dict(cls, mapping):
|
|
182
|
+
"""Create an instance from the corresponding state mapping.
|
|
183
|
+
|
|
184
|
+
:type mapping: dict
|
|
185
|
+
:param mapping: the instance state.
|
|
186
|
+
"""
|
|
187
|
+
if mapping.get("all"):
|
|
188
|
+
return cls(all_=True)
|
|
189
|
+
|
|
190
|
+
r_mappings = mapping.get("ranges", ())
|
|
191
|
+
ranges = [KeyRange(**r_mapping) for r_mapping in r_mappings]
|
|
192
|
+
|
|
193
|
+
return cls(keys=mapping.get("keys", ()), ranges=ranges)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from queue import Queue
|
|
17
|
+
from typing import Any, TYPE_CHECKING
|
|
18
|
+
from threading import Lock, Event
|
|
19
|
+
|
|
20
|
+
from google.cloud.spanner_v1._opentelemetry_tracing import trace_call
|
|
21
|
+
from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from google.cloud.spanner_v1.database import BatchSnapshot
|
|
25
|
+
|
|
26
|
+
QUEUE_SIZE_PER_WORKER = 32
|
|
27
|
+
MAX_PARALLELISM = 16
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PartitionExecutor:
|
|
31
|
+
"""
|
|
32
|
+
Executor that executes single partition on a separate thread and inserts
|
|
33
|
+
rows in the queue
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, batch_snapshot, partition_id, merged_result_set):
|
|
37
|
+
self._batch_snapshot: BatchSnapshot = batch_snapshot
|
|
38
|
+
self._partition_id = partition_id
|
|
39
|
+
self._merged_result_set: MergedResultSet = merged_result_set
|
|
40
|
+
self._queue: Queue[PartitionExecutorResult] = merged_result_set._queue
|
|
41
|
+
|
|
42
|
+
def run(self):
|
|
43
|
+
observability_options = getattr(
|
|
44
|
+
self._batch_snapshot, "observability_options", {}
|
|
45
|
+
)
|
|
46
|
+
with trace_call(
|
|
47
|
+
"CloudSpanner.PartitionExecutor.run",
|
|
48
|
+
observability_options=observability_options,
|
|
49
|
+
), MetricsCapture():
|
|
50
|
+
self.__run()
|
|
51
|
+
|
|
52
|
+
def __run(self):
|
|
53
|
+
results = None
|
|
54
|
+
try:
|
|
55
|
+
results = self._batch_snapshot.process_query_batch(self._partition_id)
|
|
56
|
+
for row in results:
|
|
57
|
+
if self._merged_result_set._metadata is None:
|
|
58
|
+
self._set_metadata(results)
|
|
59
|
+
self._queue.put(PartitionExecutorResult(data=row))
|
|
60
|
+
# Special case: The result set did not return any rows.
|
|
61
|
+
# Push the metadata to the merged result set.
|
|
62
|
+
if self._merged_result_set._metadata is None:
|
|
63
|
+
self._set_metadata(results)
|
|
64
|
+
except Exception as ex:
|
|
65
|
+
if self._merged_result_set._metadata is None:
|
|
66
|
+
self._set_metadata(results, True)
|
|
67
|
+
self._queue.put(PartitionExecutorResult(exception=ex))
|
|
68
|
+
finally:
|
|
69
|
+
# Emit a special 'is_last' result to ensure that the MergedResultSet
|
|
70
|
+
# is not blocked on a queue that never receives any more results.
|
|
71
|
+
self._queue.put(PartitionExecutorResult(is_last=True))
|
|
72
|
+
|
|
73
|
+
def _set_metadata(self, results, is_exception=False):
|
|
74
|
+
self._merged_result_set.metadata_lock.acquire()
|
|
75
|
+
try:
|
|
76
|
+
if not is_exception:
|
|
77
|
+
self._merged_result_set._metadata = results.metadata
|
|
78
|
+
finally:
|
|
79
|
+
self._merged_result_set.metadata_lock.release()
|
|
80
|
+
self._merged_result_set.metadata_event.set()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class PartitionExecutorResult:
|
|
85
|
+
data: Any = None
|
|
86
|
+
exception: Exception = None
|
|
87
|
+
is_last: bool = False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MergedResultSet:
|
|
91
|
+
"""
|
|
92
|
+
Executes multiple partitions on different threads and then combines the
|
|
93
|
+
results from multiple queries using a synchronized queue. The order of the
|
|
94
|
+
records in the MergedResultSet is not guaranteed.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, batch_snapshot, partition_ids, max_parallelism):
|
|
98
|
+
self._exception = None
|
|
99
|
+
self._metadata = None
|
|
100
|
+
self.metadata_event = Event()
|
|
101
|
+
self.metadata_lock = Lock()
|
|
102
|
+
|
|
103
|
+
partition_ids_count = len(partition_ids)
|
|
104
|
+
self._finished_count_down_latch = partition_ids_count
|
|
105
|
+
parallelism = min(MAX_PARALLELISM, partition_ids_count)
|
|
106
|
+
if max_parallelism != 0:
|
|
107
|
+
parallelism = min(partition_ids_count, max_parallelism)
|
|
108
|
+
self._queue = Queue(maxsize=QUEUE_SIZE_PER_WORKER * parallelism)
|
|
109
|
+
|
|
110
|
+
partition_executors = []
|
|
111
|
+
for partition_id in partition_ids:
|
|
112
|
+
partition_executors.append(
|
|
113
|
+
PartitionExecutor(batch_snapshot, partition_id, self)
|
|
114
|
+
)
|
|
115
|
+
executor = ThreadPoolExecutor(max_workers=parallelism)
|
|
116
|
+
for partition_executor in partition_executors:
|
|
117
|
+
executor.submit(partition_executor.run)
|
|
118
|
+
executor.shutdown(False)
|
|
119
|
+
|
|
120
|
+
def __iter__(self):
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def __next__(self):
|
|
124
|
+
if self._exception is not None:
|
|
125
|
+
raise self._exception
|
|
126
|
+
while True:
|
|
127
|
+
partition_result = self._queue.get()
|
|
128
|
+
if partition_result.is_last:
|
|
129
|
+
self._finished_count_down_latch -= 1
|
|
130
|
+
if self._finished_count_down_latch == 0:
|
|
131
|
+
raise StopIteration
|
|
132
|
+
elif partition_result.exception is not None:
|
|
133
|
+
self._exception = partition_result.exception
|
|
134
|
+
raise self._exception
|
|
135
|
+
else:
|
|
136
|
+
return partition_result.data
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def metadata(self):
|
|
140
|
+
self.metadata_event.wait()
|
|
141
|
+
return self._metadata
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def stats(self):
|
|
145
|
+
# TODO: Implement
|
|
146
|
+
return None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
BUILT_IN_METRICS_METER_NAME = "gax-python"
|
|
16
|
+
NATIVE_METRICS_PREFIX = "spanner.googleapis.com/internal/client"
|
|
17
|
+
SPANNER_RESOURCE_TYPE = "spanner_instance_client"
|
|
18
|
+
SPANNER_SERVICE_NAME = "spanner-python"
|
|
19
|
+
GOOGLE_CLOUD_RESOURCE_KEY = "google-cloud-resource-prefix"
|
|
20
|
+
GOOGLE_CLOUD_REGION_KEY = "cloud.region"
|
|
21
|
+
GOOGLE_CLOUD_REGION_GLOBAL = "global"
|
|
22
|
+
SPANNER_METHOD_PREFIX = "/google.spanner.v1."
|
|
23
|
+
ENABLE_SPANNER_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"
|
|
24
|
+
|
|
25
|
+
# Monitored resource labels
|
|
26
|
+
MONITORED_RES_LABEL_KEY_PROJECT = "project_id"
|
|
27
|
+
MONITORED_RES_LABEL_KEY_INSTANCE = "instance_id"
|
|
28
|
+
MONITORED_RES_LABEL_KEY_INSTANCE_CONFIG = "instance_config"
|
|
29
|
+
MONITORED_RES_LABEL_KEY_LOCATION = "location"
|
|
30
|
+
MONITORED_RES_LABEL_KEY_CLIENT_HASH = "client_hash"
|
|
31
|
+
MONITORED_RESOURCE_LABELS = [
|
|
32
|
+
MONITORED_RES_LABEL_KEY_PROJECT,
|
|
33
|
+
MONITORED_RES_LABEL_KEY_INSTANCE,
|
|
34
|
+
MONITORED_RES_LABEL_KEY_INSTANCE_CONFIG,
|
|
35
|
+
MONITORED_RES_LABEL_KEY_LOCATION,
|
|
36
|
+
MONITORED_RES_LABEL_KEY_CLIENT_HASH,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Metric labels
|
|
40
|
+
METRIC_LABEL_KEY_CLIENT_UID = "client_uid"
|
|
41
|
+
METRIC_LABEL_KEY_CLIENT_NAME = "client_name"
|
|
42
|
+
METRIC_LABEL_KEY_DATABASE = "database"
|
|
43
|
+
METRIC_LABEL_KEY_METHOD = "method"
|
|
44
|
+
METRIC_LABEL_KEY_STATUS = "status"
|
|
45
|
+
METRIC_LABEL_KEY_DIRECT_PATH_ENABLED = "directpath_enabled"
|
|
46
|
+
METRIC_LABEL_KEY_DIRECT_PATH_USED = "directpath_used"
|
|
47
|
+
METRIC_LABELS = [
|
|
48
|
+
METRIC_LABEL_KEY_CLIENT_UID,
|
|
49
|
+
METRIC_LABEL_KEY_CLIENT_NAME,
|
|
50
|
+
METRIC_LABEL_KEY_DATABASE,
|
|
51
|
+
METRIC_LABEL_KEY_METHOD,
|
|
52
|
+
METRIC_LABEL_KEY_STATUS,
|
|
53
|
+
METRIC_LABEL_KEY_DIRECT_PATH_ENABLED,
|
|
54
|
+
METRIC_LABEL_KEY_DIRECT_PATH_USED,
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
# Metric names
|
|
58
|
+
METRIC_NAME_OPERATION_LATENCIES = "operation_latencies"
|
|
59
|
+
METRIC_NAME_ATTEMPT_LATENCIES = "attempt_latencies"
|
|
60
|
+
METRIC_NAME_OPERATION_COUNT = "operation_count"
|
|
61
|
+
METRIC_NAME_ATTEMPT_COUNT = "attempt_count"
|
|
62
|
+
METRIC_NAME_GFE_LATENCY = "gfe_latency"
|
|
63
|
+
METRIC_NAME_GFE_MISSING_HEADER_COUNT = "gfe_missing_header_count"
|
|
64
|
+
METRIC_NAMES = [
|
|
65
|
+
METRIC_NAME_OPERATION_LATENCIES,
|
|
66
|
+
METRIC_NAME_ATTEMPT_LATENCIES,
|
|
67
|
+
METRIC_NAME_OPERATION_COUNT,
|
|
68
|
+
METRIC_NAME_ATTEMPT_COUNT,
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
METRIC_EXPORT_INTERVAL_MS = 60000 # 1 Minute
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
This module provides functionality for capturing metrics in Cloud Spanner operations.
|
|
16
|
+
|
|
17
|
+
It includes a context manager class, MetricsCapture, which automatically handles the
|
|
18
|
+
start and completion of metrics tracing for a given operation. This ensures that metrics
|
|
19
|
+
are consistently recorded for Cloud Spanner operations, facilitating observability and
|
|
20
|
+
performance monitoring.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .spanner_metrics_tracer_factory import SpannerMetricsTracerFactory
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MetricsCapture:
|
|
27
|
+
"""Context manager for capturing metrics in Cloud Spanner operations.
|
|
28
|
+
|
|
29
|
+
This class provides a context manager interface to automatically handle
|
|
30
|
+
the start and completion of metrics tracing for a given operation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __enter__(self):
|
|
34
|
+
"""Enter the runtime context related to this object.
|
|
35
|
+
|
|
36
|
+
This method initializes a new metrics tracer for the operation and
|
|
37
|
+
records the start of the operation.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
MetricsCapture: The instance of the context manager.
|
|
41
|
+
"""
|
|
42
|
+
# Short circuit out if metrics are disabled
|
|
43
|
+
factory = SpannerMetricsTracerFactory()
|
|
44
|
+
if not factory.enabled:
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
# Define a new metrics tracer for the new operation
|
|
48
|
+
SpannerMetricsTracerFactory.current_metrics_tracer = (
|
|
49
|
+
factory.create_metrics_tracer()
|
|
50
|
+
)
|
|
51
|
+
if SpannerMetricsTracerFactory.current_metrics_tracer:
|
|
52
|
+
SpannerMetricsTracerFactory.current_metrics_tracer.record_operation_start()
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
56
|
+
"""Exit the runtime context related to this object.
|
|
57
|
+
|
|
58
|
+
This method records the completion of the operation. If an exception
|
|
59
|
+
occurred, it will be propagated after the metrics are recorded.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
exc_type (Type[BaseException]): The exception type.
|
|
63
|
+
exc_value (BaseException): The exception value.
|
|
64
|
+
traceback (TracebackType): The traceback object.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
bool: False to propagate the exception if any occurred.
|
|
68
|
+
"""
|
|
69
|
+
# Short circuit out if metrics are disable
|
|
70
|
+
if not SpannerMetricsTracerFactory().enabled:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
if SpannerMetricsTracerFactory.current_metrics_tracer:
|
|
74
|
+
SpannerMetricsTracerFactory.current_metrics_tracer.record_operation_completion()
|
|
75
|
+
return False # Propagate the exception if any
|