diff --git a/core/core.api.php b/core/core.api.php
index de34440..7d18a52 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -49,6 +49,7 @@
  * - @link user_api User accounts, permissions, and roles @endlink
  * - @link theme_render Render API @endlink
  * - @link themeable Theme system @endlink
+ * - @link update_api Update API @endlink
  * - @link migration Migration @endlink
  *
  * @section additional Additional topics
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index 63f81b2..7151df8 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -8,6 +8,51 @@
 use Drupal\Core\Utility\UpdateException;
 use Drupal\Core\Url;
 
+
+/**
+ * @defgroup update_api Update API
+ * @{
+ * Updating minor versions of modules
+ *
+ * When you update code in a module, you may need to update stored data so that
+ * the stored data is compatible with the new code. If this update is between
+ * two minor versions of your module within the same major version of Drupal,
+ * you can use the Update API to update the data. This API is described in brief
+ * here; for more details, see https://www.drupal.org/node/2535316. If you are
+ * updating your module for a major version of Drupal (for instance, Drupal 7 to
+ * Drupal 8), updates will not run and you will need to use the
+ * @link migrate Migrate API @endlink instead.
+ *
+ * @section sec_when When to write update code
+ * You need to provide code that performs an update to stored data whenever your
+ * module makes a change to its data model. A data model change is any change
+ * that makes stored data on an existing site incompatible with that site's
+ * updated codebase. Examples:
+ * - Configuration schema changes: adding/removing/renaming a config key,
+ *   changing the expected data type or value structure, changing dependencies,
+ *   etc.
+ * - Database schema changes: adding, changing, or removing a database table or
+ *   field; moving stored data to different fields or tables; changing the
+ *   format of stored data.
+ * - Content entity or field changes: these updates are normally handled
+ *   automatically by the entity system, but should at least be tested.
+ *
+ * @section sec_how How to write update code
+ * Update code for a module is put into an implementation of hook_update_N(),
+ * which goes into file mymodule.install (if your module's machine name is
+ * mymodule). See the documentation of hook_update_N() and
+ * https://www.drupal.org/node/2535316 for details and examples.
+ *
+ * @section sec_test Testing update code
+ * Update code should be tested both manually and by writing an automated test.
+ * Automated tests for update code extend
+ * \Drupal\system\Tests\Update\UpdatePathTestBase -- see that class for details,
+ * and find classes that extend it for examples.
+ *
+ * @see migration
+ * @}
+ */
+
 /**
  * @addtogroup hooks
  * @{
@@ -419,75 +464,71 @@ function hook_install_tasks_alter(&$tasks, $install_state) {
 }
 
 /**
- * Perform a single update.
+ * Perform a single update between minor versions.
  *
- * For each change that requires one or more actions to be performed when
- * updating a site, add a new hook_update_N(), which will be called by
- * update.php. The documentation block preceding this function is stripped of
- * newlines and used as the description for the update on the pending updates
- * task list. Schema updates should adhere to the
- * @link https://www.drupal.org/node/150215 Schema API. @endlink
+ * hook_update_N() can only be used to update between minor versions of a
+ * module. To upgrade between major versions of Drupal (for example, between
+ * Drupal 7 and 8), use the @link migrate Migrate API @endlink instead.
  *
+ * @section sec_naming Naming and documenting your function
+ * For each change in a module that requires one or more actions to be performed
+ * when updating a site, add a new implementation of hook_update_N() to your
+ * mymodule.install file (assuming mymodule is the machine name of your module).
  * Implementations of hook_update_N() are named (module name)_update_(number).
  * The numbers are composed of three parts:
  * - 1 digit for Drupal core compatibility.
  * - 1 digit for your module's major release version (e.g., is this the 8.x-1.*
  *   (1) or 8.x-2.* (2) series of your module).
  * - 2 digits for sequential counting, starting with 01.
- *
  * Examples:
  * - mymodule_update_8100(): This is the first update to get the database ready
  *   to run mymodule 8.x-1.*.
  * - mymodule_update_8200(): This is the first update to get the database ready
  *   to run mymodule 8.x-2.*.
  *
- * As of Drupal 8.0, the database upgrade system no longer supports updating a
- * database from an earlier major version of Drupal: update.php can be used to
- * upgrade from 7.x-1.x to 7.x-2.x, or 8.x-1.x to 8.x-2.x, but not from 7.x to
- * 8.x. Therefore, only update hooks numbered 8001 or later will run for
- * Drupal 8. 8000 is reserved for the minimum core schema version and defining
- * mymodule_update_8000() will result in an exception. Use the
- * @link https://www.drupal.org/node/2127611 Migration API @endlink instead to
- * migrate data from an earlier major version of Drupal.
- *
- * For further information about releases and release numbers see:
- * @link https://www.drupal.org/node/711070 Maintaining a drupal.org project
- * with Git @endlink
- *
- * Never renumber update functions.
- *
- * Implementations of this hook should be placed in a mymodule.install file in
- * the same directory as mymodule.module. Drupal core's updates are implemented
- * using the system module as a name and stored in database/updates.inc.
- *
- * Not all module functions are available from within a hook_update_N() function.
- * In order to call a function from your mymodule.module or an include file,
- * you need to explicitly load that file first.
- *
- * During database updates the schema of any module could be out of date. For
- * this reason, caution is needed when using any API function within an update
- * function - particularly CRUD functions, functions that depend on the schema
- * (for example by using drupal_write_record()), and any functions that invoke
- * hooks.
- *
- * The $sandbox parameter should be used when a multipass update is needed, in
- * circumstances where running the whole update at once could cause PHP to
- * timeout. Each pass is run in a way that avoids PHP timeouts, provided each
- * pass remains under the timeout limit. To signify that an update requires
- * at least one more pass, set $sandbox['#finished'] to a number less than 1
- * (you need to do this each pass). The value of $sandbox['#finished'] will be
- * unset between passes but all other data in $sandbox will be preserved. The
- * system will stop iterating this update when $sandbox['#finished'] is left
- * unset or set to a number higher than 1. It is recommended that
- * $sandbox['#finished'] is initially set to 0, and then updated each pass to a
- * number between 0 and 1 that represents the overall % completed for this
- * update, finishing with 1.
+ * Never renumber update functions. The numeric part of the hook implementation
+ * function is stored in the database to keep track of which updates have run,
+ * so it is important to maintain this information consistently.
+ *
+ * The documentation block preceding this function is stripped of newlines and
+ * used as the description for the update on the pending updates task list,
+ * which users will see when they run the update.php script.
+ *
+ * @section sec_notes Notes about the function body
+ * Writing hook_update_N() functions can be a bit tricky. Here are a few
+ * notes.
+ *
+ * First, not all module functions are available from within a hook_update_N()
+ * function.  In order to call a function from your mymodule.module or an
+ * include file, you need to explicitly load that file first.
+ *
+ * Second, during database updates the database schema could be out of date. For
+ * this reason, caution is needed when using any API function or class within an
+ * update function - particularly CRUD functions, functions that depend on the
+ * database schema, and any functions that invoke hooks. Also, when you are
+ * updating a database table or field, be sure to copy/paste your new
+ * table/field schema into your hook_update_N() implementation rather than
+ * calling your hook_schema() implementation to get it, because hook_schema()
+ * may get updated later and your hook_update_N() function would at that point
+ * not work as you expect. You cannot count on when update functions will be
+ * run.
+ *
+ * @section sec_bulk Batch updates
+ * If running your update all at once could possibly cause PHP to time out, use
+ * the $sandbox parameter to indicate that the Batch API should be used for your
+ * update. In this case, your update function acts as an implementation of
+ * callback_batch_operation(), and $sandbox acts as the batch context
+ * parameter. In your function, read the state information from the previous
+ * run from $sandbox (or initialize), run a chunk of updates, save the state in
+ * $sandbox, and set $sandbox['#finished'] to a value between 0 and 1 to
+ * indicate the percent completed, or 1 if it is finished (you need to do this
+ * explicitly in each pass).
  *
  * See the @link batch Batch operations topic @endlink for more information on
  * how to use the Batch API.
  *
  * @param array $sandbox
- *   Stores information for multipass updates. See above for more information.
+ *   Stores information for batch updates. See above for more information.
  *
  * @throws \Drupal\Core\Utility\UpdateException|PDOException
  *   In case of error, update hooks should throw an instance of
@@ -500,40 +541,58 @@ function hook_install_tasks_alter(&$tasks, $install_state) {
  *   displayed to the user after the update has completed. If no message is
  *   returned, no message will be presented to the user.
  *
+ * @ingroup update_api
+ *
  * @see batch
  * @see schemaapi
  * @see hook_update_last_removed()
  * @see update_get_update_list()
  */
 function hook_update_N(&$sandbox) {
-  // For non-multipass updates, the signature can simply be;
+  // For non-batch updates, the signature can simply be:
   // function hook_update_N() {
 
-  // For most updates, the following is sufficient.
-  db_add_field('mytable1', 'newcol', array('type' => 'int', 'not null' => TRUE, 'description' => 'My new integer column.'));
+  // If you are performing database updates, as in the
+  // examples below, you may need to put use statements at the top of
+  // your mymodule.install file for the referenced classes, such as:
+  // use Drupal\Core\Database\Database;
+
+  // Example function body for adding a field to a database table, which
+  // does not require a batch operation:
+  $spec = array(
+    'type' => 'varchar',
+    'description' => "New Col",
+    'length' => 20,
+    'not null' => FALSE,
+  );
+  $schema = Database::getConnection()->schema();
+  $schema->addField('mytable1', 'newcol', $spec);
 
-  // However, for more complex operations that may take a long time,
-  // you may hook into Batch API as in the following example.
+  // Example of what to do if there is an error during your update.
+  if ($some_error_condition_met) {
+    throw new UpdateException('Something went wrong; here is what you should do.');
+  }
 
-  // Update 3 users at a time to have an exclamation point after their names.
-  // (They're really happy that we can do batch API in this hook!)
+  // Example function body for a batch update. In this example, all user
+  // names except user 1 are updated so that they end in !, for illustration
+  // purposes.
   if (!isset($sandbox['progress'])) {
+    // This must be the first run. Initialize the sandbox.
     $sandbox['progress'] = 0;
     $sandbox['current_uid'] = 0;
-    // We'll -1 to disregard the uid 0...
     $sandbox['max'] = db_query('SELECT COUNT(DISTINCT uid) FROM {users}')->fetchField() - 1;
   }
 
-  $users = db_select('users', 'u')
+  // Update in chunks of 20.
+  $users = Database::getConnection()->select('users', 'u')
     ->fields('u', array('uid', 'name'))
     ->condition('uid', $sandbox['current_uid'], '>')
-    ->range(0, 3)
+    ->range(0, 20)
     ->orderBy('uid', 'ASC')
     ->execute();
-
   foreach ($users as $user) {
     $user->setUsername($user->getUsername() . '!');
-    db_update('users')
+    Database::getConnection()->update('users')
       ->fields(array('name' => $user->getUsername()))
       ->condition('uid', $user->id())
       ->execute();
@@ -544,14 +603,9 @@ function hook_update_N(&$sandbox) {
 
   $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
 
-  if ($some_error_condition_met) {
-    // In case of an error, simply throw an exception with an error message.
-    throw new UpdateException('Something went wrong; here is what you should do.');
-  }
-
   // To display a message to the user when the update is completed, return it.
   // If you do not want to display a completion message, simply return nothing.
-  return t('The update did what it was supposed to do.');
+  return t('All user names were updated');
 }
 
 /**
@@ -576,6 +630,8 @@ function hook_update_N(&$sandbox) {
  *   you should always list the highest numbered one here (since updates within
  *   a given module always run in numerical order).
  *
+ * @ingroup update_api
+ *
  * @see update_resolve_dependencies()
  * @see hook_update_N()
  */
@@ -613,6 +669,8 @@ function hook_update_dependencies() {
  *   An integer, corresponding to hook_update_N() which has been removed from
  *   mymodule.install.
  *
+ * @ingroup update_api
+ *
  * @see hook_update_N()
  */
 function hook_update_last_removed() {
@@ -642,6 +700,8 @@ function hook_update_last_removed() {
  *     doesn't matter, but if you need to override an existing Updater, make
  *     sure your Updater has a lighter weight so that it comes first.
  *
+ * @ingroup update_api
+ *
  * @see drupal_get_updaters()
  * @see hook_updater_info_alter()
  */
@@ -671,6 +731,8 @@ function hook_updater_info() {
  *   Associative array of updaters as defined through hook_updater_info().
  *   Alter this array directly.
  *
+ * @ingroup update_api
+ *
  * @see drupal_get_updaters()
  * @see hook_updater_info()
  */
diff --git a/core/modules/migrate/migrate.api.php b/core/modules/migrate/migrate.api.php
index 005ccc8..df5c66d 100644
--- a/core/modules/migrate/migrate.api.php
+++ b/core/modules/migrate/migrate.api.php
@@ -98,6 +98,8 @@
  * - d6_custom_block
  * - d6_block
  * @endcode
+ *
+ * @see update_api
  * @}
  */
 
diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
index fb365f0..1335ef3 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
@@ -15,7 +15,24 @@
 use Symfony\Component\HttpFoundation\Request;
 
 /**
- * Provides a base class that loads a database as a starting point.
+ * Provides a base class for writing an update test.
+ *
+ * To write an update test:
+ * - Write the hook_update_N() implementations that you are testing.
+ * - Create one or more database dump files, which will set the database
+ *   to the "before updates" state. Normally, these will add some
+ *   configuration data to the database, set up some tables/fields, etc.
+ * - Create a class that extends this class.
+ * - In your setUp() method, point the $this->databaseDumpFiles variable
+ *   to the database dump files, and then call parent::setUp() to run the
+ *   base setUp() method in this class.
+ * - In your test method, call $this->runUpdates() to run the necessary
+ *   updates, and then use test assertions to verify that the result is what
+ *   you expect.
+ *
+ * @ingroup update_api
+ *
+ * @see hook_update_N()
  */
 abstract class UpdatePathTestBase extends WebTestBase {
 
@@ -27,6 +44,10 @@
  /**
    * The file path(s) to the dumped database(s) to load into the child site.
    *
+   * The file system/tests/fixtures/update/drupal-8.bare.standard.php.gz is
+   * normally included first -- this sets up the base database from a bare
+   * standard Drupal installation.
+   *
    * @var array
    */
   protected $databaseDumpFiles = [];
@@ -39,14 +60,14 @@
   protected $installProfile = 'standard';
 
   /**
-   * Flag that indicates whether the child site has been upgraded.
+   * Flag that indicates whether the child site has been updated.
    *
    * @var bool
    */
   protected $upgradedSite = FALSE;
 
   /**
-   * Array of errors triggered during the upgrade process.
+   * Array of errors triggered during the update process.
    *
    * @var array
    */
@@ -96,7 +117,7 @@ function __construct($test_id = NULL) {
   }
 
   /**
-   * Overrides WebTestBase::setUp() for upgrade testing.
+   * Overrides WebTestBase::setUp() for update testing.
    *
    * The main difference in this method is that rather than performing the
    * installation via the installer, a database is loaded. Additional work is
@@ -182,7 +203,7 @@ protected function prepareSettings() {
    */
   protected function runUpdates() {
     if (!$this->zlibInstalled) {
-      $this->fail('Missing zlib requirement for upgrade tests.');
+      $this->fail('Missing zlib requirement for update tests.');
       return FALSE;
     }
     $this->drupalLogin($this->rootUser);
