@Override
  public boolean removeKey(
      final Key key, final SendableGet client, final long time, ObjectContainer container) {
    boolean found = false;
    final String keyAsBytes = HexUtil.bytesToHex(key.getFullKey());
    Query query = container.query();
    query.constrain(PersistentCooldownQueueItem.class);
    query.descend("keyAsBytes").constrain(keyAsBytes);
    // The result from parent will be huge, and client may be huge too.
    // Don't bother with a join, just check in the evaluation.
    //		query.descend("client").constrain(client);
    //		query.descend("parent").constrain(this);
    Evaluation eval =
        new Evaluation() {
          private static final long serialVersionUID = 1537102695504880276L;

          @Override
          public void evaluate(Candidate candidate) {
            PersistentCooldownQueueItem item = (PersistentCooldownQueueItem) candidate.getObject();
            if (item.client != client) {
              candidate.include(false);
              return;
            }
            if (item.parent != PersistentCooldownQueue.this) {
              candidate.include(false);
              return;
            }
            Key k = item.key;
            candidate.objectContainer().activate(k, 5);
            if (k.equals(key)) candidate.include(true);
            else {
              candidate.include(false);
              candidate.objectContainer().deactivate(k, 5);
            }
          }
        };
    query.constrain(eval);
    ObjectSet<PersistentCooldownQueueItem> results = query.execute();

    while (results.hasNext()) {
      found = true;
      PersistentCooldownQueueItem i = (PersistentCooldownQueueItem) results.next();
      i.delete(container);
      itemsFromLastTime.remove(i);
    }
    return found;
  }
  public Object removeKeyBefore(
      final long now,
      long dontCareAfterMillis,
      ObjectContainer container,
      int maxCount,
      PersistentCooldownQueue altQueue) {
    // Will be called repeatedly until no more keys are returned, so it doesn't
    // matter very much if they're not in order.

    // This query returns bogus results (cooldown items with times in the future).
    //		ObjectSet results = container.query(new Predicate() {
    //			public boolean match(PersistentCooldownQueueItem persistentCooldownQueueItem) {
    //				if(persistentCooldownQueueItem.time >= now) return false;
    //				if(persistentCooldownQueueItem.parent != PersistentCooldownQueue.this) return false;
    //				return true;
    //			}
    //		});

    ArrayList<Key> v = null;
    if (!itemsFromLastTime.isEmpty()) {
      if (v == null) v = new ArrayList<Key>(Math.min(maxCount, itemsFromLastTime.size()));
      Logger.normal(
          this,
          "Overflow handling in cooldown queue: reusing items from last time, now "
              + itemsFromLastTime.size());
      for (Iterator<PersistentCooldownQueueItem> it = itemsFromLastTime.iterator();
          it.hasNext() && v.size() < maxCount; ) {
        PersistentCooldownQueueItem i = it.next();
        container.activate(i, 1);
        if (i.parent != this && i.parent != altQueue) {
          container.deactivate(i, 1);
          continue;
        }
        if (i.time >= now) {
          container.deactivate(i, 1);
          if (v.isEmpty()) return i.time;
          return v.toArray(new Key[v.size()]);
        }

        container.activate(i.key, 5);
        if (i.client == null || !container.ext().isStored(i.client)) {
          Logger.normal(
              this,
              "Client has been removed but not the persistent cooldown queue item: time "
                  + i.time
                  + " for key "
                  + i.key);
        }
        if (i.key == null) {
          Logger.error(
              this,
              "Key is null on cooldown queue! i = "
                  + i
                  + " client="
                  + i.client
                  + " key as bytes = "
                  + i.keyAsBytes);
        } else {
          v.add(i.key.cloneKey());
          i.key.removeFrom(container);
        }
        i.delete(container);
        it.remove();
      }
    }
    if (v != null && v.size() == maxCount) return v.toArray(new Key[v.size()]);

    // Lets re-code it in SODA.
    long tStart = System.currentTimeMillis();
    Query query = container.query();
    query.constrain(PersistentCooldownQueueItem.class);
    // Don't constrain on parent.
    // parent index is humongous, so we get a huge memory spike, queries take ages.
    // Just check manually.
    query
        .descend("time")
        .orderAscending()
        .constrain(Long.valueOf(now + dontCareAfterMillis))
        .smaller();
    ObjectSet<PersistentCooldownQueueItem> results = query.execute();
    if (results.hasNext()) {
      long tEnd = System.currentTimeMillis();
      if (tEnd - tStart > 1000)
        Logger.error(this, "Query took " + (tEnd - tStart) + " for " + results.size());
      else if (logMINOR) Logger.minor(this, "Query took " + (tEnd - tStart));
      if (v == null) v = new ArrayList<Key>(Math.min(maxCount, results.size()));
      while (results.hasNext() && v.size() < maxCount) {
        PersistentCooldownQueueItem i = (PersistentCooldownQueueItem) results.next();
        if (i.parent != this && i.parent != altQueue) {
          continue;
        }
        if (i.time >= now) {
          if (v.isEmpty()) return i.time;
          break;
        }
        container.activate(i.key, 5);
        if (i.client == null || !container.ext().isStored(i.client)) {
          Logger.normal(
              this,
              "Client has been removed but not the persistent cooldown queue item: time "
                  + i.time
                  + " for key "
                  + i.key);
        }
        if (i.key == null) {
          Logger.error(
              this,
              "Key is null on cooldown queue! i = "
                  + i
                  + " client="
                  + i.client
                  + " key as bytes = "
                  + i.keyAsBytes);
        } else {
          v.add(i.key.cloneKey());
          i.key.removeFrom(container);
        }
        i.delete(container);
      }
      if (!v.isEmpty()) {
        while (results.hasNext() && itemsFromLastTime.size() < KEEP_ITEMS_FROM_LAST_TIME) {
          PersistentCooldownQueueItem i = (PersistentCooldownQueueItem) results.next();
          container.deactivate(i, 1);
          itemsFromLastTime.add(i);
        }
        Logger.normal(
            this,
            "Overflow handling in cooldown queue: added items, items from last time now "
                + itemsFromLastTime.size());
        return v.toArray(new Key[v.size()]);
      }
    } else {
      long tEnd = System.currentTimeMillis();
      if (tEnd - tStart > 1000) Logger.error(this, "Query took " + (tEnd - tStart));
      else if (logMINOR) Logger.minor(this, "Query took " + (tEnd - tStart));
      return null;
    }
    return null;
  }