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,813 @@
|
|
|
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
|
+
"""Pools managing shared Session objects."""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import queue
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from google.cloud.exceptions import NotFound
|
|
22
|
+
from google.cloud.spanner_v1 import BatchCreateSessionsRequest
|
|
23
|
+
from google.cloud.spanner_v1 import Session
|
|
24
|
+
from google.cloud.spanner_v1._helpers import (
|
|
25
|
+
_metadata_with_prefix,
|
|
26
|
+
_metadata_with_leader_aware_routing,
|
|
27
|
+
)
|
|
28
|
+
from google.cloud.spanner_v1._opentelemetry_tracing import (
|
|
29
|
+
add_span_event,
|
|
30
|
+
get_current_span,
|
|
31
|
+
trace_call,
|
|
32
|
+
)
|
|
33
|
+
from warnings import warn
|
|
34
|
+
|
|
35
|
+
from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
|
|
36
|
+
|
|
37
|
+
_NOW = datetime.datetime.utcnow # unit tests may replace
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AbstractSessionPool(object):
|
|
41
|
+
"""Specifies required API for concrete session pool implementations.
|
|
42
|
+
|
|
43
|
+
:type labels: dict (str -> str) or None
|
|
44
|
+
:param labels: (Optional) user-assigned labels for sessions created
|
|
45
|
+
by the pool.
|
|
46
|
+
|
|
47
|
+
:type database_role: str
|
|
48
|
+
:param database_role: (Optional) user-assigned database_role for the session.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
_database = None
|
|
52
|
+
|
|
53
|
+
def __init__(self, labels=None, database_role=None):
|
|
54
|
+
if labels is None:
|
|
55
|
+
labels = {}
|
|
56
|
+
self._labels = labels
|
|
57
|
+
self._database_role = database_role
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def labels(self):
|
|
61
|
+
"""User-assigned labels for sessions created by the pool.
|
|
62
|
+
|
|
63
|
+
:rtype: dict (str -> str)
|
|
64
|
+
:returns: labels assigned by the user
|
|
65
|
+
"""
|
|
66
|
+
return self._labels
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def database_role(self):
|
|
70
|
+
"""User-assigned database_role for sessions created by the pool.
|
|
71
|
+
|
|
72
|
+
:rtype: str
|
|
73
|
+
:returns: database_role assigned by the user
|
|
74
|
+
"""
|
|
75
|
+
return self._database_role
|
|
76
|
+
|
|
77
|
+
def bind(self, database):
|
|
78
|
+
"""Associate the pool with a database.
|
|
79
|
+
|
|
80
|
+
:type database: :class:`~google.cloud.spanner_v1.database.Database`
|
|
81
|
+
:param database: database used by the pool to create sessions
|
|
82
|
+
when needed.
|
|
83
|
+
|
|
84
|
+
Concrete implementations of this method may pre-fill the pool
|
|
85
|
+
using the database.
|
|
86
|
+
|
|
87
|
+
:raises NotImplementedError: abstract method
|
|
88
|
+
"""
|
|
89
|
+
raise NotImplementedError()
|
|
90
|
+
|
|
91
|
+
def get(self):
|
|
92
|
+
"""Check a session out from the pool.
|
|
93
|
+
|
|
94
|
+
Concrete implementations of this method are allowed to raise an
|
|
95
|
+
error to signal that the pool is exhausted, or to block until a
|
|
96
|
+
session is available.
|
|
97
|
+
|
|
98
|
+
:raises NotImplementedError: abstract method
|
|
99
|
+
"""
|
|
100
|
+
raise NotImplementedError()
|
|
101
|
+
|
|
102
|
+
def put(self, session):
|
|
103
|
+
"""Return a session to the pool.
|
|
104
|
+
|
|
105
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
106
|
+
:param session: the session being returned.
|
|
107
|
+
|
|
108
|
+
Concrete implementations of this method are allowed to raise an
|
|
109
|
+
error to signal that the pool is full, or to block until it is
|
|
110
|
+
not full.
|
|
111
|
+
|
|
112
|
+
:raises NotImplementedError: abstract method
|
|
113
|
+
"""
|
|
114
|
+
raise NotImplementedError()
|
|
115
|
+
|
|
116
|
+
def clear(self):
|
|
117
|
+
"""Delete all sessions in the pool.
|
|
118
|
+
|
|
119
|
+
Concrete implementations of this method are allowed to raise an
|
|
120
|
+
error to signal that the pool is full, or to block until it is
|
|
121
|
+
not full.
|
|
122
|
+
|
|
123
|
+
:raises NotImplementedError: abstract method
|
|
124
|
+
"""
|
|
125
|
+
raise NotImplementedError()
|
|
126
|
+
|
|
127
|
+
def _new_session(self):
|
|
128
|
+
"""Helper for concrete methods creating session instances.
|
|
129
|
+
|
|
130
|
+
:rtype: :class:`~google.cloud.spanner_v1.session.Session`
|
|
131
|
+
:returns: new session instance.
|
|
132
|
+
"""
|
|
133
|
+
return self._database.session(
|
|
134
|
+
labels=self.labels, database_role=self.database_role
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def session(self, **kwargs):
|
|
138
|
+
"""Check out a session from the pool.
|
|
139
|
+
|
|
140
|
+
:param kwargs: (optional) keyword arguments, passed through to
|
|
141
|
+
the returned checkout.
|
|
142
|
+
|
|
143
|
+
:rtype: :class:`~google.cloud.spanner_v1.session.SessionCheckout`
|
|
144
|
+
:returns: a checkout instance, to be used as a context manager for
|
|
145
|
+
accessing the session and returning it to the pool.
|
|
146
|
+
"""
|
|
147
|
+
return SessionCheckout(self, **kwargs)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class FixedSizePool(AbstractSessionPool):
|
|
151
|
+
"""Concrete session pool implementation:
|
|
152
|
+
|
|
153
|
+
- Pre-allocates / creates a fixed number of sessions.
|
|
154
|
+
|
|
155
|
+
- "Pings" existing sessions via :meth:`session.exists` before returning
|
|
156
|
+
sessions that have not been used for more than 55 minutes and replaces
|
|
157
|
+
expired sessions.
|
|
158
|
+
|
|
159
|
+
- Blocks, with a timeout, when :meth:`get` is called on an empty pool.
|
|
160
|
+
Raises after timing out.
|
|
161
|
+
|
|
162
|
+
- Raises when :meth:`put` is called on a full pool. That error is
|
|
163
|
+
never expected in normal practice, as users should be calling
|
|
164
|
+
:meth:`get` followed by :meth:`put` whenever in need of a session.
|
|
165
|
+
|
|
166
|
+
:type size: int
|
|
167
|
+
:param size: fixed pool size
|
|
168
|
+
|
|
169
|
+
:type default_timeout: int
|
|
170
|
+
:param default_timeout: default timeout, in seconds, to wait for
|
|
171
|
+
a returned session.
|
|
172
|
+
|
|
173
|
+
:type labels: dict (str -> str) or None
|
|
174
|
+
:param labels: (Optional) user-assigned labels for sessions created
|
|
175
|
+
by the pool.
|
|
176
|
+
|
|
177
|
+
:type database_role: str
|
|
178
|
+
:param database_role: (Optional) user-assigned database_role for the session.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
DEFAULT_SIZE = 10
|
|
182
|
+
DEFAULT_TIMEOUT = 10
|
|
183
|
+
DEFAULT_MAX_AGE_MINUTES = 55
|
|
184
|
+
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
size=DEFAULT_SIZE,
|
|
188
|
+
default_timeout=DEFAULT_TIMEOUT,
|
|
189
|
+
labels=None,
|
|
190
|
+
database_role=None,
|
|
191
|
+
max_age_minutes=DEFAULT_MAX_AGE_MINUTES,
|
|
192
|
+
):
|
|
193
|
+
super(FixedSizePool, self).__init__(labels=labels, database_role=database_role)
|
|
194
|
+
self.size = size
|
|
195
|
+
self.default_timeout = default_timeout
|
|
196
|
+
self._sessions = queue.LifoQueue(size)
|
|
197
|
+
self._max_age = datetime.timedelta(minutes=max_age_minutes)
|
|
198
|
+
|
|
199
|
+
def bind(self, database):
|
|
200
|
+
"""Associate the pool with a database.
|
|
201
|
+
|
|
202
|
+
:type database: :class:`~google.cloud.spanner_v1.database.Database`
|
|
203
|
+
:param database: database used by the pool to used to create sessions
|
|
204
|
+
when needed.
|
|
205
|
+
"""
|
|
206
|
+
self._database = database
|
|
207
|
+
requested_session_count = self.size - self._sessions.qsize()
|
|
208
|
+
span = get_current_span()
|
|
209
|
+
span_event_attributes = {"kind": type(self).__name__}
|
|
210
|
+
|
|
211
|
+
if requested_session_count <= 0:
|
|
212
|
+
add_span_event(
|
|
213
|
+
span,
|
|
214
|
+
f"Invalid session pool size({requested_session_count}) <= 0",
|
|
215
|
+
span_event_attributes,
|
|
216
|
+
)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
api = database.spanner_api
|
|
220
|
+
metadata = _metadata_with_prefix(database.name)
|
|
221
|
+
if database._route_to_leader_enabled:
|
|
222
|
+
metadata.append(
|
|
223
|
+
_metadata_with_leader_aware_routing(database._route_to_leader_enabled)
|
|
224
|
+
)
|
|
225
|
+
self._database_role = self._database_role or self._database.database_role
|
|
226
|
+
if requested_session_count > 0:
|
|
227
|
+
add_span_event(
|
|
228
|
+
span,
|
|
229
|
+
f"Requesting {requested_session_count} sessions",
|
|
230
|
+
span_event_attributes,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if self._sessions.full():
|
|
234
|
+
add_span_event(span, "Session pool is already full", span_event_attributes)
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
request = BatchCreateSessionsRequest(
|
|
238
|
+
database=database.name,
|
|
239
|
+
session_count=requested_session_count,
|
|
240
|
+
session_template=Session(creator_role=self.database_role),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
observability_options = getattr(self._database, "observability_options", None)
|
|
244
|
+
with trace_call(
|
|
245
|
+
"CloudSpanner.FixedPool.BatchCreateSessions",
|
|
246
|
+
observability_options=observability_options,
|
|
247
|
+
metadata=metadata,
|
|
248
|
+
) as span, MetricsCapture():
|
|
249
|
+
returned_session_count = 0
|
|
250
|
+
while not self._sessions.full():
|
|
251
|
+
request.session_count = requested_session_count - self._sessions.qsize()
|
|
252
|
+
add_span_event(
|
|
253
|
+
span,
|
|
254
|
+
f"Creating {request.session_count} sessions",
|
|
255
|
+
span_event_attributes,
|
|
256
|
+
)
|
|
257
|
+
resp = api.batch_create_sessions(
|
|
258
|
+
request=request,
|
|
259
|
+
metadata=database.metadata_with_request_id(
|
|
260
|
+
database._next_nth_request,
|
|
261
|
+
1,
|
|
262
|
+
metadata,
|
|
263
|
+
span,
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
add_span_event(
|
|
268
|
+
span,
|
|
269
|
+
"Created sessions",
|
|
270
|
+
dict(count=len(resp.session)),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
for session_pb in resp.session:
|
|
274
|
+
session = self._new_session()
|
|
275
|
+
session._session_id = session_pb.name.split("/")[-1]
|
|
276
|
+
self._sessions.put(session)
|
|
277
|
+
returned_session_count += 1
|
|
278
|
+
|
|
279
|
+
add_span_event(
|
|
280
|
+
span,
|
|
281
|
+
f"Requested for {requested_session_count} sessions, returned {returned_session_count}",
|
|
282
|
+
span_event_attributes,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def get(self, timeout=None):
|
|
286
|
+
"""Check a session out from the pool.
|
|
287
|
+
|
|
288
|
+
:type timeout: int
|
|
289
|
+
:param timeout: seconds to block waiting for an available session
|
|
290
|
+
|
|
291
|
+
:rtype: :class:`~google.cloud.spanner_v1.session.Session`
|
|
292
|
+
:returns: an existing session from the pool, or a newly-created
|
|
293
|
+
session.
|
|
294
|
+
:raises: :exc:`queue.Empty` if the queue is empty.
|
|
295
|
+
"""
|
|
296
|
+
if timeout is None:
|
|
297
|
+
timeout = self.default_timeout
|
|
298
|
+
|
|
299
|
+
start_time = time.time()
|
|
300
|
+
current_span = get_current_span()
|
|
301
|
+
span_event_attributes = {"kind": type(self).__name__}
|
|
302
|
+
add_span_event(current_span, "Acquiring session", span_event_attributes)
|
|
303
|
+
|
|
304
|
+
session = None
|
|
305
|
+
try:
|
|
306
|
+
add_span_event(
|
|
307
|
+
current_span,
|
|
308
|
+
"Waiting for a session to become available",
|
|
309
|
+
span_event_attributes,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
session = self._sessions.get(block=True, timeout=timeout)
|
|
313
|
+
age = _NOW() - session.last_use_time
|
|
314
|
+
|
|
315
|
+
if age >= self._max_age and not session.exists():
|
|
316
|
+
if not session.exists():
|
|
317
|
+
add_span_event(
|
|
318
|
+
current_span,
|
|
319
|
+
"Session is not valid, recreating it",
|
|
320
|
+
span_event_attributes,
|
|
321
|
+
)
|
|
322
|
+
session = self._database.session()
|
|
323
|
+
session.create()
|
|
324
|
+
# Replacing with the updated session.id.
|
|
325
|
+
span_event_attributes["session.id"] = session._session_id
|
|
326
|
+
|
|
327
|
+
span_event_attributes["session.id"] = session._session_id
|
|
328
|
+
span_event_attributes["time.elapsed"] = time.time() - start_time
|
|
329
|
+
add_span_event(current_span, "Acquired session", span_event_attributes)
|
|
330
|
+
|
|
331
|
+
except queue.Empty as e:
|
|
332
|
+
add_span_event(
|
|
333
|
+
current_span, "No sessions available in the pool", span_event_attributes
|
|
334
|
+
)
|
|
335
|
+
raise e
|
|
336
|
+
|
|
337
|
+
return session
|
|
338
|
+
|
|
339
|
+
def put(self, session):
|
|
340
|
+
"""Return a session to the pool.
|
|
341
|
+
|
|
342
|
+
Never blocks: if the pool is full, raises.
|
|
343
|
+
|
|
344
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
345
|
+
:param session: the session being returned.
|
|
346
|
+
|
|
347
|
+
:raises: :exc:`queue.Full` if the queue is full.
|
|
348
|
+
"""
|
|
349
|
+
self._sessions.put_nowait(session)
|
|
350
|
+
|
|
351
|
+
def clear(self):
|
|
352
|
+
"""Delete all sessions in the pool."""
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
try:
|
|
356
|
+
session = self._sessions.get(block=False)
|
|
357
|
+
except queue.Empty:
|
|
358
|
+
break
|
|
359
|
+
else:
|
|
360
|
+
session.delete()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class BurstyPool(AbstractSessionPool):
|
|
364
|
+
"""Concrete session pool implementation:
|
|
365
|
+
|
|
366
|
+
- "Pings" existing sessions via :meth:`session.exists` before returning
|
|
367
|
+
them.
|
|
368
|
+
|
|
369
|
+
- Creates a new session, rather than blocking, when :meth:`get` is called
|
|
370
|
+
on an empty pool.
|
|
371
|
+
|
|
372
|
+
- Discards the returned session, rather than blocking, when :meth:`put`
|
|
373
|
+
is called on a full pool.
|
|
374
|
+
|
|
375
|
+
:type target_size: int
|
|
376
|
+
:param target_size: max pool size
|
|
377
|
+
|
|
378
|
+
:type labels: dict (str -> str) or None
|
|
379
|
+
:param labels: (Optional) user-assigned labels for sessions created
|
|
380
|
+
by the pool.
|
|
381
|
+
|
|
382
|
+
:type database_role: str
|
|
383
|
+
:param database_role: (Optional) user-assigned database_role for the session.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def __init__(self, target_size=10, labels=None, database_role=None):
|
|
387
|
+
super(BurstyPool, self).__init__(labels=labels, database_role=database_role)
|
|
388
|
+
self.target_size = target_size
|
|
389
|
+
self._database = None
|
|
390
|
+
self._sessions = queue.LifoQueue(target_size)
|
|
391
|
+
|
|
392
|
+
def bind(self, database):
|
|
393
|
+
"""Associate the pool with a database.
|
|
394
|
+
|
|
395
|
+
:type database: :class:`~google.cloud.spanner_v1.database.Database`
|
|
396
|
+
:param database: database used by the pool to create sessions
|
|
397
|
+
when needed.
|
|
398
|
+
"""
|
|
399
|
+
self._database = database
|
|
400
|
+
self._database_role = self._database_role or self._database.database_role
|
|
401
|
+
|
|
402
|
+
def get(self):
|
|
403
|
+
"""Check a session out from the pool.
|
|
404
|
+
|
|
405
|
+
:rtype: :class:`~google.cloud.spanner_v1.session.Session`
|
|
406
|
+
:returns: an existing session from the pool, or a newly-created
|
|
407
|
+
session.
|
|
408
|
+
"""
|
|
409
|
+
current_span = get_current_span()
|
|
410
|
+
span_event_attributes = {"kind": type(self).__name__}
|
|
411
|
+
add_span_event(current_span, "Acquiring session", span_event_attributes)
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
add_span_event(
|
|
415
|
+
current_span,
|
|
416
|
+
"Waiting for a session to become available",
|
|
417
|
+
span_event_attributes,
|
|
418
|
+
)
|
|
419
|
+
session = self._sessions.get_nowait()
|
|
420
|
+
except queue.Empty:
|
|
421
|
+
add_span_event(
|
|
422
|
+
current_span,
|
|
423
|
+
"No sessions available in pool. Creating session",
|
|
424
|
+
span_event_attributes,
|
|
425
|
+
)
|
|
426
|
+
session = self._new_session()
|
|
427
|
+
session.create()
|
|
428
|
+
else:
|
|
429
|
+
if not session.exists():
|
|
430
|
+
add_span_event(
|
|
431
|
+
current_span,
|
|
432
|
+
"Session is not valid, recreating it",
|
|
433
|
+
span_event_attributes,
|
|
434
|
+
)
|
|
435
|
+
session = self._new_session()
|
|
436
|
+
session.create()
|
|
437
|
+
return session
|
|
438
|
+
|
|
439
|
+
def put(self, session):
|
|
440
|
+
"""Return a session to the pool.
|
|
441
|
+
|
|
442
|
+
Never blocks: if the pool is full, the returned session is
|
|
443
|
+
discarded.
|
|
444
|
+
|
|
445
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
446
|
+
:param session: the session being returned.
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
self._sessions.put_nowait(session)
|
|
450
|
+
except queue.Full:
|
|
451
|
+
try:
|
|
452
|
+
# Sessions from pools are never multiplexed, so we can always delete them
|
|
453
|
+
session.delete()
|
|
454
|
+
except NotFound:
|
|
455
|
+
pass
|
|
456
|
+
|
|
457
|
+
def clear(self):
|
|
458
|
+
"""Delete all sessions in the pool."""
|
|
459
|
+
|
|
460
|
+
while True:
|
|
461
|
+
try:
|
|
462
|
+
session = self._sessions.get(block=False)
|
|
463
|
+
except queue.Empty:
|
|
464
|
+
break
|
|
465
|
+
else:
|
|
466
|
+
session.delete()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class PingingPool(AbstractSessionPool):
|
|
470
|
+
"""Concrete session pool implementation:
|
|
471
|
+
|
|
472
|
+
- Pre-allocates / creates a fixed number of sessions.
|
|
473
|
+
|
|
474
|
+
- Sessions are used in "round-robin" order (LRU first).
|
|
475
|
+
|
|
476
|
+
- "Pings" existing sessions in the background after a specified interval
|
|
477
|
+
via an API call (``session.ping()``).
|
|
478
|
+
|
|
479
|
+
- Blocks, with a timeout, when :meth:`get` is called on an empty pool.
|
|
480
|
+
Raises after timing out.
|
|
481
|
+
|
|
482
|
+
- Raises when :meth:`put` is called on a full pool. That error is
|
|
483
|
+
never expected in normal practice, as users should be calling
|
|
484
|
+
:meth:`get` followed by :meth:`put` whenever in need of a session.
|
|
485
|
+
|
|
486
|
+
The application is responsible for calling :meth:`ping` at appropriate
|
|
487
|
+
times, e.g. from a background thread.
|
|
488
|
+
|
|
489
|
+
:type size: int
|
|
490
|
+
:param size: fixed pool size
|
|
491
|
+
|
|
492
|
+
:type default_timeout: int
|
|
493
|
+
:param default_timeout: default timeout, in seconds, to wait for
|
|
494
|
+
a returned session.
|
|
495
|
+
|
|
496
|
+
:type ping_interval: int
|
|
497
|
+
:param ping_interval: interval at which to ping sessions.
|
|
498
|
+
|
|
499
|
+
:type labels: dict (str -> str) or None
|
|
500
|
+
:param labels: (Optional) user-assigned labels for sessions created
|
|
501
|
+
by the pool.
|
|
502
|
+
|
|
503
|
+
:type database_role: str
|
|
504
|
+
:param database_role: (Optional) user-assigned database_role for the session.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
def __init__(
|
|
508
|
+
self,
|
|
509
|
+
size=10,
|
|
510
|
+
default_timeout=10,
|
|
511
|
+
ping_interval=3000,
|
|
512
|
+
labels=None,
|
|
513
|
+
database_role=None,
|
|
514
|
+
):
|
|
515
|
+
super(PingingPool, self).__init__(labels=labels, database_role=database_role)
|
|
516
|
+
self.size = size
|
|
517
|
+
self.default_timeout = default_timeout
|
|
518
|
+
self._delta = datetime.timedelta(seconds=ping_interval)
|
|
519
|
+
self._sessions = queue.PriorityQueue(size)
|
|
520
|
+
|
|
521
|
+
def bind(self, database):
|
|
522
|
+
"""Associate the pool with a database.
|
|
523
|
+
|
|
524
|
+
:type database: :class:`~google.cloud.spanner_v1.database.Database`
|
|
525
|
+
:param database: database used by the pool to create sessions
|
|
526
|
+
when needed.
|
|
527
|
+
"""
|
|
528
|
+
self._database = database
|
|
529
|
+
api = database.spanner_api
|
|
530
|
+
metadata = _metadata_with_prefix(database.name)
|
|
531
|
+
if database._route_to_leader_enabled:
|
|
532
|
+
metadata.append(
|
|
533
|
+
_metadata_with_leader_aware_routing(database._route_to_leader_enabled)
|
|
534
|
+
)
|
|
535
|
+
self._database_role = self._database_role or self._database.database_role
|
|
536
|
+
|
|
537
|
+
request = BatchCreateSessionsRequest(
|
|
538
|
+
database=database.name,
|
|
539
|
+
session_count=self.size,
|
|
540
|
+
session_template=Session(creator_role=self.database_role),
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
span_event_attributes = {"kind": type(self).__name__}
|
|
544
|
+
current_span = get_current_span()
|
|
545
|
+
requested_session_count = request.session_count
|
|
546
|
+
if requested_session_count <= 0:
|
|
547
|
+
add_span_event(
|
|
548
|
+
current_span,
|
|
549
|
+
f"Invalid session pool size({requested_session_count}) <= 0",
|
|
550
|
+
span_event_attributes,
|
|
551
|
+
)
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
add_span_event(
|
|
555
|
+
current_span,
|
|
556
|
+
f"Requesting {requested_session_count} sessions",
|
|
557
|
+
span_event_attributes,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
observability_options = getattr(self._database, "observability_options", None)
|
|
561
|
+
with trace_call(
|
|
562
|
+
"CloudSpanner.PingingPool.BatchCreateSessions",
|
|
563
|
+
observability_options=observability_options,
|
|
564
|
+
metadata=metadata,
|
|
565
|
+
) as span, MetricsCapture():
|
|
566
|
+
returned_session_count = 0
|
|
567
|
+
while returned_session_count < self.size:
|
|
568
|
+
resp = api.batch_create_sessions(
|
|
569
|
+
request=request,
|
|
570
|
+
metadata=database.metadata_with_request_id(
|
|
571
|
+
database._next_nth_request,
|
|
572
|
+
1,
|
|
573
|
+
metadata,
|
|
574
|
+
span,
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
add_span_event(
|
|
579
|
+
span,
|
|
580
|
+
f"Created {len(resp.session)} sessions",
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
for session_pb in resp.session:
|
|
584
|
+
session = self._new_session()
|
|
585
|
+
returned_session_count += 1
|
|
586
|
+
session._session_id = session_pb.name.split("/")[-1]
|
|
587
|
+
self.put(session)
|
|
588
|
+
|
|
589
|
+
add_span_event(
|
|
590
|
+
span,
|
|
591
|
+
f"Requested for {requested_session_count} sessions, returned {returned_session_count}",
|
|
592
|
+
span_event_attributes,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def get(self, timeout=None):
|
|
596
|
+
"""Check a session out from the pool.
|
|
597
|
+
|
|
598
|
+
:type timeout: int
|
|
599
|
+
:param timeout: seconds to block waiting for an available session
|
|
600
|
+
|
|
601
|
+
:rtype: :class:`~google.cloud.spanner_v1.session.Session`
|
|
602
|
+
:returns: an existing session from the pool, or a newly-created
|
|
603
|
+
session.
|
|
604
|
+
:raises: :exc:`queue.Empty` if the queue is empty.
|
|
605
|
+
"""
|
|
606
|
+
if timeout is None:
|
|
607
|
+
timeout = self.default_timeout
|
|
608
|
+
|
|
609
|
+
start_time = time.time()
|
|
610
|
+
span_event_attributes = {"kind": type(self).__name__}
|
|
611
|
+
current_span = get_current_span()
|
|
612
|
+
add_span_event(
|
|
613
|
+
current_span,
|
|
614
|
+
"Waiting for a session to become available",
|
|
615
|
+
span_event_attributes,
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
ping_after = None
|
|
619
|
+
session = None
|
|
620
|
+
try:
|
|
621
|
+
ping_after, session = self._sessions.get(block=True, timeout=timeout)
|
|
622
|
+
except queue.Empty as e:
|
|
623
|
+
add_span_event(
|
|
624
|
+
current_span,
|
|
625
|
+
"No sessions available in the pool within the specified timeout",
|
|
626
|
+
span_event_attributes,
|
|
627
|
+
)
|
|
628
|
+
raise e
|
|
629
|
+
|
|
630
|
+
if _NOW() > ping_after:
|
|
631
|
+
# Using session.exists() guarantees the returned session exists.
|
|
632
|
+
# session.ping() uses a cached result in the backend which could
|
|
633
|
+
# result in a recently deleted session being returned.
|
|
634
|
+
if not session.exists():
|
|
635
|
+
session = self._new_session()
|
|
636
|
+
session.create()
|
|
637
|
+
|
|
638
|
+
span_event_attributes.update(
|
|
639
|
+
{
|
|
640
|
+
"time.elapsed": time.time() - start_time,
|
|
641
|
+
"session.id": session._session_id,
|
|
642
|
+
"kind": "pinging_pool",
|
|
643
|
+
}
|
|
644
|
+
)
|
|
645
|
+
add_span_event(current_span, "Acquired session", span_event_attributes)
|
|
646
|
+
return session
|
|
647
|
+
|
|
648
|
+
def put(self, session):
|
|
649
|
+
"""Return a session to the pool.
|
|
650
|
+
|
|
651
|
+
Never blocks: if the pool is full, raises.
|
|
652
|
+
|
|
653
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
654
|
+
:param session: the session being returned.
|
|
655
|
+
|
|
656
|
+
:raises: :exc:`queue.Full` if the queue is full.
|
|
657
|
+
"""
|
|
658
|
+
self._sessions.put_nowait((_NOW() + self._delta, session))
|
|
659
|
+
|
|
660
|
+
def clear(self):
|
|
661
|
+
"""Delete all sessions in the pool."""
|
|
662
|
+
while True:
|
|
663
|
+
try:
|
|
664
|
+
_, session = self._sessions.get(block=False)
|
|
665
|
+
except queue.Empty:
|
|
666
|
+
break
|
|
667
|
+
else:
|
|
668
|
+
session.delete()
|
|
669
|
+
|
|
670
|
+
def ping(self):
|
|
671
|
+
"""Refresh maybe-expired sessions in the pool.
|
|
672
|
+
|
|
673
|
+
This method is designed to be called from a background thread,
|
|
674
|
+
or during the "idle" phase of an event loop.
|
|
675
|
+
"""
|
|
676
|
+
while True:
|
|
677
|
+
try:
|
|
678
|
+
ping_after, session = self._sessions.get(block=False)
|
|
679
|
+
except queue.Empty: # all sessions in use
|
|
680
|
+
break
|
|
681
|
+
if ping_after > _NOW(): # oldest session is fresh
|
|
682
|
+
# Re-add to queue with existing expiration
|
|
683
|
+
self._sessions.put((ping_after, session))
|
|
684
|
+
break
|
|
685
|
+
try:
|
|
686
|
+
session.ping()
|
|
687
|
+
except NotFound:
|
|
688
|
+
session = self._new_session()
|
|
689
|
+
session.create()
|
|
690
|
+
# Re-add to queue with new expiration
|
|
691
|
+
self.put(session)
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class TransactionPingingPool(PingingPool):
|
|
695
|
+
"""Concrete session pool implementation:
|
|
696
|
+
|
|
697
|
+
Deprecated: TransactionPingingPool no longer begins a transaction for each of its sessions at startup.
|
|
698
|
+
Hence the TransactionPingingPool is same as :class:`PingingPool` and maybe removed in the future.
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
In addition to the features of :class:`PingingPool`, this class
|
|
702
|
+
creates and begins a transaction for each of its sessions at startup.
|
|
703
|
+
|
|
704
|
+
When a session is returned to the pool, if its transaction has been
|
|
705
|
+
committed or rolled back, the pool creates a new transaction for the
|
|
706
|
+
session and pushes the transaction onto a separate queue of "transactions
|
|
707
|
+
to begin." The application is responsible for flushing this queue
|
|
708
|
+
as appropriate via the pool's :meth:`begin_pending_transactions` method.
|
|
709
|
+
|
|
710
|
+
:type size: int
|
|
711
|
+
:param size: fixed pool size
|
|
712
|
+
|
|
713
|
+
:type default_timeout: int
|
|
714
|
+
:param default_timeout: default timeout, in seconds, to wait for
|
|
715
|
+
a returned session.
|
|
716
|
+
|
|
717
|
+
:type ping_interval: int
|
|
718
|
+
:param ping_interval: interval at which to ping sessions.
|
|
719
|
+
|
|
720
|
+
:type labels: dict (str -> str) or None
|
|
721
|
+
:param labels: (Optional) user-assigned labels for sessions created
|
|
722
|
+
by the pool.
|
|
723
|
+
|
|
724
|
+
:type database_role: str
|
|
725
|
+
:param database_role: (Optional) user-assigned database_role for the session.
|
|
726
|
+
"""
|
|
727
|
+
|
|
728
|
+
def __init__(
|
|
729
|
+
self,
|
|
730
|
+
size=10,
|
|
731
|
+
default_timeout=10,
|
|
732
|
+
ping_interval=3000,
|
|
733
|
+
labels=None,
|
|
734
|
+
database_role=None,
|
|
735
|
+
):
|
|
736
|
+
"""This throws a deprecation warning on initialization."""
|
|
737
|
+
warn(
|
|
738
|
+
f"{self.__class__.__name__} is deprecated.",
|
|
739
|
+
DeprecationWarning,
|
|
740
|
+
stacklevel=2,
|
|
741
|
+
)
|
|
742
|
+
self._pending_sessions = queue.Queue()
|
|
743
|
+
|
|
744
|
+
super(TransactionPingingPool, self).__init__(
|
|
745
|
+
size,
|
|
746
|
+
default_timeout,
|
|
747
|
+
ping_interval,
|
|
748
|
+
labels=labels,
|
|
749
|
+
database_role=database_role,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
self.begin_pending_transactions()
|
|
753
|
+
|
|
754
|
+
def bind(self, database):
|
|
755
|
+
"""Associate the pool with a database.
|
|
756
|
+
|
|
757
|
+
:type database: :class:`~google.cloud.spanner_v1.database.Database`
|
|
758
|
+
:param database: database used by the pool to create sessions
|
|
759
|
+
when needed.
|
|
760
|
+
"""
|
|
761
|
+
super(TransactionPingingPool, self).bind(database)
|
|
762
|
+
self._database_role = self._database_role or self._database.database_role
|
|
763
|
+
self.begin_pending_transactions()
|
|
764
|
+
|
|
765
|
+
def put(self, session):
|
|
766
|
+
"""Return a session to the pool.
|
|
767
|
+
|
|
768
|
+
Never blocks: if the pool is full, raises.
|
|
769
|
+
|
|
770
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
771
|
+
:param session: the session being returned.
|
|
772
|
+
|
|
773
|
+
:raises: :exc:`queue.Full` if the queue is full.
|
|
774
|
+
"""
|
|
775
|
+
if self._sessions.full():
|
|
776
|
+
raise queue.Full
|
|
777
|
+
|
|
778
|
+
txn = session._transaction
|
|
779
|
+
if txn is None or txn.committed or txn.rolled_back:
|
|
780
|
+
session.transaction()
|
|
781
|
+
self._pending_sessions.put(session)
|
|
782
|
+
else:
|
|
783
|
+
super(TransactionPingingPool, self).put(session)
|
|
784
|
+
|
|
785
|
+
def begin_pending_transactions(self):
|
|
786
|
+
"""Begin all transactions for sessions added to the pool."""
|
|
787
|
+
while not self._pending_sessions.empty():
|
|
788
|
+
session = self._pending_sessions.get()
|
|
789
|
+
super(TransactionPingingPool, self).put(session)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class SessionCheckout(object):
|
|
793
|
+
"""Context manager: hold session checked out from a pool.
|
|
794
|
+
|
|
795
|
+
:type pool: concrete subclass of
|
|
796
|
+
:class:`~google.cloud.spanner_v1.pool.AbstractSessionPool`
|
|
797
|
+
:param pool: Pool from which to check out a session.
|
|
798
|
+
|
|
799
|
+
:param kwargs: extra keyword arguments to be passed to :meth:`pool.get`.
|
|
800
|
+
"""
|
|
801
|
+
|
|
802
|
+
_session = None # Not checked out until '__enter__'.
|
|
803
|
+
|
|
804
|
+
def __init__(self, pool, **kwargs):
|
|
805
|
+
self._pool = pool
|
|
806
|
+
self._kwargs = kwargs.copy()
|
|
807
|
+
|
|
808
|
+
def __enter__(self):
|
|
809
|
+
self._session = self._pool.get(**self._kwargs)
|
|
810
|
+
return self._session
|
|
811
|
+
|
|
812
|
+
def __exit__(self, *ignored):
|
|
813
|
+
self._pool.put(self._session)
|