Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement multi-use snapshots #3615

Merged
merged 19 commits into from
Jul 26, 2017
Merged

Implement multi-use snapshots #3615

merged 19 commits into from
Jul 26, 2017

Conversation

tseaver
Copy link
Contributor

@tseaver tseaver commented Jul 17, 2017

Multi-use snapshots trigger an "implicit" server-side transaction, and capture its ID on the first request. Subsequent requests return that ID, allowing for isolation from other changes. We default to multi_use=False because that mode is much more performant for the simple case.

This feature is one which we originally decided to leave out, but the P0 system test list requires that it be implemented.

I think that a commitwise review might be easier than reviewing the whole enchilada.

@tseaver tseaver added api: spanner Issues related to the Spanner API. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design. labels Jul 17, 2017
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Jul 17, 2017
Copy link
Contributor

@dhermes dhermes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real issues, just cosmetics.

Sorry for the delay in review.

@@ -389,8 +389,7 @@ def batch(self):
"""
return BatchCheckout(self)

def snapshot(self, read_timestamp=None, min_read_timestamp=None,
max_staleness=None, exact_staleness=None):
def snapshot(self, **kw):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@@ -168,11 +174,17 @@ def __init__(self, session, read_timestamp=None, min_read_timestamp=None,
if len(flagged) > 1:
raise ValueError("Supply zero or one options.")

if multi_use and (min_read_timestamp or max_staleness):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@@ -168,11 +174,17 @@ def __init__(self, session, read_timestamp=None, min_read_timestamp=None,
if len(flagged) > 1:
raise ValueError("Supply zero or one options.")

if multi_use and (min_read_timestamp or max_staleness):
raise ValueError(
"'multi_use' is incompatile with "

This comment was marked as spam.

This comment was marked as spam.

@@ -130,6 +134,7 @@ def consume_next(self):
self._resume_token = response.resume_token

if self._metadata is None: # first response
# XXX: copy implicit txn ID to snapshot, if present.

This comment was marked as spam.

@@ -274,6 +292,12 @@ def test_execute_sql_normal(self):
self.assertEqual(options.kwargs['metadata'],
[('google-cloud-resource-prefix', database.name)])

def test_execute_sql_wo_mulit_use(self):

This comment was marked as spam.

@@ -99,15 +99,52 @@ def _makeListValue(values=(), value_pbs=None):
return Value(list_value=ListValue(values=value_pbs))
return Value(list_value=_make_list_value_pb(values))

@staticmethod
def _makeResultSetMetadata(fields=(), transaction_id=None):

This comment was marked as spam.

This comment was marked as spam.

@@ -13,6 +13,7 @@
# limitations under the License.


import mock

This comment was marked as spam.

This comment was marked as spam.

@tseaver tseaver force-pushed the spanner-multi_use_snapshot branch 2 times, most recently from ad62692 to d445a73 Compare July 19, 2017 22:43
Copy link
Contributor

@dhermes dhermes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tseaver Can you stop adding system tests to this PR? They keep coming in "after review" (e.g. 7d6fa02)


def test_multiuse_snapshot_read_isolation_exact_staleness(self):
import time
from datetime import timedelta

This comment was marked as spam.

@tseaver
Copy link
Contributor Author

tseaver commented Jul 19, 2017

Can you stop adding system tests to this PR? They keep coming in "after review"

I haven't added any: I had to rebase to fix the conflicts with your assertIs PR.

if self._multi_use:
return StreamedResultSet(iterator, source=self)
else:
return StreamedResultSet(iterator)

def execute_sql(self, sql, params=None, param_types=None, query_mode=None,
resume_token=b''):

This comment was marked as spam.

This comment was marked as spam.

@@ -157,9 +165,17 @@ class Snapshot(_SnapshotBase):
:type exact_staleness: :class:`datetime.timedelta`
:param exact_staleness: Execute all reads at a timestamp that is
``exact_staleness`` old.

:type multi_use: :class:`bool`
:param multi_use: If true, the first read operation creates a read-only

This comment was marked as spam.

if self._multi_use:
return StreamedResultSet(iterator, source=self)
else:
return StreamedResultSet(iterator)

This comment was marked as spam.

@vkedia
Copy link

vkedia commented Jul 20, 2017

The documentation on this page suggests that snapshot already allows you to do multiple reads at a consistent snapshot.
This is what it says:

A Snapshot represents a read-only transaction: when multiple read operations are peformed via a Snapshot, the results are consistent as of a particular point in time.

In light of this PR that is clearly incorrect.

@jonparrott We also claim the same thing in our [sample code])
https://cloud.google.com/spanner/docs/getting-started/python/#retrieve_data_using_read-only_transactions). That too is incorrect.

@lukesneeringer @bjwatson Can we please get this merged asap. This is a severe bug.

@vkedia vkedia added priority: p0 Highest priority. Critical issue. P0 implies highest priority. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns. and removed type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design. labels Jul 20, 2017
Copy link
Contributor

@lukesneeringer lukesneeringer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing no actual concerns (either in my own review or the other comments), this is good to go out.

@vkedia
Copy link

vkedia commented Jul 20, 2017

@lukesneeringer I believe my comments need to be addressed before merging this. Specifically
#3615 (comment)

@lukesneeringer
Copy link
Contributor

lukesneeringer commented Jul 20, 2017

Ah -- confirmed. Thanks @vkedia. (@tseaver His proposed solution seems reasonable to me. Thoughts?)

@vkedia
Copy link

vkedia commented Jul 21, 2017

@tseaver Any updates on this?

@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

I can see adding an explicit Snapshot.begin to capture a transaction ID.

In addition, I had thought of having a _transaction_pending flag on the multi-use snapshot, which would cause subsequent read / execute_sql requests to raise an exception. This would smooth the "garden path" usage (i.e., reading / querying with fetch, rather than interleaving).

- Convert 'Database.snapshot' and 'Session.snapshot' factories to take /
  forward '**kw'.
- When reading / executing SQL for a multi-use snapshot, pass the snapshot
  as the iterator's source.
- PartialResultSet
- ResultSetMetadata
- ResultSetStats.
- Source will only be set for multi-use snapshots.
- Valid only for multi-use snapshots.
- Raises if the snapshot already has a transaction ID.
@tseaver tseaver force-pushed the spanner-multi_use_snapshot branch from 799ce25 to 230d715 Compare July 24, 2017 17:49
@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

To implement the single-use request guard, the snapshot needs to maintain a request counter, which we can use to detect the "interleaved" case.

@vkedia
Copy link

vkedia commented Jul 24, 2017

That sounds good. We can have an explicit begin which users can chose to call to get a transaction id and then they can interleave reads. If they do not call begin then the first read or query will create the transaction and if they try to interleave before it has returned they will get back an error.
So essentially users can interleave reads only after there is a transaction id which can be got by either calling begin or by the first read

@vkedia
Copy link

vkedia commented Jul 24, 2017

Actually why do we need Snapshot to support single_use? For single use transactions database.read and database.query method should be sufficient. We should extend those methods to also take timestamp bound specific options.

@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

Database.read and Database.execute_sql create Snapshot instances under the covers (via Session.read / Session.execute_sql). I just opened #3659 to track adding the read isolation parameters to those methods.

@tseaver tseaver force-pushed the spanner-multi_use_snapshot branch from 1fff028 to 5dc09f8 Compare July 24, 2017 19:15
@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

@vkedia I've just updated the system tests to pass multi_use=True where needed (I also fixed a new bug where Transaction was not implicitly multi-use for reads). I am, however, seeing system failures on the third invocation using multi-use snapshots:

________________ TestSessionAPI.test_execute_sql_w_query_param _________________
Traceback (most recent call last):
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/tests/system/test_system.py", line 960, in test_execute_sql_w_query_param
    expected=[(19,), (99,)],
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/tests/system/test_system.py", line 888, in _check_sql_results
    sql, params=params, param_types=param_types))
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/google/cloud/spanner/streamed.py", line 166, in __iter__
    self.consume_next()  # raises StopIteration
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/google/cloud/spanner/streamed.py", line 132, in consume_next
    response = six.next(self._response_iterator)
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/grpc/_channel.py", line 363, in __next__
    return self._next()
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/grpc/_channel.py", line 357, in _next
    raise self
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.INVALID_ARGUMENT, Transaction was started in a different session.)>
______________________ TestSessionAPI.test_read_w_ranges _______________________
Traceback (most recent call last):
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/tests/system/test_system.py", line 846, in test_read_w_ranges
    self.TABLE, self.COLUMNS, keyset))
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/google/cloud/spanner/streamed.py", line 166, in __iter__
    self.consume_next()  # raises StopIteration
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/google/cloud/spanner/streamed.py", line 132, in consume_next
    response = six.next(self._response_iterator)
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/grpc/_channel.py", line 363, in __next__
    return self._next()
  File "/home/tseaver/projects/agendaless/Google/src/google-cloud-python/spanner/.nox/sys-3-6/lib/python3.6/site-packages/grpc/_channel.py", line 357, in _next
    raise self
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.INVALID_ARGUMENT, Transaction was started in a different session.)>

I tried putting an explicit snapshot.begin() before the repeated read / execute_sql calls, but that didnt' change anything. Would the back-end be somehow be passing back a new transaction ID for the second read / execute_sql request (which does succeed)?

@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

Looks like the back-end passes back an empty transaction ID for read requests after the first one. 0966806 keeps us from clearing it in that case.

@vkedia
Copy link

vkedia commented Jul 24, 2017

Thats right. Transaction field is only set if the read or query started a new transaction.
https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.ResultSetMetadata

@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

The new system tests are passing.

@dhermes
Copy link
Contributor

dhermes commented Jul 24, 2017

@tseaver What are you looking for here in terms of review?

@tseaver
Copy link
Contributor Author

tseaver commented Jul 24, 2017

@dhermes the commits which are new since @lukesneeringer gave an LGTM on Thursday (the rest are just rebase to fix conflicts). a5219a5 is the hash of that rebased commit, so the diff would be: a5219a5...spanner-multi_use_snapshot

@tseaver
Copy link
Contributor Author

tseaver commented Jul 26, 2017

@vkedia, @dhermes any issues remaining?

@dhermes
Copy link
Contributor

dhermes commented Jul 26, 2017

LGTM

@tseaver tseaver merged commit e273319 into master Jul 26, 2017
@tseaver tseaver deleted the spanner-multi_use_snapshot branch July 26, 2017 22:00
@bjwatson
Copy link

@tseaver I just saw this. I think that @vkedia will want to review this more when he returns from vacation on Monday. I guess if he finds anything else, it can be addressed in a separate PR.

Let's remain Alpha until next week. (FYI @lukesneeringer)

@bjwatson
Copy link

bjwatson commented Aug 8, 2017

@vkedia Do you plan to finish reviewing this post-merge?

landrito pushed a commit to landrito/google-cloud-python that referenced this pull request Aug 21, 2017
landrito pushed a commit to landrito/google-cloud-python that referenced this pull request Aug 22, 2017
landrito pushed a commit to landrito/google-cloud-python that referenced this pull request Aug 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the Spanner API. cla: yes This human has signed the Contributor License Agreement. priority: p0 Highest priority. Critical issue. P0 implies highest priority. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants