docs: public Deferred<List<byte[]>> lookupAsync() {
      final Pattern tagv_regex = tagv_filter != null ? 
          Pattern.compile(tagv_filter) : null;
      
      // we don't really know what size the UIDs will resolve to so just grab
      // a decent amount.
      final StringBuffer buf = to_stdout ? new StringBuffer(2048) : null;
      final long start = System.currentTimeMillis();
      final int limit;
      if (query.getLimit() > 0) {
        if (query.useMeta() || Const.SALT_WIDTH() < 1) {
          limit = query.getLimit();
        } else if (query.getLimit() < Const.SALT_BUCKETS()) {
          limit = 1;
        } else {
          limit = query.getLimit() / Const.SALT_BUCKETS();
        }
      } else {
        limit = 0;
      }
          
      class ScannerCB implements Callback<Deferred<List<byte[]>>, 
        ArrayList<ArrayList<KeyValue>>> {
        private final Scanner scanner;
        // used to avoid dupes when scanning the data table
        private byte[] last_tsuid = null;
        private int rows_read;
        
        ScannerCB(final Scanner scanner) {
          this.scanner = scanner;
        }
        
        Deferred<List<byte[]>> scan() {
          return scanner.nextRows().addCallbackDeferring(this);
        }
        
        @Override
        public Deferred<List<byte[]>> call(final ArrayList<ArrayList<KeyValue>> rows)
            throws Exception {
          if (rows == null) {
            scanner.close();
            if (query.useMeta() || Const.SALT_WIDTH() < 1) {
              LOG.debug("Lookup query matched " + tsuids.size() + " time series in " +
                  (System.currentTimeMillis() - start) + " ms");
            }
            return Deferred.fromResult(tsuids);
          }
          
          for (final ArrayList<KeyValue> row : rows) {
            if (limit > 0 && rows_read >= limit) {
              // little recursion to close the scanner and log above.
              return call(null);
            }
            final byte[] tsuid = query.useMeta() ? row.get(0).key() : 
              UniqueId.getTSUIDFromKey(row.get(0).key(), TSDB.metrics_width(), 
                  Const.TIMESTAMP_BYTES);
            
            // TODO - there MUST be a better way than creating a ton of temp
            // string objects.
            if (tagv_regex != null && 
                !tagv_regex.matcher(new String(tsuid, CHARSET)).find()) {
              continue;
            }
            
            if (to_stdout) {
              if (last_tsuid != null && Bytes.memcmp(last_tsuid, tsuid) == 0) {
                continue;
              }
              last_tsuid = tsuid;
              
              try {
                buf.append(UniqueId.uidToString(tsuid)).append(" ");
                buf.append(RowKey.metricNameAsync(tsdb, tsuid)
                    .joinUninterruptibly());
                buf.append(" ");
                
                final List<byte[]> tag_ids = UniqueId.getTagPairsFromTSUID(tsuid);
                final Map<String, String> resolved_tags = 
                    Tags.resolveIdsAsync(tsdb, tag_ids).joinUninterruptibly();
                for (final Map.Entry<String, String> tag_pair : 
                    resolved_tags.entrySet()) {
                  buf.append(tag_pair.getKey()).append("=")
                     .append(tag_pair.getValue()).append(" ");
                }
              } catch (NoSuchUniqueId nsui) {
                LOG.error("Unable to resolve UID in TSUID (" + 
                    UniqueId.uidToString(tsuid) + ") " + nsui.getMessage());
              }
              buf.setLength(0); // reset the buffer so we can re-use it
            } else {
              tsuids.add(tsuid);
            }
            ++rows_read;
          }
          
          return scan();
        }
        
        @Override
        public String toString() {
          return "Scanner callback";
        }
      }
      
      class CompleteCB implements Callback<List<byte[]>, ArrayList<List<byte[]>>> {
        @Override
        public List<byte[]> call(final ArrayList<List<byte[]>> unused) throws Exception {
          LOG.debug("Lookup query matched " + tsuids.size() + " time series in " +
              (System.currentTimeMillis() - start) + " ms");
          return tsuids;
        }
        @Override
        public String toString() {
          return "Final async lookup callback";
        }
      }
      
      class UIDCB implements Callback<Deferred<List<byte[]>>, Object> {
        @Override
        public Deferred<List<byte[]>> call(Object arg0) throws Exception {
          if (!query.useMeta() && Const.SALT_WIDTH() > 0 && metric_uid != null) {
            final ArrayList<Deferred<List<byte[]>>> deferreds = 
                new ArrayList<Deferred<List<byte[]>>>(Const.SALT_BUCKETS());
            for (int i = 0; i < Const.SALT_BUCKETS(); i++) {
              deferreds.add(new ScannerCB(getScanner(i)).scan());
            }
            return Deferred.group(deferreds).addCallback(new CompleteCB());
          } else {
            return new ScannerCB(getScanner(0)).scan();
          }
        }
        @Override
        public String toString() {
          return "UID resolution callback";
        }
      }
      
      return resolveUIDs().addCallbackDeferring(new UIDCB());
    }