|
| 1 | +===================== |
| 2 | +Retryable Write Tests |
| 3 | +===================== |
| 4 | + |
| 5 | +.. contents:: |
| 6 | + |
| 7 | +---- |
| 8 | + |
| 9 | +Introduction |
| 10 | +============ |
| 11 | + |
| 12 | +The YAML and JSON files in this directory tree are platform-independent tests |
| 13 | +that drivers can use to prove their conformance to the Retryable Writes spec. |
| 14 | + |
| 15 | +Several prose tests, which are not easily expressed in YAML, are also presented |
| 16 | +in this file. Those tests will need to be manually implemented by each driver. |
| 17 | + |
| 18 | +Tests will require a MongoClient created with options defined in the tests. |
| 19 | +Integration tests will require a running MongoDB cluster with server versions |
| 20 | +3.6.0 or later. The ``{setFeatureCompatibilityVersion: 3.6}`` admin command |
| 21 | +will also need to have been executed to enable support for retryable writes on |
| 22 | +the cluster. |
| 23 | + |
| 24 | +Server Fail Point |
| 25 | +================= |
| 26 | + |
| 27 | +The tests depend on a server fail point, ``onPrimaryTransactionalWrite``, which |
| 28 | +allows us to force a network error before the server would return a write result |
| 29 | +to the client. The fail point also allows control whether the server will |
| 30 | +successfully commit the write via its ``failBeforeCommitExceptionCode`` option. |
| 31 | +Keep in mind that the fail point only triggers for transaction writes (i.e. write |
| 32 | +commands including ``txnNumber`` and ``lsid`` fields). See `SERVER-29606`_ for |
| 33 | +more information. |
| 34 | + |
| 35 | +.. _SERVER-29606: https://jira.mongodb.org/browse/SERVER-29606 |
| 36 | + |
| 37 | +The fail point may be configured like so:: |
| 38 | + |
| 39 | + db.runCommand({ |
| 40 | + configureFailPoint: "onPrimaryTransactionalWrite", |
| 41 | + mode: <string|document>, |
| 42 | + data: <document> |
| 43 | + }); |
| 44 | + |
| 45 | +``mode`` is a generic fail point option and may be assigned a string or document |
| 46 | +value. The string values ``"alwaysOn"`` and ``"off"`` may be used to enable or |
| 47 | +disable the fail point, respectively. A document may be used to specify either |
| 48 | +``times`` or ``skip``, which are mutually exclusive: |
| 49 | + |
| 50 | +- ``{ times: <integer> }`` may be used to limit the number of times the fail |
| 51 | + point may trigger before transitioning to ``"off"``. |
| 52 | +- ``{ skip: <integer> }`` may be used to defer the first trigger of a fail |
| 53 | + point, after which it will transition to ``"alwaysOn"``. |
| 54 | + |
| 55 | +The ``data`` option is a document that may be used to specify options that |
| 56 | +control the fail point's behavior. As noted in `SERVER-29606`_, |
| 57 | +``onPrimaryTransactionalWrite`` supports the following ``data`` options, which |
| 58 | +may be combined if desired: |
| 59 | + |
| 60 | +- ``closeConnection``: Boolean option, which defaults to ``true``. If ``true``, |
| 61 | + the connection on which the write is executed will be closed before a result |
| 62 | + can be returned. |
| 63 | +- ``failBeforeCommitExceptionCode``: Integer option, which is unset by default. |
| 64 | + If set, the specified exception code will be thrown and the write will not be |
| 65 | + committed. If unset, the write will be allowed to commit. |
| 66 | + |
| 67 | +Disabling Fail Point after Test Execution |
| 68 | +----------------------------------------- |
| 69 | + |
| 70 | +After each test that configures a fail point, drivers should disable the |
| 71 | +``onPrimaryTransactionalWrite`` fail point to avoid spurious failures in |
| 72 | +subsequent tests. The fail point may be disabled like so:: |
| 73 | + |
| 74 | + db.runCommand({ |
| 75 | + configureFailPoint: "onPrimaryTransactionalWrite", |
| 76 | + mode: "off" |
| 77 | + }); |
| 78 | + |
| 79 | +Network Error Tests |
| 80 | +=================== |
| 81 | + |
| 82 | +Network error tests are expressed in YAML and should be run against a replica |
| 83 | +set. These tests cannot be run against a shard cluster because mongos does not |
| 84 | +support the necessary fail point. |
| 85 | + |
| 86 | +The tests exercise the following scenarios: |
| 87 | + |
| 88 | +- Single-statement write operations |
| 89 | + |
| 90 | + - Each test expecting a write result will encounter at-most one network error |
| 91 | + for the write command. Retry attempts should return without error and allow |
| 92 | + operation to succeed. Observation of the collection state will assert that |
| 93 | + the write occurred at-most once. |
| 94 | + |
| 95 | + - Each test expecting an error will encounter successive network errors for |
| 96 | + the write command. Observation of the collection state will assert that the |
| 97 | + write was never committed on the server. |
| 98 | + |
| 99 | +- Multi-statement write operations |
| 100 | + |
| 101 | + - Each test expecting a write result will encounter at-most one network error |
| 102 | + for some write command(s) in the batch. Retry attempts should return without |
| 103 | + error and allow the batch to ultimately succeed. Observation of the |
| 104 | + collection state will assert that each write occurred at-most once. |
| 105 | + |
| 106 | + - Each test expecting an error will encounter successive network errors for |
| 107 | + some write command in the batch. The batch will ultimately fail with an |
| 108 | + error, but observation of the collection state will assert that the failing |
| 109 | + write was never committed on the server. We may observe that earlier writes |
| 110 | + in the batch occurred at-most once. |
| 111 | + |
| 112 | +We cannot test a scenario where the first and second attempts both encounter |
| 113 | +network errors but the write does actually commit during one of those attempts. |
| 114 | +This is because (1) the fail point only triggers when a write would be committed |
| 115 | +and (2) the skip and times options are mutually exclusive. That said, such a |
| 116 | +test would mainly assert the server's correctness for at-most once semantics and |
| 117 | +is not essential to assert driver correctness. |
| 118 | + |
| 119 | +Test Format |
| 120 | +----------- |
| 121 | + |
| 122 | +Each YAML file has the following keys: |
| 123 | + |
| 124 | +- ``data``: The data that should exist in the collection under test before each |
| 125 | + test run. |
| 126 | + |
| 127 | +- ``minServerVersion`` (optional): The minimum server version (inclusive) |
| 128 | + required to successfully run the test. If this field is not present, it should |
| 129 | + be assumed that there is no lower bound on the required server version. |
| 130 | + |
| 131 | +- ``maxServerVersion`` (optional): The maximum server version (exclusive) |
| 132 | + against which this test can run successfully. If this field is not present, |
| 133 | + it should be assumed that there is no upper bound on the required server |
| 134 | + version. |
| 135 | + |
| 136 | +- ``tests``: An array of tests that are to be run independently of each other. |
| 137 | + Each test will have some or all of the following fields: |
| 138 | + |
| 139 | + - ``description``: The name of the test. |
| 140 | + |
| 141 | + - ``clientOptions``: Parameters to pass to MongoClient(). |
| 142 | + |
| 143 | + - ``failPoint``: The ``configureFailPoint`` command document to run to |
| 144 | + configure a fail point on the primary server. Drivers must ensure that |
| 145 | + ``configureFailPoint`` is the first field in the command. |
| 146 | + |
| 147 | + - ``operation``: Document describing the operation to be executed. The |
| 148 | + operation should be executed through a collection object derived from a |
| 149 | + client that has been created with ``clientOptions``. The operation will have |
| 150 | + some or all of the following fields: |
| 151 | + |
| 152 | + - ``name``: The name of the operation as defined in the CRUD specification. |
| 153 | + |
| 154 | + - ``arguments``: The names and values of arguments from the CRUD |
| 155 | + specification. |
| 156 | + |
| 157 | + - ``outcome``: Document describing the return value and/or expected state of |
| 158 | + the collection after the operation is executed. This will have some or all |
| 159 | + of the following fields: |
| 160 | + |
| 161 | + - ``error``: If ``true``, the test should expect an error or exception. Note |
| 162 | + that some drivers may report server-side errors as a write error within a |
| 163 | + write result object. |
| 164 | + |
| 165 | + - ``result``: The return value from the operation. This will correspond to |
| 166 | + an operation's result object as defined in the CRUD specification. This |
| 167 | + field may be omitted if ``error`` is ``true``. If this field is present |
| 168 | + and ``error`` is ``true`` (generally for multi-statement tests), the |
| 169 | + result reports information about operations that succeeded before an |
| 170 | + unrecoverable failure. In that case, drivers may choose to check the |
| 171 | + result object if their BulkWriteException (or equivalent) provides access |
| 172 | + to a write result object. |
| 173 | + |
| 174 | + - ``collection``: |
| 175 | + |
| 176 | + - ``name`` (optional): The name of the collection to verify. If this isn't |
| 177 | + present then use the collection under test. |
| 178 | + |
| 179 | + - ``data``: The data that should exist in the collection after the |
| 180 | + operation has been run. |
| 181 | + |
| 182 | +Split Batch Tests |
| 183 | +================= |
| 184 | + |
| 185 | +The YAML tests specify bulk write operations that are split by command type |
| 186 | +(e.g. sequence of insert, update, and delete commands). Multi-statement write |
| 187 | +operations may also be split due to ``maxWriteBatchSize``, |
| 188 | +``maxBsonObjectSize``, or ``maxMessageSizeBytes``. |
| 189 | + |
| 190 | +For instance, an insertMany operation with five 10 MB documents executed using |
| 191 | +OP_MSG payload type 0 (i.e. entire command in one document) would be split into |
| 192 | +five insert commands in order to respect the 16 MB ``maxBsonObjectSize`` limit. |
| 193 | +The same insertMany operation executed using OP_MSG payload type 1 (i.e. command |
| 194 | +arguments pulled out into a separate payload vector) would be split into two |
| 195 | +insert commands in order to respect the 48 MB ``maxMessageSizeBytes`` limit. |
| 196 | + |
| 197 | +Noting when a driver might split operations, the ``onPrimaryTransactionalWrite`` |
| 198 | +fail point's ``skip`` option may be used to control when the fail point first |
| 199 | +triggers. Once triggered, the fail point will transition to the ``alwaysOn`` |
| 200 | +state until disabled. Driver authors should also note that the server attempts |
| 201 | +to process all documents in a single insert command within a single commit (i.e. |
| 202 | +one insert command with five documents may only trigger the fail point once). |
| 203 | +This behavior is unique to insert commands (each statement in an update and |
| 204 | +delete command is processed independently). |
| 205 | + |
| 206 | +If testing an insert that is split into two commands, a ``skip`` of one will |
| 207 | +allow the fail point to trigger on the second insert command (because all |
| 208 | +documents in the first command will be processed in the same commit). When |
| 209 | +testing an update or delete that is split into two commands, the ``skip`` should |
| 210 | +be set to the number of statements in the first command to allow the fail point |
| 211 | +to trigger on the second command. |
| 212 | + |
| 213 | +Replica Set Failover Test |
| 214 | +========================= |
| 215 | + |
| 216 | +In addition to network errors, writes should also be retried in the event of a |
| 217 | +primary failover, which results in a "not master" command error (or similar). |
| 218 | +The ``stepdownHangBeforePerformingPostMemberStateUpdateActions`` fail point |
| 219 | +implemented in `d4eb562`_ for `SERVER-31355`_ may be used for this test, as it |
| 220 | +allows a primary to keep its client connections open after a step down. This |
| 221 | +fail point operates by hanging the step down procedure (i.e. ``replSetStepDown`` |
| 222 | +command) until the fail point is later deactivated. |
| 223 | + |
| 224 | +.. _d4eb562: https://github.com/mongodb/mongo/commit/d4eb562ac63717904f24de4a22e395070687bc62 |
| 225 | +.. _SERVER-31355: https://jira.mongodb.org/browse/SERVER-31355 |
| 226 | + |
| 227 | +The following test requires three MongoClient instances and will generally |
| 228 | +require two execution contexts (async drivers may get by with a single thread). |
| 229 | + |
| 230 | +- The client under test will connect to the replica set and be used to execute |
| 231 | + write operations. |
| 232 | +- The fail point client will connect directly to the initial primary and be used |
| 233 | + to toggle the fail point. |
| 234 | +- The step down client will connect to the replica set and be used to step down |
| 235 | + the primary. This client will generally require its own execution context, |
| 236 | + since the step down will hang. |
| 237 | + |
| 238 | +In order to guarantee that the client under test does not detect the stepped |
| 239 | +down primary's state change via SDAM, it must be configured with a large |
| 240 | +`heartbeatFrequencyMS`_ value (e.g. 60 seconds). Single-threaded drivers may |
| 241 | +also need to set `serverSelectionTryOnce`_ to ``false`` to ensure that server |
| 242 | +selection for the retry attempt waits until a new primary is elected. |
| 243 | + |
| 244 | +.. _heartbeatFrequencyMS: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#heartbeatfrequencyms |
| 245 | +.. _serverSelectionTryOnce: https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#serverselectiontryonce |
| 246 | + |
| 247 | +The test proceeds as follows: |
| 248 | + |
| 249 | +- Using the client under test, insert a document and observe a successful write |
| 250 | + result. This will ensure that initial discovery takes place. |
| 251 | +- Using the fail point client, activate the fail point by setting ``mode`` |
| 252 | + to ``"alwaysOn"``. |
| 253 | +- Using the step down client, step down the primary by executing the command |
| 254 | + ``{ replSetStepDown: 60, force: true}``. This operation will hang so long as |
| 255 | + the fail point is activated. When the fail point is later deactivated, the |
| 256 | + step down will complete and the primary's client connections will be dropped. |
| 257 | + At that point, any ensuing network error should be ignored. |
| 258 | +- Using the client under test, insert a document and observe a successful write |
| 259 | + result. The test MUST assert that the insert command fails once against the |
| 260 | + stepped down node and is successfully retried on the newly elected primary |
| 261 | + (after SDAM discovers the topology change). The test MAY use APM or another |
| 262 | + means to observe both attempts. |
| 263 | +- Using the fail point client, deactivate the fail point by setting ``mode`` |
| 264 | + to ``"off"``. |
| 265 | + |
| 266 | +Command Construction Tests |
| 267 | +========================== |
| 268 | + |
| 269 | +Drivers should also assert that command documents are properly constructed with |
| 270 | +or without a transaction ID, depending on whether the write operation is |
| 271 | +supported. `Command Monitoring`_ may be used to check for the presence of a |
| 272 | +``txnNumber`` field in the command document. Note that command documents may |
| 273 | +always include an ``lsid`` field per the `Driver Session`_ specification. |
| 274 | + |
| 275 | +.. _Command Monitoring: ../../command-monitoring/command-monitoring.rst |
| 276 | +.. _Driver Session: ../../sessions/driver-sessions.rst |
| 277 | + |
| 278 | +These tests may be run against both a replica set and shard cluster. |
| 279 | + |
| 280 | +Drivers should test that transaction IDs are never included in commands for |
| 281 | +unsupported write operations: |
| 282 | + |
| 283 | +* Write commands with unacknowledged write concerns (e.g. ``{w: 0}``) |
| 284 | + |
| 285 | +* Unsupported single-statement write operations |
| 286 | + |
| 287 | + - ``updateMany()`` |
| 288 | + - ``deleteMany()`` |
| 289 | + |
| 290 | +* Unsupported multi-statement write operations |
| 291 | + |
| 292 | + - ``bulkWrite()`` that includes ``UpdateMany`` or ``DeleteMany`` |
| 293 | + |
| 294 | +* Unsupported write commands |
| 295 | + |
| 296 | + - ``aggregate`` with ``$out`` pipeline operator |
| 297 | + |
| 298 | +Drivers should test that transactions IDs are always included in commands for |
| 299 | +supported write operations: |
| 300 | + |
| 301 | +* Supported single-statement write operations |
| 302 | + |
| 303 | + - ``insertOne()`` |
| 304 | + - ``updateOne()`` |
| 305 | + - ``replaceOne()`` |
| 306 | + - ``deleteOne()`` |
| 307 | + - ``findOneAndDelete()`` |
| 308 | + - ``findOneAndReplace()`` |
| 309 | + - ``findOneAndUpdate()`` |
| 310 | + |
| 311 | +* Supported multi-statement write operations |
| 312 | + |
| 313 | + - ``insertMany()`` with ``ordered=true`` |
| 314 | + - ``insertMany()`` with ``ordered=false`` |
| 315 | + - ``bulkWrite()`` with ``ordered=true`` (no ``UpdateMany`` or ``DeleteMany``) |
| 316 | + - ``bulkWrite()`` with ``ordered=false`` (no ``UpdateMany`` or ``DeleteMany``) |
0 commit comments