diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php
index af6f7f7f43..b25905668b 100755
--- a/scripts/ssh/ssh-auth.php
+++ b/scripts/ssh/ssh-auth.php
@@ -1,77 +1,87 @@
 #!/usr/bin/env php
 <?php
 
 $root = dirname(dirname(dirname(__FILE__)));
 require_once $root.'/scripts/__init_script__.php';
 
-$keys = id(new PhabricatorAuthSSHKeyQuery())
-  ->setViewer(PhabricatorUser::getOmnipotentUser())
-  ->withIsActive(true)
-  ->execute();
-
-if (!$keys) {
-  echo pht('No keys found.')."\n";
-  exit(1);
-}
+$cache = PhabricatorCaches::getMutableCache();
+$authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY;
+$authfile = $cache->getKey($authfile_key);
+
+if ($authfile === null) {
+  $keys = id(new PhabricatorAuthSSHKeyQuery())
+    ->setViewer(PhabricatorUser::getOmnipotentUser())
+    ->withIsActive(true)
+    ->execute();
+
+  if (!$keys) {
+    echo pht('No keys found.')."\n";
+    exit(1);
+  }
 
-$bin = $root.'/bin/ssh-exec';
-foreach ($keys as $ssh_key) {
-  $key_argv = array();
-  $object = $ssh_key->getObject();
-  if ($object instanceof PhabricatorUser) {
-    $key_argv[] = '--phabricator-ssh-user';
-    $key_argv[] = $object->getUsername();
-  } else if ($object instanceof AlmanacDevice) {
-    if (!$ssh_key->getIsTrusted()) {
-      // If this key is not a trusted device key, don't allow SSH
-      // authentication.
+  $bin = $root.'/bin/ssh-exec';
+  foreach ($keys as $ssh_key) {
+    $key_argv = array();
+    $object = $ssh_key->getObject();
+    if ($object instanceof PhabricatorUser) {
+      $key_argv[] = '--phabricator-ssh-user';
+      $key_argv[] = $object->getUsername();
+    } else if ($object instanceof AlmanacDevice) {
+      if (!$ssh_key->getIsTrusted()) {
+        // If this key is not a trusted device key, don't allow SSH
+        // authentication.
+        continue;
+      }
+      $key_argv[] = '--phabricator-ssh-device';
+      $key_argv[] = $object->getName();
+    } else {
+      // We don't know what sort of key this is; don't permit SSH auth.
       continue;
     }
-    $key_argv[] = '--phabricator-ssh-device';
-    $key_argv[] = $object->getName();
-  } else {
-    // We don't know what sort of key this is; don't permit SSH auth.
-    continue;
-  }
 
-  $key_argv[] = '--phabricator-ssh-key';
-  $key_argv[] = $ssh_key->getID();
+    $key_argv[] = '--phabricator-ssh-key';
+    $key_argv[] = $ssh_key->getID();
 
-  $cmd = csprintf('%s %Ls', $bin, $key_argv);
+    $cmd = csprintf('%s %Ls', $bin, $key_argv);
 
-  $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
-  if (strlen($instance)) {
-    $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd);
-  }
+    $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
+    if (strlen($instance)) {
+      $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd);
+    }
 
-  // This is additional escaping for the SSH 'command="..."' string.
-  $cmd = addcslashes($cmd, '"\\');
+    // This is additional escaping for the SSH 'command="..."' string.
+    $cmd = addcslashes($cmd, '"\\');
 
-  // Strip out newlines and other nonsense from the key type and key body.
+    // Strip out newlines and other nonsense from the key type and key body.
 
-  $type = $ssh_key->getKeyType();
-  $type = preg_replace('@[\x00-\x20]+@', '', $type);
-  if (!strlen($type)) {
-    continue;
-  }
+    $type = $ssh_key->getKeyType();
+    $type = preg_replace('@[\x00-\x20]+@', '', $type);
+    if (!strlen($type)) {
+      continue;
+    }
 
-  $key = $ssh_key->getKeyBody();
-  $key = preg_replace('@[\x00-\x20]+@', '', $key);
-  if (!strlen($key)) {
-    continue;
-  }
+    $key = $ssh_key->getKeyBody();
+    $key = preg_replace('@[\x00-\x20]+@', '', $key);
+    if (!strlen($key)) {
+      continue;
+    }
+
+    $options = array(
+      'command="'.$cmd.'"',
+      'no-port-forwarding',
+      'no-X11-forwarding',
+      'no-agent-forwarding',
+      'no-pty',
+    );
+    $options = implode(',', $options);
 
-  $options = array(
-    'command="'.$cmd.'"',
-    'no-port-forwarding',
-    'no-X11-forwarding',
-    'no-agent-forwarding',
-    'no-pty',
-  );
-  $options = implode(',', $options);
+    $lines[] = $options.' '.$type.' '.$key."\n";
+  }
 
-  $lines[] = $options.' '.$type.' '.$key."\n";
+  $authfile = implode('', $lines);
+  $ttl = phutil_units('24 hours in seconds');
+  $cache->setKey($authfile_key, $authfile, $ttl);
 }
 
-echo implode('', $lines);
+echo $authfile;
 exit(0);
diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
index 1fc61ffb2c..4d04707598 100644
--- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
+++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
@@ -1,244 +1,258 @@
 <?php
 
 final class PhabricatorAuthSSHKeyEditor
   extends PhabricatorApplicationTransactionEditor {
 
   public function getEditorApplicationClass() {
     return 'PhabricatorAuthApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('SSH Keys');
   }
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
     $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
     $types[] = PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE;
 
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
         return $object->getName();
       case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
         return $object->getEntireKey();
       case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
         return !$object->getIsActive();
     }
 
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
       case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
         return $xaction->getNewValue();
       case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
         return (bool)$xaction->getNewValue();
     }
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $value = $xaction->getNewValue();
     switch ($xaction->getTransactionType()) {
       case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
         $object->setName($value);
         return;
       case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
         $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value);
 
         $type = $public_key->getType();
         $body = $public_key->getBody();
         $comment = $public_key->getComment();
 
         $object->setKeyType($type);
         $object->setKeyBody($body);
         $object->setKeyComment($comment);
         return;
       case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
         if ($value) {
           $new = null;
         } else {
           $new = 1;
         }
 
         $object->setIsActive($new);
         return;
     }
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     return;
   }
 
   protected function validateTransaction(
     PhabricatorLiskDAO $object,
     $type,
     array $xactions) {
 
     $errors = parent::validateTransaction($object, $type, $xactions);
 
     switch ($type) {
       case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
         $missing = $this->validateIsEmptyTextField(
           $object->getName(),
           $xactions);
 
         if ($missing) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Required'),
             pht('SSH key name is required.'),
             nonempty(last($xactions), null));
 
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         }
         break;
 
       case PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
         $missing = $this->validateIsEmptyTextField(
           $object->getName(),
           $xactions);
 
         if ($missing) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Required'),
             pht('SSH key material is required.'),
             nonempty(last($xactions), null));
 
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         } else {
           foreach ($xactions as $xaction) {
             $new = $xaction->getNewValue();
 
             try {
               $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new);
             } catch (Exception $ex) {
               $errors[] = new PhabricatorApplicationTransactionValidationError(
                 $type,
                 pht('Invalid'),
                 $ex->getMessage(),
                 $xaction);
             }
           }
         }
         break;
 
       case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
         foreach ($xactions as $xaction) {
           if (!$xaction->getNewValue()) {
             $errors[] = new PhabricatorApplicationTransactionValidationError(
               $type,
               pht('Invalid'),
               pht('SSH keys can not be reactivated.'),
               $xaction);
           }
         }
         break;
     }
 
     return $errors;
   }
 
   protected function didCatchDuplicateKeyException(
     PhabricatorLiskDAO $object,
     array $xactions,
     Exception $ex) {
 
     $errors = array();
     $errors[] = new PhabricatorApplicationTransactionValidationError(
       PhabricatorAuthSSHKeyTransaction::TYPE_KEY,
       pht('Duplicate'),
       pht(
         'This public key is already associated with another user or device. '.
         'Each key must unambiguously identify a single unique owner.'),
       null);
 
     throw new PhabricatorApplicationTransactionValidationException($errors);
   }
 
 
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function getMailSubjectPrefix() {
     return pht('[SSH Key]');
   }
 
   protected function getMailThreadID(PhabricatorLiskDAO $object) {
     return 'ssh-key-'.$object->getPHID();
   }
 
+  protected function applyFinalEffects(
+    PhabricatorLiskDAO $object,
+    array $xactions) {
+
+    // After making any change to an SSH key, drop the authfile cache so it
+    // is regenerated the next time anyone authenticates.
+    $cache = PhabricatorCaches::getMutableCache();
+    $authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY;
+    $cache->deleteKey($authfile_key);
+
+    return $xactions;
+  }
+
+
   protected function getMailTo(PhabricatorLiskDAO $object) {
     return $object->getObject()->getSSHKeyNotifyPHIDs();
   }
 
   protected function getMailCC(PhabricatorLiskDAO $object) {
     return array();
   }
 
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new PhabricatorAuthSSHKeyReplyHandler())
       ->setMailReceiver($object);
   }
 
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     $id = $object->getID();
     $name = $object->getName();
     $phid = $object->getPHID();
 
     $mail = id(new PhabricatorMetaMTAMail())
       ->setSubject(pht('SSH Key %d: %s', $id, $name))
       ->addHeader('Thread-Topic', $phid);
 
     // The primary value of this mail is alerting users to account compromises,
     // so force delivery. In particular, this mail should still be delievered
     // even if "self mail" is disabled.
     $mail->setForceDelivery(true);
 
     return $mail;
   }
 
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $body = parent::buildMailBody($object, $xactions);
 
     $body->addTextSection(
       pht('SECURITY WARNING'),
       pht(
         'If you do not recognize this change, it may indicate your account '.
         'has been compromised.'));
 
     $detail_uri = $object->getURI();
     $detail_uri = PhabricatorEnv::getProductionURI($detail_uri);
 
     $body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri);
 
     return $body;
   }
 
 }
diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
index 4592d794fa..6ba047d100 100644
--- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
@@ -1,130 +1,132 @@
 <?php
 
 final class PhabricatorAuthSSHKeyQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
+  const AUTHFILE_CACHEKEY = 'ssh.authfile';
+
   private $ids;
   private $phids;
   private $objectPHIDs;
   private $keys;
   private $isActive;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withObjectPHIDs(array $object_phids) {
     $this->objectPHIDs = $object_phids;
     return $this;
   }
 
   public function withKeys(array $keys) {
     assert_instances_of($keys, 'PhabricatorAuthSSHPublicKey');
     $this->keys = $keys;
     return $this;
   }
 
   public function withIsActive($active) {
     $this->isActive = $active;
     return $this;
   }
 
   public function newResultObject() {
     return new PhabricatorAuthSSHKey();
   }
 
   protected function loadPage() {
     return $this->loadStandardPage($this->newResultObject());
   }
 
   protected function willFilterPage(array $keys) {
     $object_phids = mpull($keys, 'getObjectPHID');
 
     $objects = id(new PhabricatorObjectQuery())
       ->setViewer($this->getViewer())
       ->setParentQuery($this)
       ->withPHIDs($object_phids)
       ->execute();
     $objects = mpull($objects, null, 'getPHID');
 
     foreach ($keys as $key => $ssh_key) {
       $object = idx($objects, $ssh_key->getObjectPHID());
 
       // We must have an object, and that object must be a valid object for
       // SSH keys.
       if (!$object || !($object instanceof PhabricatorSSHPublicKeyInterface)) {
         $this->didRejectResult($ssh_key);
         unset($keys[$key]);
         continue;
       }
 
       $ssh_key->attachObject($object);
     }
 
     return $keys;
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->objectPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'objectPHID IN (%Ls)',
         $this->objectPHIDs);
     }
 
     if ($this->keys !== null) {
       $sql = array();
       foreach ($this->keys as $key) {
         $sql[] = qsprintf(
           $conn,
           '(keyType = %s AND keyIndex = %s)',
           $key->getType(),
           $key->getHash());
       }
       $where[] = implode(' OR ', $sql);
     }
 
     if ($this->isActive !== null) {
       if ($this->isActive) {
         $where[] = qsprintf(
           $conn,
           'isActive = %d',
           1);
       } else {
         $where[] = qsprintf(
           $conn,
           'isActive IS NULL');
       }
     }
 
     return $where;
 
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorAuthApplication';
   }
 
 }
diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php
index c9bd304ccf..a725238150 100644
--- a/src/applications/cache/PhabricatorCaches.php
+++ b/src/applications/cache/PhabricatorCaches.php
@@ -1,394 +1,411 @@
 <?php
 
 /**
  *
  * @task request    Request Cache
  * @task immutable  Immutable Cache
  * @task setup      Setup Cache
  * @task compress   Compression
  */
 final class PhabricatorCaches extends Phobject {
 
   private static $requestCache;
 
   public static function getNamespace() {
     return PhabricatorEnv::getEnvConfig('phabricator.cache-namespace');
   }
 
   private static function newStackFromCaches(array $caches) {
     $caches = self::addNamespaceToCaches($caches);
     $caches = self::addProfilerToCaches($caches);
     return id(new PhutilKeyValueCacheStack())
       ->setCaches($caches);
   }
 
 /* -(  Request Cache  )------------------------------------------------------ */
 
 
   /**
    * Get a request cache stack.
    *
    * This cache stack is destroyed after each logical request. In particular,
    * it is destroyed periodically by the daemons, while `static` caches are
    * not.
    *
    * @return PhutilKeyValueCacheStack Request cache stack.
    */
   public static function getRequestCache() {
     if (!self::$requestCache) {
       self::$requestCache = new PhutilInRequestKeyValueCache();
     }
     return self::$requestCache;
   }
 
 
   /**
    * Destroy the request cache.
    *
    * This is called at the beginning of each logical request.
    *
    * @return void
    */
   public static function destroyRequestCache() {
     self::$requestCache = null;
   }
 
 
 /* -(  Immutable Cache  )---------------------------------------------------- */
 
 
   /**
    * Gets an immutable cache stack.
    *
    * This stack trades mutability away for improved performance. Normally, it is
    * APC + DB.
    *
    * In the general case with multiple web frontends, this stack can not be
    * cleared, so it is only appropriate for use if the value of a given key is
    * permanent and immutable.
    *
    * @return PhutilKeyValueCacheStack Best immutable stack available.
    * @task immutable
    */
   public static function getImmutableCache() {
     static $cache;
     if (!$cache) {
       $caches = self::buildImmutableCaches();
       $cache = self::newStackFromCaches($caches);
     }
     return $cache;
   }
 
 
   /**
    * Build the immutable cache stack.
    *
    * @return list<PhutilKeyValueCache> List of caches.
    * @task immutable
    */
   private static function buildImmutableCaches() {
     $caches = array();
 
     $apc = new PhutilAPCKeyValueCache();
     if ($apc->isAvailable()) {
       $caches[] = $apc;
     }
 
     $caches[] = new PhabricatorKeyValueDatabaseCache();
 
     return $caches;
   }
 
+  public static function getMutableCache() {
+    static $cache;
+    if (!$cache) {
+      $caches = self::buildMutableCaches();
+      $cache = self::newStackFromCaches($caches);
+    }
+    return $cache;
+  }
+
+  private static function buildMutableCaches() {
+    $caches = array();
+
+    $caches[] = new PhabricatorKeyValueDatabaseCache();
+
+    return $caches;
+  }
+
 
 /* -(  Repository Graph Cache  )--------------------------------------------- */
 
 
   public static function getRepositoryGraphL1Cache() {
     static $cache;
     if (!$cache) {
       $caches = self::buildRepositoryGraphL1Caches();
       $cache = self::newStackFromCaches($caches);
     }
     return $cache;
   }
 
   private static function buildRepositoryGraphL1Caches() {
     $caches = array();
 
     $request = new PhutilInRequestKeyValueCache();
     $request->setLimit(32);
     $caches[] = $request;
 
     $apc = new PhutilAPCKeyValueCache();
     if ($apc->isAvailable()) {
       $caches[] = $apc;
     }
 
     return $caches;
   }
 
   public static function getRepositoryGraphL2Cache() {
     static $cache;
     if (!$cache) {
       $caches = self::buildRepositoryGraphL2Caches();
       $cache = self::newStackFromCaches($caches);
     }
     return $cache;
   }
 
   private static function buildRepositoryGraphL2Caches() {
     $caches = array();
     $caches[] = new PhabricatorKeyValueDatabaseCache();
     return $caches;
   }
 
 
 /* -(  Server State Cache  )------------------------------------------------- */
 
 
   /**
    * Highly specialized cache for storing server process state.
    *
    * We use this cache to track initial steps in the setup phase, before
    * configuration is loaded.
    *
    * This cache does NOT use the cache namespace (it must be accessed before
    * we build configuration), and is global across all instances on the host.
    *
    * @return PhutilKeyValueCacheStack Best available server state cache stack.
    * @task setup
    */
   public static function getServerStateCache() {
     static $cache;
     if (!$cache) {
       $caches = self::buildSetupCaches('phabricator-server');
 
       // NOTE: We are NOT adding a cache namespace here! This cache is shared
       // across all instances on the host.
 
       $caches = self::addProfilerToCaches($caches);
       $cache = id(new PhutilKeyValueCacheStack())
         ->setCaches($caches);
 
     }
     return $cache;
   }
 
 
 
 /* -(  Setup Cache  )-------------------------------------------------------- */
 
 
   /**
    * Highly specialized cache for performing setup checks. We use this cache
    * to determine if we need to run expensive setup checks when the page
    * loads. Without it, we would need to run these checks every time.
    *
    * Normally, this cache is just APC. In the absence of APC, this cache
    * degrades into a slow, quirky on-disk cache.
    *
    * NOTE: Do not use this cache for anything else! It is not a general-purpose
    * cache!
    *
    * @return PhutilKeyValueCacheStack Most qualified available cache stack.
    * @task setup
    */
   public static function getSetupCache() {
     static $cache;
     if (!$cache) {
       $caches = self::buildSetupCaches('phabricator-setup');
       $cache = self::newStackFromCaches($caches);
     }
     return $cache;
   }
 
 
   /**
    * @task setup
    */
   private static function buildSetupCaches($cache_name) {
     // If this is the CLI, just build a setup cache.
     if (php_sapi_name() == 'cli') {
       return array();
     }
 
     // In most cases, we should have APC. This is an ideal cache for our
     // purposes -- it's fast and empties on server restart.
     $apc = new PhutilAPCKeyValueCache();
     if ($apc->isAvailable()) {
       return array($apc);
     }
 
     // If we don't have APC, build a poor approximation on disk. This is still
     // much better than nothing; some setup steps are quite slow.
     $disk_path = self::getSetupCacheDiskCachePath($cache_name);
     if ($disk_path) {
       $disk = new PhutilOnDiskKeyValueCache();
       $disk->setCacheFile($disk_path);
       $disk->setWait(0.1);
       if ($disk->isAvailable()) {
         return array($disk);
       }
     }
 
     return array();
   }
 
 
   /**
    * @task setup
    */
   private static function getSetupCacheDiskCachePath($name) {
     // The difficulty here is in choosing a path which will change on server
     // restart (we MUST have this property), but as rarely as possible
     // otherwise (we desire this property to give the cache the best hit rate
     // we can).
 
     // Unfortunately, we don't have a very good strategy for minimizing the
     // churn rate of the cache. We previously tried to use the parent process
     // PID in some cases, but this was not reliable. See T9599 for one case of
     // this.
 
     $pid_basis = getmypid();
 
     // If possible, we also want to know when the process launched, so we can
     // drop the cache if a process restarts but gets the same PID an earlier
     // process had. "/proc" is not available everywhere (e.g., not on OSX), but
     // check if we have it.
     $epoch_basis = null;
     $stat = @stat("/proc/{$pid_basis}");
     if ($stat !== false) {
       $epoch_basis = $stat['ctime'];
     }
 
     $tmp_dir = sys_get_temp_dir();
 
     $tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.$name;
     if (!file_exists($tmp_path)) {
       @mkdir($tmp_path);
     }
 
     $is_ok = self::testTemporaryDirectory($tmp_path);
     if (!$is_ok) {
       $tmp_path = $tmp_dir;
       $is_ok = self::testTemporaryDirectory($tmp_path);
       if (!$is_ok) {
         // We can't find anywhere to write the cache, so just bail.
         return null;
       }
     }
 
     $tmp_name = 'setup-'.$pid_basis;
     if ($epoch_basis) {
       $tmp_name .= '.'.$epoch_basis;
     }
     $tmp_name .= '.cache';
 
     return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name;
   }
 
 
   /**
    * @task setup
    */
   private static function testTemporaryDirectory($dir) {
     if (!@file_exists($dir)) {
       return false;
     }
     if (!@is_dir($dir)) {
       return false;
     }
     if (!@is_writable($dir)) {
       return false;
     }
 
     return true;
   }
 
   private static function addProfilerToCaches(array $caches) {
     foreach ($caches as $key => $cache) {
       $pcache = new PhutilKeyValueCacheProfiler($cache);
       $pcache->setProfiler(PhutilServiceProfiler::getInstance());
       $caches[$key] = $pcache;
     }
     return $caches;
   }
 
   private static function addNamespaceToCaches(array $caches) {
     $namespace = self::getNamespace();
     if (!$namespace) {
       return $caches;
     }
 
     foreach ($caches as $key => $cache) {
       $ncache = new PhutilKeyValueCacheNamespace($cache);
       $ncache->setNamespace($namespace);
       $caches[$key] = $ncache;
     }
 
     return $caches;
   }
 
 
   /**
    * Deflate a value, if deflation is available and has an impact.
    *
    * If the value is larger than 1KB, we have `gzdeflate()`, we successfully
    * can deflate it, and it benefits from deflation, we deflate it. Otherwise
    * we leave it as-is.
    *
    * Data can later be inflated with @{method:inflateData}.
    *
    * @param string String to attempt to deflate.
    * @return string|null Deflated string, or null if it was not deflated.
    * @task compress
    */
   public static function maybeDeflateData($value) {
     $len = strlen($value);
     if ($len <= 1024) {
       return null;
     }
 
     if (!function_exists('gzdeflate')) {
       return null;
     }
 
     $deflated = gzdeflate($value);
     if ($deflated === false) {
       return null;
     }
 
     $deflated_len = strlen($deflated);
     if ($deflated_len >= ($len / 2)) {
       return null;
     }
 
     return $deflated;
   }
 
 
   /**
    * Inflate data previously deflated by @{method:maybeDeflateData}.
    *
    * @param string Deflated data, from @{method:maybeDeflateData}.
    * @return string Original, uncompressed data.
    * @task compress
    */
   public static function inflateData($value) {
     if (!function_exists('gzinflate')) {
       throw new Exception(
         pht(
           '%s is not available; unable to read deflated data!',
           'gzinflate()'));
     }
 
     $value = gzinflate($value);
     if ($value === false) {
       throw new Exception(pht('Failed to inflate data!'));
     }
 
     return $value;
   }
 
 
 }
diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
index 99060478c0..c6a52024fe 100644
--- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
+++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
@@ -1,174 +1,174 @@
 <?php
 
 final class PhabricatorKeyValueDatabaseCache
   extends PhutilKeyValueCache {
 
   const CACHE_FORMAT_RAW        = 'raw';
   const CACHE_FORMAT_DEFLATE    = 'deflate';
 
   public function setKeys(array $keys, $ttl = null) {
     if (PhabricatorEnv::isReadOnly()) {
       return;
     }
 
     if ($keys) {
       $map = $this->digestKeys(array_keys($keys));
       $conn_w = $this->establishConnection('w');
 
       $sql = array();
       foreach ($map as $key => $hash) {
         $value = $keys[$key];
 
         list($format, $storage_value) = $this->willWriteValue($key, $value);
 
         $sql[] = qsprintf(
           $conn_w,
           '(%s, %s, %s, %B, %d, %nd)',
           $hash,
           $key,
           $format,
           $storage_value,
           time(),
           $ttl ? (time() + $ttl) : null);
       }
 
       $guard = AphrontWriteGuard::beginScopedUnguardedWrites();
         foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
           queryfx(
             $conn_w,
             'INSERT INTO %T
               (cacheKeyHash, cacheKey, cacheFormat, cacheData,
                 cacheCreated, cacheExpires) VALUES %Q
               ON DUPLICATE KEY UPDATE
                 cacheKey = VALUES(cacheKey),
                 cacheFormat = VALUES(cacheFormat),
                 cacheData = VALUES(cacheData),
                 cacheCreated = VALUES(cacheCreated),
                 cacheExpires = VALUES(cacheExpires)',
             $this->getTableName(),
             $chunk);
         }
       unset($guard);
     }
 
     return $this;
   }
 
   public function getKeys(array $keys) {
     $results = array();
     if ($keys) {
       $map = $this->digestKeys($keys);
 
       $rows = queryfx_all(
         $this->establishConnection('r'),
         'SELECT * FROM %T WHERE cacheKeyHash IN (%Ls)',
         $this->getTableName(),
         $map);
       $rows = ipull($rows, null, 'cacheKey');
 
       foreach ($keys as $key) {
         if (empty($rows[$key])) {
           continue;
         }
 
         $row = $rows[$key];
 
         if ($row['cacheExpires'] && ($row['cacheExpires'] < time())) {
           continue;
         }
 
         try {
           $results[$key] = $this->didReadValue(
             $row['cacheFormat'],
             $row['cacheData']);
         } catch (Exception $ex) {
           // Treat this as a cache miss.
           phlog($ex);
         }
       }
     }
 
     return $results;
   }
 
   public function deleteKeys(array $keys) {
     if ($keys) {
       $map = $this->digestKeys($keys);
       queryfx(
         $this->establishConnection('w'),
         'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)',
         $this->getTableName(),
-        $keys);
+        $map);
     }
 
     return $this;
   }
 
   public function destroyCache() {
     queryfx(
       $this->establishConnection('w'),
       'DELETE FROM %T',
       $this->getTableName());
     return $this;
   }
 
 
 /* -(  Raw Cache Access  )--------------------------------------------------- */
 
 
   public function establishConnection($mode) {
     // TODO: This is the only concrete table we have on the database right
     // now.
     return id(new PhabricatorMarkupCache())->establishConnection($mode);
   }
 
   public function getTableName() {
     return 'cache_general';
   }
 
 
 /* -(  Implementation  )----------------------------------------------------- */
 
 
   private function digestKeys(array $keys) {
     $map = array();
     foreach ($keys as $key) {
       $map[$key] = PhabricatorHash::digestForIndex($key);
     }
     return $map;
   }
 
   private function willWriteValue($key, $value) {
     if (!is_string($value)) {
       throw new Exception(pht('Only strings may be written to the DB cache!'));
     }
 
     static $can_deflate;
     if ($can_deflate === null) {
       $can_deflate = function_exists('gzdeflate') &&
                      PhabricatorEnv::getEnvConfig('cache.enable-deflate');
     }
 
     if ($can_deflate) {
       $deflated = PhabricatorCaches::maybeDeflateData($value);
       if ($deflated !== null) {
         return array(self::CACHE_FORMAT_DEFLATE, $deflated);
       }
     }
 
     return array(self::CACHE_FORMAT_RAW, $value);
   }
 
   private function didReadValue($format, $value) {
     switch ($format) {
       case self::CACHE_FORMAT_RAW:
         return $value;
       case self::CACHE_FORMAT_DEFLATE:
         return PhabricatorCaches::inflateData($value);
       default:
         throw new Exception(pht('Unknown cache format.'));
     }
   }
 
 
 }