From a7cc3c1281c918673a74e0f642173d3992777888 Mon Sep 17 00:00:00 2001 From: Emily Date: Tue, 16 Jan 2018 14:02:29 +0100 Subject: [PATCH 1/4] RUBY-1271 Sessions tutorial --- docs/ruby-driver-tutorials.txt | 1 + docs/tutorials/ruby-driver-sessions.txt | 115 ++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 docs/tutorials/ruby-driver-sessions.txt diff --git a/docs/ruby-driver-tutorials.txt b/docs/ruby-driver-tutorials.txt index 6ce883c3f6..855650ca0d 100644 --- a/docs/ruby-driver-tutorials.txt +++ b/docs/ruby-driver-tutorials.txt @@ -19,6 +19,7 @@ operations available in the Ruby driver. /tutorials/ruby-driver-crud-operations /tutorials/ruby-driver-collection-tasks /tutorials/ruby-driver-projections + /tutorials/ruby-driver-sessions /tutorials/ruby-driver-admin-tasks /tutorials/ruby-driver-indexing /tutorials/ruby-driver-collations diff --git a/docs/tutorials/ruby-driver-sessions.txt b/docs/tutorials/ruby-driver-sessions.txt new file mode 100644 index 0000000000..525e2c051b --- /dev/null +++ b/docs/tutorials/ruby-driver-sessions.txt @@ -0,0 +1,115 @@ +============================ +Creating and using Sessions +============================ + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + + +Version 3.6 of the server introduces the concept of logical sessions for clients. +A session is an abstract concept that represents a set of sequential operations executed +by an application that are related in some way. A session object can be create via a ``Mongo::Client`` +and passed to operation methods that should be executed in the context of that session. + +Please note that sessions are not thread safe. They can only be used by one thread at a time. + +Creating a session from a ``Mongo::Client`` +------------------------------------------- + +A session can be created by calling the ``start_session`` method on a client: + +.. code-block:: ruby + + session = client.start_session + + +It is valid to call ``start_session`` with no options set. This will result in a +session that has no effect on the operations performed in the context of that session, +other than to include a session ID in commands sent to the server. Please see the API docs for all supported +session options. + +An error will be thrown if the driver is connected to a deployment that does not support sessions and the +``start_session`` method is called. + +Note that server sessions are discarded server-side if not used for a certain period of time. That said, +be aware that if the application calls ``#start_session`` on a client and waits more than 1 minute to use +the session, it risks getting errors due to the session going stale before it is used. + + + +Using a session +--------------- +A session can be passed to most operations performed via the driver so that the operation can be executed in the +context of that session. Please see the API docs for which methods support a session argument. + +Create a session and execute an insert, then a find using that session: + +.. code-block:: ruby + + session = client.start_session + client[:artists].insert_one({ :name => 'FKA Twigs' }, session: session) + client[:artists].find({ :name => 'FKA Twigs' }, session: session).first + +If you like to call methods on a ``Mongo::Collection::View`` that use a particular session, you can create the +``Mongo::Collection::View`` with the session and then call methods on it: + +.. code-block:: ruby + + session = client.start_session(causal_consistency: true) + view = client[:artists].find({ :name => 'FKA Twigs' }, session: session) + view.count # will use the session + +You can also pass the session option to the methods directly. This session will override any session associated with +the ``Mongo::Collection::View``: + +.. code-block:: ruby + + session = client.start_session + second_session = client.start_session + view = client[:artists].find({ :name => 'FKA Twigs' }, session: session) + view.count(session: second_session) # will use the second_session + +Causal Consistency +------------------ +A causally consistent session will let you read your writes and guarantee monotonically increasing +reads from secondaries. +To create a causally consistent session, set the ``causal_consistency`` option to true: + +.. code-block:: ruby + + session = client.start_session(causal_consistency: true) + + # The update message goes to the primary. + collection = client[:artists] + collection.update_one({ '_id' => 1 }, { '$set' => { 'x' => 0 } }, session: session) + + # Read your write, even when reading from a secondary! + collection.find({ '_id' => 1 }, limit: 1, session: session).first + + # This query returns data at least as new as the previous query, + # even if it chooses a different secondary. + collection.find({ '_id' => 2 }, limit: 1, session: session).first + +Since unacknowledged writes don't receive a response from the server (or don't wait for a response), the driver +has no way of keeping track of where the unacknowledged write is in logical time. Therefore, be aware that causally +consistent reads are not causally consistent with unacknowledged writes. + +Note that if you set the causal_consistency option to nil as in ``(causal_consistency: nil)``, it will be interpreted +as false. + +End a session +------------- +To end a session, call the ``end_session`` method: + +.. code-block:: ruby + + session.end_session + +The Ruby driver will then add the id for the corresponding server session to a pool for reuse. +When a client is closed, the driver will send a command to the server to end all sessions it has cached +in its server session pool. You may see this command in your logs when a client is closed. From 3f2beb18ac5d971b5c09084a41a5bad232bb6e51 Mon Sep 17 00:00:00 2001 From: Emily Date: Tue, 16 Jan 2018 14:32:22 +0100 Subject: [PATCH 2/4] RUBY-1271 Change Streams tutorial --- docs/ruby-driver-tutorials.txt | 1 + docs/tutorials/ruby-driver-change-streams.txt | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 docs/tutorials/ruby-driver-change-streams.txt diff --git a/docs/ruby-driver-tutorials.txt b/docs/ruby-driver-tutorials.txt index 855650ca0d..19d16b1572 100644 --- a/docs/ruby-driver-tutorials.txt +++ b/docs/ruby-driver-tutorials.txt @@ -20,6 +20,7 @@ operations available in the Ruby driver. /tutorials/ruby-driver-collection-tasks /tutorials/ruby-driver-projections /tutorials/ruby-driver-sessions + /tutorials/ruby-driver-change-streams /tutorials/ruby-driver-admin-tasks /tutorials/ruby-driver-indexing /tutorials/ruby-driver-collations diff --git a/docs/tutorials/ruby-driver-change-streams.txt b/docs/tutorials/ruby-driver-change-streams.txt new file mode 100644 index 0000000000..56e475154b --- /dev/null +++ b/docs/tutorials/ruby-driver-change-streams.txt @@ -0,0 +1,75 @@ +============== +Change Streams +============== + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +As of version 3.6 of the MongoDB server, a new ``$changeStream`` pipeline stage is supported in the aggregation +framework. The Ruby driver provides an API for receiving notifications for changes to a particular collection using this +new pipeline stage. Although you can create a change stream using the pipeline operator and aggregation framework +directly, it is recommended to use the driver API described below as the driver resumes the change stream if there is +timeout or network error. + +Change streams on the server requires a ``"majority"`` read concern or no read concern. + +Change streams do not work properly with JRuby because of the issue documented here_. + +.. _here: https://github.com/jruby/jruby/issues/4212 + +Namely, JRuby eagerly evaluates ```#next`` on an Enumerator in a background green thread. +So calling ```#next`` on the change stream will cause getmores to be called in a loop in the background. + +Watching for changes on a particular collection +----------------------------------------------- + +A change stream is created by calling the ``#watch`` method on a collection: + +.. code-block:: ruby + + stream = collection.watch + collection.insert_one(a: 1) + doc = stream.to_enum.next + process(doc) + + +You can also receive the notifications as they are available: + +.. code-block:: ruby + + stream = client[:test].watch + enum = stream.to_enum + while doc = enum.next + process(doc) + end + + +The change stream can take filters in the aggregation framework pipeline operator format: + +.. code-block:: ruby + + stream = collection.watch([{'$match' => { 'operationType' => {'$in' => ['insert', 'replace'] } } }, + {'$match' => { 'fullDocument.n' => { '$gte' => 1 } } } + ]) + enum = stream.to_enum + while doc = enum.next + process(doc) + end + +Close a Change Stream +--------------------- + +You can close a Change Stream by calling the ``#close`` method: + +.. code-block:: ruby + + stream = collection.watch + collection.insert_one(a: 1) + doc = stream.to_enum.next + process(doc) + stream.close From 08e4ab3fce45e87aa28f14ae89fe2a1b4b179d27 Mon Sep 17 00:00:00 2001 From: Emily Date: Tue, 16 Jan 2018 14:45:26 +0100 Subject: [PATCH 3/4] RUBY-1271 Add retry_writes documentation for a client --- docs/tutorials/ruby-driver-create-client.txt | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/tutorials/ruby-driver-create-client.txt b/docs/tutorials/ruby-driver-create-client.txt index f57c2afe2d..fc962070ad 100644 --- a/docs/tutorials/ruby-driver-create-client.txt +++ b/docs/tutorials/ruby-driver-create-client.txt @@ -407,6 +407,12 @@ Ruby Options - ``Integer`` - none + * - ``:retry_writes`` + - If a single-statement write operation fails from a network error, the driver automatically retries it once + when connected to server versions 3.6+. + - ``Boolean`` + - false + Details on Timeout Options -------------------------- @@ -530,3 +536,20 @@ When ``#close`` is called on a client by any thread, all connections are closed: .. code-block:: ruby client.close + +Details on Retryable Writes +--------------------------- + +If the ``retry_writes`` option is set to true, the driver will retry single-statement write operations that fail from +a network error. The driver automatically retries the operation once. + +Most of the write methods you use day-to-day on a collection will be retryable in 3.6. They are: + +- ``collection#insert_one`` +- ``collection#update_one`` +- ``collection#delete_one`` +- ``collection#replace_one`` +- ``collection#find_one_and_update`` +- ``collection#find_one_and_replace`` +- ``collection#find_one_and_delete`` +- ``collection#bulk_write`` # as long as there is no ``update_many`` or ``delete_many`` in the list of operations From 0a579e3ffdeca821654a1c45bc893430440ec3f4 Mon Sep 17 00:00:00 2001 From: Emily Date: Wed, 17 Jan 2018 11:16:49 +0100 Subject: [PATCH 4/4] RUBY-1271 Minor fixes to tutorial --- docs/tutorials/ruby-driver-create-client.txt | 2 +- docs/tutorials/ruby-driver-sessions.txt | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/tutorials/ruby-driver-create-client.txt b/docs/tutorials/ruby-driver-create-client.txt index fc962070ad..73a72b72c7 100644 --- a/docs/tutorials/ruby-driver-create-client.txt +++ b/docs/tutorials/ruby-driver-create-client.txt @@ -552,4 +552,4 @@ Most of the write methods you use day-to-day on a collection will be retryable i - ``collection#find_one_and_update`` - ``collection#find_one_and_replace`` - ``collection#find_one_and_delete`` -- ``collection#bulk_write`` # as long as there is no ``update_many`` or ``delete_many`` in the list of operations +- ``collection#bulk_write`` # for all single statement ops (i.e. not for ``update_many`` or ``delete_many``) diff --git a/docs/tutorials/ruby-driver-sessions.txt b/docs/tutorials/ruby-driver-sessions.txt index 525e2c051b..e40bb58967 100644 --- a/docs/tutorials/ruby-driver-sessions.txt +++ b/docs/tutorials/ruby-driver-sessions.txt @@ -41,10 +41,9 @@ be aware that if the application calls ``#start_session`` on a client and waits the session, it risks getting errors due to the session going stale before it is used. - Using a session --------------- -A session can be passed to most operations performed via the driver so that the operation can be executed in the +A session object can be passed to most driver methods so that the operation can be executed in the context of that session. Please see the API docs for which methods support a session argument. Create a session and execute an insert, then a find using that session: @@ -53,9 +52,9 @@ Create a session and execute an insert, then a find using that session: session = client.start_session client[:artists].insert_one({ :name => 'FKA Twigs' }, session: session) - client[:artists].find({ :name => 'FKA Twigs' }, session: session).first + client[:artists].find({ :name => 'FKA Twigs' }, limit: 1, session: session).first -If you like to call methods on a ``Mongo::Collection::View`` that use a particular session, you can create the +If you like to call methods on a ``Mongo::Collection::View`` in the context of a particular session, you can create the ``Mongo::Collection::View`` with the session and then call methods on it: .. code-block:: ruby @@ -89,14 +88,14 @@ To create a causally consistent session, set the ``causal_consistency`` option t collection.update_one({ '_id' => 1 }, { '$set' => { 'x' => 0 } }, session: session) # Read your write, even when reading from a secondary! - collection.find({ '_id' => 1 }, limit: 1, session: session).first + collection.find({ '_id' => 1 }, session: session).first # This query returns data at least as new as the previous query, # even if it chooses a different secondary. - collection.find({ '_id' => 2 }, limit: 1, session: session).first + collection.find({ '_id' => 2 }, session: session).first Since unacknowledged writes don't receive a response from the server (or don't wait for a response), the driver -has no way of keeping track of where the unacknowledged write is in logical time. Therefore, be aware that causally +has no way of keeping track of where the unacknowledged write is in logical time. Therefore, causally consistent reads are not causally consistent with unacknowledged writes. Note that if you set the causal_consistency option to nil as in ``(causal_consistency: nil)``, it will be interpreted