Skip to content

DRIVERS-1607: Snapshot reads on Secondaries #1022

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

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions source/causal-consistency/causal-consistency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ this:
All read operations performed using this session will now be causally
consistent.

If no value is provided for ``causalConsistency`` a value of true is
implied. See the ``causalConsistency`` section.
If no value is provided for ``causalConsistency`` and snapshot reads are not requested
a value of true is implied. See the ``causalConsistency`` section.

MongoClient changes
===================
Expand Down
18 changes: 16 additions & 2 deletions source/read-write-concern/read-write-concern.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Read and Write Concern
:Status: Approved
:Type: Standards
:Server Versions: 2.4+
:Last Modified: 2021-04-07
:Version: 1.5.5
:Last Modified: 2021-06-15
:Version: 1.6

.. contents::

Expand Down Expand Up @@ -81,6 +81,11 @@ Defined below are the constructs for drivers.
* This is rendered as "available" (lower-case) on the wire.
*/
available

/**
* This is rendered as "snapshot" (lower-case) on the wire.
*/
snapshot
}

class ReadConcern {
Expand Down Expand Up @@ -131,6 +136,14 @@ considered the server’s default ``ReadConcern``.
default ``ReadConcern`` while the latter is the user explicitly specifying a
``ReadConcern`` with a ``level`` of “local”.

Snapshot Read Concern
---------------------

When a ``ReadConcern`` ``level`` ``snapshot`` is used, ``atClusterTime`` may be specified to indicate
the desired point in time for reading. ``find``, ``aggregate`` and ``distinct`` operations executed with ``ReadConcern`` ``snapshot`` but without ``atClusterTime``
will return ``atClusterTime`` timestamp in the server response. The obtained ``atClusterTime`` timestamp can be used for subsequent
read operations.
``ReadConcern`` ``level`` ``snapshot`` with ``clusterTime`` is supported in ``find``, ``aggregate`` and ``distinct`` operations.

On the Wire
-----------
Expand Down Expand Up @@ -701,3 +714,4 @@ Version History
- 2019-10-31: Explicitly define write concern option mappings.
- 2020-02-13: Inconsistent write concern must be considered an error.
- 2021-04-07: Updated to use hello command.
- 2021-06-15: Added "snapshot" to Readconcern level
6 changes: 3 additions & 3 deletions source/read-write-concern/tests/README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=======================
Connection String Tests
=======================
============================
Read and Write Concern Tests
============================

The YAML and JSON files in this directory tree are platform-independent tests
that drivers can use to prove their conformance to the Read and Write Concern
Expand Down
271 changes: 271 additions & 0 deletions source/sessions/snapshot-sessions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
============================
Snapshot Reads Specification
============================

:Spec Title: Snapshot Reads Specification (See the registry of specs)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need to call out a registry of specs here

Suggested change
:Spec Title: Snapshot Reads Specification (See the registry of specs)
:Spec Title: Snapshot Reads Specification

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what's the convention here as this referral appears in causal-consistency.rst and driver-sessions.rst.
Removed for now.

Copy link
Member

Choose a reason for hiding this comment

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

I believe those are leftovers from the templates. I'll give the specs a quick look to clean these up. Thanks for pointing out other instances.

:Spec Version: 1.0
:Author: Boris Dogadov
:Advisors: Jeff Yemin, A. Jesse Jiryu Davis, Judah Schvimer
:Status: Draft (Could be Draft, Accepted, Rejected, Final, or Replaced)
Copy link
Member

Choose a reason for hiding this comment

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

Reminder to change this to "Accepted" before merging.

Suggested change
:Status: Draft (Could be Draft, Accepted, Rejected, Final, or Replaced)
:Status: Accepted

:Type: Standards
:Minimum Server Version: 5.0
:Last Modified: 15-Jun-2021

.. contents::

--------

Abstract
========

Version 5.0 of the server introduces support for read concern level "snapshot" (non-speculative)
for read commands outside of transactions, including on secondaries.
This spec builds upon the `Sessions Specification <../driver-sessions.rst>`_ to define how an application
requests "snapshot" level read concern and how a driver interacts with the server
to implement snapshot reads.

Definitions
===========

META
----

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”,
“SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be
interpreted as described in `RFC 2119 <https://www.ietf.org/rfc/rfc2119.txt>`_.

Terms
-----

ClientSession
The driver object representing a client session and the operations that can be
performed on it.

MongoClient
The root object of a driver's API. MAY be named differently in some drivers.

MongoCollection
The driver object representing a collection and the operations that can be
performed on it. MAY be named differently in some drivers.

MongoDatabase
The driver object representing a database and the operations that can be
performed on it. MAY be named differently in some drivers.

ServerSession
The driver object representing a server session.

Session
A session is an abstract concept that represents a set of sequential
operations executed by an application that are related in some way. This
specification defines how sessions are used to implement snapshot reads.

Snapshot reads
Reads with read concern level ``snapshot`` that occur outside of transactions on
both the primary and secondary nodes, including in sharded clusters.
Snapshots reads are majority committed reads.

Snapshot timestamp
Snapshot timestamp, representing timestamp of the first read (find/aggregate/distinct operation) in the session.
The server creates a cursor in response to a snapshot find/aggregate command and
reports ``atClusterTime`` field in the cursor response. For distinct command server adds ``atClusterTime`` field to the distinct response object.
``atClusterTime`` field represents the timestamp of the read and is guaranteed to be majority committed.

Specification
=============

An application requests snapshot reads by creating a ``ClientSession``
with options that specify that snapshot reads are desired. An
application then passes the session as an argument to methods in the
``MongoDatabase`` and ``MongoCollection`` classes. Read operations (find/aggregate/distinct) performed against
that session will be read from the same snapshot.

High level summary of the API changes for snapshot reads
========================================================

Snapshot reads are built on top of client sessions.

Applications will start a new client session for snapshot reads like
this:

.. code:: typescript

options = new SessionOptions(snapshot = true);
session = client.startSession(options);

All read operations performed using this session will be read from the same snapshot.

If no value is provided for ``snapshot`` a value of false is
implied.
There are no MongoDatabase, MongoClient or MongoCollection API changes.

SessionOptions changes
======================

``SessionOptions`` change summary

.. code:: typescript

class SessionOptions {
Optional<bool> snapshot;

// other options defined by other specs
}

In order to support snapshot reads a new property named
``snapshot`` is added to ``SessionOptions``. Applications set
``snapshot`` when starting a client session to indicate
whether they want snapshot reads. All read operations performed
using that client session will share the same snapshot.

Each new member is documented below.

snapshot
--------

Applications set ``snapshot`` when starting a session to
indicate whether they want snapshot reads.

Note that the ``snapshot`` property is optional. The default value of
this property is false.

Snapshot reads and causal consistency are mutually exclusive. Therefore if ``snapshot`` is set to true,
``causalConsistency`` property is set to false. Client MUST throw an Error if both ``snapshot`` and ``causalConsistency`` are set to true.
Snapshot reads are supported both on primaries and secondaries.

ClientSession changes
=====================

Transaction are not allowed with snapshot sessions.
Calling ``session.startTransaction(options)`` on snapshot session should raise an error.

ReadConcern changes
===================

``snapshot`` added to `ReadConcernLevel enumeration <../read-write-concern/read-write-concern.rst#read-concern>`_.`.

Server Commands
===============

There are no new server commands related to snapshot reads. Instead,
snapshot reads are implemented by:

1. Saving the ``atClusterTime`` returned by 5.0+ servers for the first find/aggregate/distinct operation in a
private property ``snapshotTime`` of the ``ClientSession`` object. Drivers MUST save the ``atClusterTime``
in the ``ClientSession`` object.

2. Passing that ``snapshotTime`` in the ``atClusterTime`` field of the ``readConcern`` field
for subsequent snapshot read operations (for find/aggregate/distinct commands).

Server Command Responses
Copy link
Member

Choose a reason for hiding this comment

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

When the driver starts a snapshot session but the server does not return the atClusterTime field the driver needs to raise a client side error indicating that server does not support snapshot reads. If we don't do this then snapshot reads can silently fail.

Copy link
Member

@ShaneHarvey ShaneHarvey Jun 24, 2021

Choose a reason for hiding this comment

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

False alarm. The problem was that my implementation neglected to set readConcern.level to ‘snapshot’. When the driver correctly sends ‘snapshot’ old servers will reject the request with this error:

{'operationTime': Timestamp(1624572085, 12), 'ok': 0.0, 'errmsg': 'read concern level snapshot is only valid in a transaction', 'code': 72, 'codeName': 'InvalidOptions', '$clusterTime': {'clusterTime': Timestamp(1624572085, 12), 'signature': {'hash': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'keyId': 0}}}

Can we add a unified test case that confirms that the server returns an error when drivers try to use a snapshot session? It should run on 3.6-4.4.

========================

To support snapshot reads the server returns the ``atClusterTime`` in
cursor object it sends to the driver (for both find/aggregate commands).

.. code:: typescript

{
ok : 1 or 0,
... // the rest of the command reply
cursor : {
... // the rest of the cursor reply
atClusterTime : <BsonTimestamp>
}
}

For distinct commands server returns the ``atClusterTime`` in
distinct response object it sends to the driver.

.. code:: typescript

{
ok : 1 or 0,
... // the rest of the command reply
atClusterTime : <BsonTimestamp>
}

The ``atClusterTime`` MUST be stored in the ``ClientSession`` to later be passed as the
``atClusterTime`` field of the ``readConcern`` with ``snapshot`` level field in subsequent read operations.

Server Errors
=============
1. The server may reply to read commands with a ``SnapshotTooOld`` error if the client's ``atClusterTime`` value is not available in the server's history.
2. The server will return ``InvalidOptions`` error if both ``atClusterTime`` and ``afterClusterTime`` options are set to true.

Snapshot read commands
======================

For snapshot reads the driver MUST first obtain ``atClusterTime`` from the server response of find/aggregate/distinct command,
by specifying ``readConcern`` with ``snapshot`` level field, and store it as ``snapshotTime`` in
``ClientSession`` object.

.. code:: typescript

{
find : <string>, // or other read command
... // the rest of the command parameters
readConcern :
{
level : "snapshot"
}
}

For subsequent reads from same snapshot driver MUST send the ``snapshotTime`` saved in
the ``ClientSession`` as the value of the ``atClusterTime`` field of the
``readConcern`` with ``snapshot`` level field:

.. code:: typescript

{
find : <string>, // or other read command
... // the rest of the command parameters
readConcern :
{
level : "snapshot",
atClusterTime : <BsonTimestamp>
}
}

Lists of commands that support snapshot reads:

1. find
2. aggregate
3. distinct

Motivation
==========

To support snapshot reads. Only supported with server version 5.0+ or newer.

Design Rationale
================

The goal is to modify the driver API as little as possible so that existing
programs that don't need snapshot reads don't have to be changed.
This goal is met by defining a ``SessionOptions`` field that applications use to
start a ``ClientSession`` that can be used for snapshot reads. Alternative explicit approach of
obtaining ``atClusterTime`` from ``cursor`` object and passing it to read concern object was considered initially.
Session based approach was chosen as it aligns better with the existing API, and requires minimal API changes.
Future extensibility for snapshot reads would be better served by session based approach, as no API changes will be required.

Backwards Compatibility
=======================

The API changes to support sessions extend the existing API but do not
introduce any backward breaking changes. Existing programs that don't use
snapshot reads continue to compile and run correctly.

Reference Implementation
========================

C# driver will provide the reference implementation.
The corresponding ticket is `CSHARP-3668 <https://jira.mongodb.org/browse/CSHARP-3668>`_.

Q&A
===

Changelog
=========

:2021-06-15: Initial version.
26 changes: 23 additions & 3 deletions source/sessions/tests/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ Driver Session Tests
Introduction
============

The YAML and JSON files in this directory are platform-independent tests that
drivers can use to prove their conformance to the Driver Sessions Spec. They are
The YAML and JSON files in the ``legacy`` and ``unified`` sub-directories are platform-independent tests
that drivers can use to prove their conformance to the Driver Sessions Spec. They are
designed with the intention of sharing most test-runner code with the
Transactions spec tests.
`Transactions Spec tests <../../transactions/tests/README.rst#test-format>`_.. Tests in the
``unified`` directory are written using the `Unified Test Format <../../unified-test-format/unified-test-format.rst>`_.

Several prose tests, which are not easily expressed in YAML, are also presented
in the Driver Sessions Spec. Those tests will need to be manually implemented
Expand Down Expand Up @@ -78,7 +79,26 @@ the given session is *not* marked dirty::
arguments:
session: session0

Snapshot session tests
======================
Snapshot sessions tests require server of version 5.0 or higher and
replica set or a sharded cluster deployment.
Default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow configuration
may lead to `SnapshotTooOld` errors. Drivers can work around this issue by increasing the server's `minSnapshotHistoryWindowInSeconds` parameter, for example:

.. code:: python

client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=60)

Prose tests
```````````
- Setting both ``snapshot`` and ``causalConsistency`` is not allowed

* ``client.startSession(snapshot = true, causalConsistency = true)``
* Assert that an error was raised by driver
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a unified test for this? It could run the client.startSession operation with snapshot = true, causalConsistency = true arguments and assert that we raise a client-side error with isClientError: true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That might be a good idea, but session entities creation stage does not have error validation, and we don't have startSession operation. Introducing startSession operation for all drivers instead of prose test seemed overkill in this case.
Is there any simple way to achieve that?

Copy link
Member

@ShaneHarvey ShaneHarvey Jun 25, 2021

Choose a reason for hiding this comment

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

and we don't have startSession operation.

The unified test runner allows us to call any method on the client including startSession. I don't think it's overkill but we don't need to do it in this PR. I'll open a drivers ticket for it.

Edit: or maybe this would require a "schemaVersion" bump in the unified test format. Still I think it's worthwhile to be able to call startSession within tests.


Changelog
=========

:2019-05-15: Initial version.
:2021-06-15: Added snapshot-session tests. Introduced legacy and unified folders.
Loading