diff --git a/README.txt b/README.txt
index 3b6ed02..45f841d 100644
--- a/README.txt
+++ b/README.txt
@@ -117,10 +117,18 @@ indexed again. You can also select a server that is at the moment disabled, or
 choose to let the index lie on no server at all, for the time being. Note,
 however, that you can only create enabled indexes on an enabled server. Also,
 disabling a server will disable all indexes that lie on it.
-Lastly, the cron limit option allows you to set whether, and how many, items
-will be indexed for this index when cron runs (and the index is enabled). Items
-can also be indexed manually, so even if this is set to 0, the index can still
-be used.
+The "Index items immediately" option specifies that you want items to be
+directly re-indexed after being changed, instead of waiting for the next cron
+run. Use this if it is important that users see no stale data in searches, and
+only when your setup enables relatively fast indexing.
+Lastly, the "Cron batch size" option allows you to set whether items will be
+indexed when cron runs (as long as the index is enabled), and how many items
+will be indexed in a single batch. The best value for this setting depends on
+how time-consuming indexing is for your setup, which in turn depends mostly on
+the server used and the enabled data alterations. You should set it to a number
+of items which can easily be indexed in 10 seconds' time. Items can also be
+indexed manually, or directly when they are changed, so even if this is set to
+0, the index can still be used.
 
 - Indexed fields
   (Configuration > Search API > [Index name] > Fields)
@@ -156,7 +164,7 @@ On this page you can view how much of the entities are already indexed and also
 control indexing. With the "Index now" button (displayed only when there are
 still unindexed items) you can directly index a certain number of "dirty" items
 (i.e., items not yet indexed in their current state). Setting "-1" as the number
-will index all of those items, similar to the cron limit setting.
+will index all of those items, similar to the cron batch size setting.
 When you change settings that could affect indexing, and the index is not
 automatically marked for re-indexing, you can do this manually with the
 "Re-index content" button. All items in the index will be marked as dirty and be
diff --git a/includes/index_entity.inc b/includes/index_entity.inc
index fd9ded5..7669ee2 100644
--- a/includes/index_entity.inc
+++ b/includes/index_entity.inc
@@ -66,7 +66,7 @@ class SearchApiIndex extends Entity {
 
   /**
    * An array of options for configuring this index. The layout is as follows:
-   * - cron_limit: The maximum number of items to be indexed per cron run.
+   * - cron_limit: The maximum number of items to be indexed per cron batch.
    * - index_directly: Boolean setting whether entities are indexed immediately
    *   after they are created or updated.
    * - fields: An array of all known fields for this index. Keys are the field
diff --git a/search_api.admin.inc b/search_api.admin.inc
index 0d298a2..e5b8bcd 100644
--- a/search_api.admin.inc
+++ b/search_api.admin.inc
@@ -574,8 +574,8 @@ function search_api_admin_add_index(array $form, array &$form_state) {
   );
   $form['cron_limit'] = array(
     '#type' => 'textfield',
-    '#title' => t('Cron limit'),
-    '#description' => t('Set how many items will be indexed at most during each run of cron. ' .
+    '#title' => t('Cron batch size'),
+    '#description' => t('Set how many items will be indexed at once when indexing items during a cron run. ' .
         '"0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'),
     '#default_value' => SEARCH_API_DEFAULT_CRON_LIMIT,
     '#size' => 4,
@@ -602,7 +602,7 @@ function search_api_admin_add_index_validate(array $form, array &$form_state) {
   $cron_limit = $form_state['values']['cron_limit'];
   if ($cron_limit != '' . ((int) $cron_limit)) {
     // We don't enforce stricter rules and treat all negative values as -1.
-    form_set_error('cron_limit', t('The cron limit must be an integer.'));
+    form_set_error('cron_limit', t('The cron batch size must be an integer.'));
   }
 }
 
@@ -739,7 +739,7 @@ function theme_search_api_index(array $variables) {
   if (!$read_only && !empty($options)) {
     $output .= '<dt>' . t('Index options') . '</dt>' . "\n";
     $output .= '<dd><dl>' . "\n";
-    $output .= '<dt>' . t('Cron limit') . '</dt>' . "\n";
+    $output .= '<dt>' . t('Cron batch size') . '</dt>' . "\n";
     if (empty($options['cron_limit'])) {
       $output .= '<dd>' . t("Don't index during cron runs") . '</dd>' . "\n";
     }
@@ -747,7 +747,7 @@ function theme_search_api_index(array $variables) {
       $output .= '<dd>' . t('Unlimited') . '</dd>' . "\n";
     }
     else {
-      $output .= '<dd>' . format_plural($options['cron_limit'], '1 item per cron run.', '@count items per cron run.') . '</dd>' . "\n";
+      $output .= '<dd>' . format_plural($options['cron_limit'], '1 item per cron batch.', '@count items per cron batch.') . '</dd>' . "\n";
     }
 
     if (!empty($options['fields'])) {
@@ -1045,10 +1045,10 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
   );
   $form['cron_limit'] = array(
     '#type' => 'textfield',
-    '#title' => t('Cron limit'),
-    '#description' => t('Set how many items will be indexed at most during each run of cron. ' .
+    '#title' => t('Cron batch size'),
+    '#description' => t('Set how many items will be indexed at once when indexing items during a cron run. ' .
         '"0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'),
-    '#default_value' => isset($index->options['cron_limit']) ? $index->options['cron_limit'] : SEARCH_API_DEFAULT_CRON_LIMIT,
+      '#default_value' => isset($index->options['cron_limit']) ? $index->options['cron_limit'] : SEARCH_API_DEFAULT_CRON_LIMIT,
     '#size' => 4,
     '#attributes' => array('class' => array('search-api-cron-limit')),
     '#element_validate' => array('_element_validate_integer'),
diff --git a/search_api.drush.inc b/search_api.drush.inc
index b6d9858..d7b3507 100644
--- a/search_api.drush.inc
+++ b/search_api.drush.inc
@@ -47,7 +47,7 @@ function search_api_drush_command() {
     ),
     'arguments' => array(
       'index_id' => dt('The numeric ID or machine name of an index.'),
-      'limit' => dt("The number of items to index. Use 0 to index all items. Defaults to the index's cron limit."),
+      'limit' => dt("The number of items to index. Use 0 to index all items. Defaults to the index's cron batch size."),
     ),
     'aliases' => array('sapi-i'),
   );
diff --git a/search_api.module b/search_api.module
index 4dbbdcc..2153029 100644
--- a/search_api.module
+++ b/search_api.module
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * Default number of items indexed at each cron run for each enabled index.
+ * Default number of items indexed per cron batch for each enabled index.
  */
 define('SEARCH_API_DEFAULT_CRON_LIMIT', 50);
 
@@ -218,15 +218,27 @@ function search_api_permission() {
  * Will index $options['cron-limit'] items for each enabled index.
  */
 function search_api_cron() {
+  $queue = DrupalQueue::get('search_api_indexing_queue');
   foreach (search_api_index_load_multiple(FALSE, array('enabled' => TRUE, 'read_only' => 0)) as $index) {
     $limit = isset($index->options['cron_limit'])
         ? $index->options['cron_limit']
         : SEARCH_API_DEFAULT_CRON_LIMIT;
     if ($limit) {
       try {
-        $num = search_api_index_items($index, $limit);
-        if ($num) {
-          watchdog('search_api', t('Indexed !num items for index !name', array('!num' => $num, '!name' => $index->name)), NULL, WATCHDOG_INFO);
+        $task = array(
+          'index' => $index->machine_name,
+          'limit' => $limit,
+        );
+        if ($limit < 0) {
+          $num = 1;
+        }
+        else {
+          $status = search_api_index_status($index);
+          $todo = $status['total'] - $status['indexed'];
+          $num = (int) ceil($todo / $limit);
+        }
+        for ($i = 0; $i < $num; ++$i) {
+          $queue->createItem($task);
         }
       }
       catch (SearchApiException $e) {
@@ -237,6 +249,19 @@ function search_api_cron() {
 }
 
 /**
+ * Implements hook_cron_queue_info().
+ *
+ * Defines a queue for saved searches that should be checked for new items.
+ */
+function search_api_cron_queue_info() {
+  return array(
+    'search_api_indexing_queue' => array(
+      'worker callback' => '_search_api_indexing_queue_process',
+    ),
+  );
+}
+
+/**
  * Implements hook_entity_info().
  */
 function search_api_entity_info() {
@@ -734,7 +759,7 @@ function search_api_mark_dirty($entity_type, array $ids) {
           ->fields(array(
             'changed' => REQUEST_TIME,
           ))
-          ->condition('item_id', $ids, 'IN')
+          ->condition('item_id', $diff, 'IN')
           ->condition('index_id', $index->id)
           ->condition('changed', 0)
           ->execute();
@@ -1759,6 +1784,30 @@ function search_api_index_delete($id) {
 }
 
 /**
+ * Cron queue worker callback for indexing some items.
+ *
+ * @param array $task
+ *   An associative array containing:
+ *   - index: The ID of the index on which items should be indexed.
+ *   - items: The items that should be indexed.
+ */
+function _search_api_indexing_queue_process(array $task) {
+  $index = search_api_index_load($task['index']);
+  $limit = $task['limit'];
+  if ($index && $index->enabled && !$index->read_only) {
+    try {
+      $num = search_api_index_items($index, $limit);
+      if ($num) {
+        watchdog('search_api', t('Indexed !num items for index !name', array('!num' => $num, '!name' => $index->name)), NULL, WATCHDOG_INFO);
+      }
+    }
+    catch (SearchApiException $e) {
+      watchdog('search_api', $e->getMessage(), NULL, WATCHDOG_WARNING);
+    }
+  }
+}
+
+/**
  * Helper function to be used as a "property info alter" callback.
  *
  * If a wrapped entity is passed to this function, all its available properties
diff --git a/search_api.test b/search_api.test
index 46adf78..6b3c1d2 100644
--- a/search_api.test
+++ b/search_api.test
@@ -139,7 +139,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
     $this->assertFalse($index->enabled, t('Status correctly inserted.'));
     $this->assertEqual($index->description, $values['description'], t('Description correctly inserted.'));
     $this->assertNull($index->server, t('Index server correctly inserted.'));
-    $this->assertEqual($index->options['cron_limit'], $values['cron_limit'], t('Cron limit correctly inserted.'));
+    $this->assertEqual($index->options['cron_limit'], $values['cron_limit'], t('Cron batch size correctly inserted.'));
 
     $values = array(
       'additional[field]' => 'parent',
@@ -213,7 +213,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
     $this->assertTitle('Search API test index | Drupal', t('Correct title when viewing index.'));
     $this->assertText('An index used for testing.', t('!field displayed.', array('!field' => t('Description'))));
     $this->assertText('Search API test entity', t('!field displayed.', array('!field' => t('Entity type'))));
-    $this->assertText(format_plural(5, '1 item per cron run.', '@count items per cron run.'), t('!field displayed.', array('!field' => t('Cron limit'))));
+    $this->assertText(format_plural(5, '1 item per cron batch.', '@count items per cron batch.'), t('!field displayed.', array('!field' => t('Cron batch size'))));
 
     $this->drupalGet("admin/config/search/search_api/index/$id/status");
     $this->assertText(t('The index is currently disabled.'), t('"Disabled" status displayed.'));
