/**
  * Retrieve or build a graph cache bucket from the cache.
  *
  * Normally, this operates as a readthrough cache call. It can also be used
  * to force a cache update by passing the existing data to `$rebuild_data`.
  *
  * @param   int     Bucket key, from @{method:getBucketKey}.
  * @param   mixed   Current data, to force a cache rebuild of this bucket.
  * @return  array   Data from the cache.
  * @task cache
  */
 private function getBucketData($bucket_key, $rebuild_data = null)
 {
     $cache_key = $this->getBucketCacheKey($bucket_key);
     // TODO: This cache stuff could be handled more gracefully, but the
     // database cache currently requires values to be strings and needs
     // some tweaking to support this as part of a stack. Our cache semantics
     // here are also unusual (not purely readthrough) because this cache is
     // appendable.
     $cache_level1 = PhabricatorCaches::getRepositoryGraphL1Cache();
     $cache_level2 = PhabricatorCaches::getRepositoryGraphL2Cache();
     if ($rebuild_data === null) {
         $bucket_data = $cache_level1->getKey($cache_key);
         if ($bucket_data) {
             return $bucket_data;
         }
         $bucket_data = $cache_level2->getKey($cache_key);
         if ($bucket_data) {
             $unserialized = @unserialize($bucket_data);
             if ($unserialized) {
                 // Fill APC if we got a database hit but missed in APC.
                 $cache_level1->setKey($cache_key, $unserialized);
                 return $unserialized;
             }
         }
     }
     if (!is_array($rebuild_data)) {
         $rebuild_data = array();
     }
     $bucket_data = $this->rebuildBucket($bucket_key, $rebuild_data);
     // Don't bother writing the data if we didn't update anything.
     if ($bucket_data !== $rebuild_data) {
         $cache_level2->setKey($cache_key, serialize($bucket_data));
         $cache_level1->setKey($cache_key, $bucket_data);
     }
     return $bucket_data;
 }