@Override
  public LimitedScanResult<List<TransactionId>> scanTraceIndex(
      final String applicationName, Range range, int limit, boolean scanBackward) {
    if (applicationName == null) {
      throw new NullPointerException("applicationName must not be null");
    }
    if (range == null) {
      throw new NullPointerException("range must not be null");
    }
    if (limit < 0) {
      throw new IllegalArgumentException("negative limit:" + limit);
    }
    logger.debug("scanTraceIndex");
    Scan scan = createScan(applicationName, range, scanBackward);

    final LimitedScanResult<List<TransactionId>> limitedScanResult = new LimitedScanResult<>();
    LastRowAccessor lastRowAccessor = new LastRowAccessor();
    List<List<TransactionId>> traceIndexList =
        hbaseOperations2.findParallel(
            HBaseTables.APPLICATION_TRACE_INDEX,
            scan,
            traceIdRowKeyDistributor,
            limit,
            traceIndexMapper,
            lastRowAccessor,
            APPLICATION_TRACE_INDEX_NUM_PARTITIONS);

    List<TransactionId> transactionIdSum = new ArrayList<>(128);
    for (List<TransactionId> transactionId : traceIndexList) {
      transactionIdSum.addAll(transactionId);
    }
    limitedScanResult.setScanData(transactionIdSum);

    if (transactionIdSum.size() >= limit) {
      Long lastRowTimestamp = lastRowAccessor.getLastRowTimestamp();
      limitedScanResult.setLimitedTime(lastRowTimestamp);
      if (logger.isDebugEnabled()) {
        logger.debug("lastRowTimestamp lastTime:{}", DateUtils.longToDateStr(lastRowTimestamp));
      }
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("scanner start lastTime:{}", DateUtils.longToDateStr(range.getFrom()));
      }
      limitedScanResult.setLimitedTime(range.getFrom());
    }

    return limitedScanResult;
  }
  private Scan createScan(String applicationName, Range range, boolean scanBackward) {
    Scan scan = new Scan();
    scan.setCaching(this.scanCacheSize);

    byte[] bApplicationName = Bytes.toBytes(applicationName);
    byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bApplicationName, range.getFrom());
    byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bApplicationName, range.getTo());

    if (scanBackward) {
      // start key is replaced by end key because key has been reversed
      scan.setStartRow(traceIndexEndKey);
      scan.setStopRow(traceIndexStartKey);
    } else {
      scan.setReversed(true);
      scan.setStartRow(traceIndexStartKey);
      scan.setStopRow(traceIndexEndKey);
    }

    scan.addFamily(HBaseTables.APPLICATION_TRACE_INDEX_CF_TRACE);
    scan.setId("ApplicationTraceIndexScan");

    // toString() method of Scan converts a message to json format so it is slow for the first time.
    logger.trace("create scan:{}", scan);
    return scan;
  }
  @Override
  public ScatterData scanTraceScatterData(
      String applicationName,
      Range range,
      int xGroupUnit,
      int yGroupUnit,
      int limit,
      boolean scanBackward) {
    if (applicationName == null) {
      throw new NullPointerException("applicationName must not be null");
    }
    if (range == null) {
      throw new NullPointerException("range must not be null");
    }
    if (limit < 0) {
      throw new IllegalArgumentException("negative limit:" + limit);
    }
    logger.debug("scanTraceScatterDataMadeOfDotGroup");
    Scan scan = createScan(applicationName, range, scanBackward);

    TraceIndexScatterMapper3 mapper =
        new TraceIndexScatterMapper3(range.getFrom(), range.getTo(), xGroupUnit, yGroupUnit);
    List<ScatterData> dotGroupList =
        hbaseOperations2.findParallel(
            HBaseTables.APPLICATION_TRACE_INDEX,
            scan,
            traceIdRowKeyDistributor,
            limit,
            mapper,
            APPLICATION_TRACE_INDEX_NUM_PARTITIONS);

    if (ListUtils.isEmpty(dotGroupList)) {
      return new ScatterData(range.getFrom(), range.getTo(), xGroupUnit, yGroupUnit);
    } else {
      ScatterData firstScatterData = dotGroupList.get(0);
      for (int i = 1; i < dotGroupList.size(); i++) {
        firstScatterData.merge(dotGroupList.get(i));
      }

      return firstScatterData;
    }
  }