diff --git a/core/core.api.php b/core/core.api.php
index 7c4ebf40b1..c03afbea43 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -606,6 +606,17 @@
  *  $settings['cache']['default'] = 'cache.custom';
  * @endcode
  *
+ * For cache bins that are stored in the database, the number of rows is limited
+ * to 10,000 by default. This can be changed for all database cache bins:
+ * @code
+ *  $settings['database_cache_max_rows']['default'] = 50000;
+ * @endcode
+ *
+ * Or per bin (in this case removing the maximum):
+ * @code
+ *  $settings['database_cache_max_rows']['bins']['dynamic_page_cache'] = -1;
+ * @endcode
+ *
  * Finally, you can chain multiple cache backends together, see
  * \Drupal\Core\Cache\ChainedFastBackend and \Drupal\Core\Cache\BackendChain.
  *
diff --git a/core/core.services.yml b/core/core.services.yml
index 76088786cd..598754e10b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -192,7 +192,7 @@ services:
       - [setContainer, ['@service_container']]
   cache.backend.database:
     class: Drupal\Core\Cache\DatabaseBackendFactory
-    arguments: ['@database', '@cache_tags.invalidator.checksum']
+    arguments: ['@database', '@cache_tags.invalidator.checksum', '@settings']
   cache.backend.apcu:
     class: Drupal\Core\Cache\ApcuBackendFactory
     arguments: ['@app.root', '@site.path', '@cache_tags.invalidator.checksum']
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index d53c51c2fc..c579c06744 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -17,6 +17,20 @@
 class DatabaseBackend implements CacheBackendInterface {
 
   /**
+   * -1 means "no maximum".
+   */
+  const MAXIMUM_NONE = -1;
+
+  /**
+   * The maximum number of rows that this cache bin table is allowed to store.
+   *
+   * * @see ::MAXIMUM_NONE
+   *
+   * @var int
+   */
+  protected $maxRows;
+
+  /**
    * @var string
    */
   protected $bin;
@@ -45,14 +59,17 @@ class DatabaseBackend implements CacheBackendInterface {
    *   The cache tags checksum provider.
    * @param string $bin
    *   The cache bin for which the object is created.
+   * @param int $max_rows
+   *   (optional) The maximum number of rows that are allowed in this cache bin table.
    */
-  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin) {
+  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin, $max_rows = 50000) {
     // All cache tables should be prefixed with 'cache_'.
     $bin = 'cache_' . $bin;
 
     $this->bin = $bin;
     $this->connection = $connection;
     $this->checksumProvider = $checksum_provider;
+    $this->maxRows = $max_rows;
   }
 
   /**
@@ -326,6 +343,22 @@ public function invalidateAll() {
    */
   public function garbageCollection() {
     try {
+      // Bounded size cache bin, using FIFO.
+      if ($this->maxRows !== static::MAXIMUM_NONE) {
+        $first_invalid_cid = $this->connection->select($this->bin)
+          ->fields($this->bin, ['cid'])
+          ->orderBy("{$this->bin}.cid", 'ASC')
+          ->range($this->maxRows, $this->maxRows + 1)
+          ->execute()
+          ->fetchField();
+
+        if ($first_invalid_cid) {
+          $this->connection->delete($this->bin)
+            ->condition("{$this->bin}.cid", $first_invalid_cid, '<')
+            ->execute();
+        }
+      }
+
       $this->connection->delete($this->bin)
         ->condition('expire', Cache::PERMANENT, '<>')
         ->condition('expire', REQUEST_TIME, '<')
@@ -478,4 +511,13 @@ public function schemaDefinition() {
     return $schema;
   }
 
+  /**
+   * The maximum number of rows that this cache bin table is allowed to store.
+   *
+   * @return int
+   */
+  public function getMaxRows() {
+    return $this->maxRows;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
index 8aa018ec45..2cb34b3679 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
@@ -3,6 +3,7 @@
 namespace Drupal\Core\Cache;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Site\Settings;
 
 class DatabaseBackendFactory implements CacheFactoryInterface {
 
@@ -21,16 +22,26 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
   protected $checksumProvider;
 
   /**
+   * The settings array.
+   *
+   * @var \Drupal\Core\Site\Settings
+   */
+  protected $settings;
+
+  /**
    * Constructs the DatabaseBackendFactory object.
    *
    * @param \Drupal\Core\Database\Connection $connection
    *   Database connection
    * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
    *   The cache tags checksum provider.
+   * @param \Drupal\Core\Site\Settings $settings
+   *   (optional) The settings array.
    */
-  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider) {
+  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, Settings $settings = NULL) {
     $this->connection = $connection;
     $this->checksumProvider = $checksum_provider;
+    $this->settings = $settings ?: Settings::getInstance();
   }
 
   /**
@@ -43,7 +54,34 @@ public function __construct(Connection $connection, CacheTagsChecksumInterface $
    *   The cache backend object for the specified cache bin.
    */
   public function get($bin) {
-    return new DatabaseBackend($this->connection, $this->checksumProvider, $bin);
+    $max_rows = $this->getMaxRowsForBin($bin);
+    return new DatabaseBackend($this->connection, $this->checksumProvider, $bin, $max_rows);
+  }
+
+  /**
+   * Gets the max rows for the specified cache bin.
+   *
+   * @param string $bin
+   *   The cache bin for which the object is created.
+   *
+   * @return int
+   *   The maximum number of rows for the given bin. Defaults to 10000.
+   */
+  protected function getMaxRowsForBin($bin) {
+    $max_rows_settings = $this->settings->get('database_cache_max_rows');
+    // First, look for a cache bin specific setting.
+    if (isset($max_rows_settings['bins'][$bin])) {
+      $max_rows  = $max_rows_settings['bins'][$bin];
+    }
+    // Third, use configured default backend.
+    elseif (isset($max_rows_settings['default'])) {
+      $max_rows = $max_rows_settings['default'];
+    }
+    else {
+      // Fall back to the default max rows if nothing else is configured.
+      $max_rows = 10000;
+    }
+    return $max_rows;
   }
 
 }
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 02c97f5edb..8333f40df6 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -7,6 +7,7 @@
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\DatabaseBackend;
 use Drupal\Core\Config\BootstrapConfigStorageFactory;
 use Drupal\Core\Config\NullStorage;
 use Drupal\Core\Database\Database;
@@ -77,7 +78,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
       ],
       'cache.container' => [
         'class' => 'Drupal\Core\Cache\DatabaseBackend',
-        'arguments' => ['@database', '@cache_tags_provider.container', 'container'],
+        'arguments' => ['@database', '@cache_tags_provider.container', 'container', DatabaseBackend::MAXIMUM_NONE],
       ],
       'cache_tags_provider.container' => [
         'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum',
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
index 3021b041fe..a93b674d5c 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
@@ -20,7 +20,7 @@ class ChainedFastBackendTest extends GenericCacheBackendUnitTestBase {
    *   A new ChainedFastBackend object.
    */
   protected function createCacheBackend($bin) {
-    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin);
+    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin, 100);
     $fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
     $backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
     // Explicitly register the cache bin as it can not work through the
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
index de8bbda553..4f10c71e6c 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
@@ -12,6 +12,13 @@
 class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
 
   /**
+   * The max rows to use for test bins.
+   *
+   * @var int
+   */
+  protected static $maxRows = 100;
+
+  /**
    * Modules to enable.
    *
    * @var array
@@ -25,7 +32,7 @@ class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
    *   A new DatabaseBackend object.
    */
   protected function createCacheBackend($bin) {
-    return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin);
+    return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin, static::$maxRows);
   }
 
   /**
@@ -48,4 +55,47 @@ public function testSetGet() {
     $this->assertIdentical($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
   }
 
+  /**
+   * Tests the row count limiting of cache bin database tables.
+   */
+  public function testGarbageCollection() {
+    $backend = $this->getCacheBackend();
+    $max_rows = static::$maxRows;
+
+    $this->assertSame(0, (int) $this->getNumRows());
+
+    // Fill to just the limit.
+    for ($i = 0; $i < $max_rows; $i++) {
+      $backend->set("test$i", $i);
+    }
+    $this->assertSame($max_rows, $this->getNumRows());
+
+    // Garbage collection has no effect.
+    $backend->garbageCollection();
+    $this->assertSame($max_rows, $this->getNumRows());
+
+    // Go one row beyond the limit.
+    $backend->set('test' . ($max_rows + 1), $max_rows + 1);
+    $this->assertSame($max_rows + 1, $this->getNumRows());
+
+    // Garbage collection removes one row: the oldest.
+    $backend->garbageCollection();
+    $this->assertSame($max_rows, $this->getNumRows());
+    $this->assertFalse($backend->get('test0'));
+  }
+
+  /**
+   * Gets the number of rows in the test cache bin database table.
+   *
+   * @return int
+   *   The number of rows in the test cache bin database table.
+   */
+  protected function getNumRows() {
+    $table = 'cache_' . $this->testBin;
+    $connection = $this->container->get('database');
+    $query = $connection->select($table);
+    $query->addExpression('COUNT(cid)', 'cid');
+    return (int) $query->execute()->fetchField();
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php b/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php
index 8129410e60..13b29c33f3 100644
--- a/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php
@@ -70,7 +70,8 @@ public function register(ContainerBuilder $container) {
     parent::register($container);
     $container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
       ->addArgument(new Reference('database'))
-      ->addArgument(new Reference('cache_tags.invalidator.checksum'));
+      ->addArgument(new Reference('cache_tags.invalidator.checksum'))
+      ->addArgument(new Reference('settings'));
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php b/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php
new file mode 100644
index 0000000000..f609ff1078
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Tests\Core\Cache;
+
+use Drupal\Core\Cache\CacheTagsChecksumInterface;
+use Drupal\Core\Cache\DatabaseBackendFactory;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Site\Settings;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Cache\DatabaseBackendFactory
+ * @group Cache
+ */
+class DatabaseBackendFactoryTest extends UnitTestCase {
+
+  /**
+   * @covers ::__construct
+   * @covers ::get
+   * @dataProvider getProvider
+   */
+  public function testGet(array $settings, $expected_max_rows_foo, $expected_max_rows_bar) {
+    $database_backend_factory = new DatabaseBackendFactory(
+      $this->prophesize(Connection::class)->reveal(),
+      $this->prophesize(CacheTagsChecksumInterface::class)->reveal(),
+      new Settings($settings)
+    );
+
+    $this->assertSame($expected_max_rows_foo, $database_backend_factory->get('foo')->getMaxRows());
+    $this->assertSame($expected_max_rows_bar, $database_backend_factory->get('bar')->getMaxRows());
+  }
+
+  public function getProvider() {
+    return [
+      'default' => [
+        [],
+        10000,
+        10000,
+      ],
+      'default overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'default' => 99,
+          ],
+        ],
+        99,
+        99,
+      ],
+      'default + foo bin overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'bins' => [
+              'foo' => 13,
+            ],
+          ],
+        ],
+        13,
+        10000,
+      ],
+      'default + bar bin overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'bins' => [
+              'bar' => 13,
+            ],
+          ],
+        ],
+        10000,
+        13,
+      ],
+      'default overridden + bar bin overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'default' => 99,
+            'bins' => [
+              'bar' => 13,
+            ],
+          ],
+        ],
+        99,
+        13,
+      ],
+      'default + both bins overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'bins' => [
+              'foo' => 13,
+              'bar' => 31,
+            ],
+          ],
+        ],
+        13,
+        31,
+      ],
+      'default overridden + both bins overridden' => [
+        [
+          'database_cache_max_rows' => [
+            'default' => 99,
+            'bins' => [
+              'foo' => 13,
+              'bar' => 31,
+            ],
+          ],
+        ],
+        13,
+        31,
+      ],
+    ];
+  }
+
+}
