@Override
    public void reduce(
        BytesWritable topkRollupKey, Iterable<BytesWritable> timeSeriesIterable, Context context)
        throws IOException, InterruptedException {

      TopKRollupPhaseOneMapOutputKey wrapper =
          TopKRollupPhaseOneMapOutputKey.fromBytes(topkRollupKey.getBytes());
      LOGGER.info(
          "DimensionName {} DimensionValue {}",
          wrapper.getDimensionName(),
          wrapper.getDimensionValue());

      MetricTimeSeries aggregateSeries = new MetricTimeSeries(metricSchema);
      for (BytesWritable writable : timeSeriesIterable) {
        MetricTimeSeries series = MetricTimeSeries.fromBytes(writable.copyBytes(), metricSchema);
        aggregateSeries.aggregate(series);
      }

      Map<String, Long> metricValues = new HashMap<String, Long>();
      for (MetricSpec metricSpec : starTreeConfig.getMetrics()) {
        metricValues.put(metricSpec.getName(), 0L);
      }
      for (Long time : aggregateSeries.getTimeWindowSet()) {
        for (MetricSpec metricSpec : starTreeConfig.getMetrics()) {
          String metricName = metricSpec.getName();
          long metricValue = aggregateSeries.get(time, metricName).longValue();
          metricValues.put(metricName, metricValues.get(metricName) + metricValue);
        }
      }

      boolean aboveThreshold = true;
      for (MetricSpec metricSpec : starTreeConfig.getMetrics()) {
        String metricName = metricSpec.getName();

        long metricValue = metricValues.get(metricName);
        long metricSum = metricSums.get(metricName);
        double metricThreshold = metricThresholds.get(metricName);

        LOGGER.info("metricValue : {} metricSum : {}", metricValue, metricSum);
        if (metricValue < (metricThreshold / 100) * metricSum) {
          aboveThreshold = false;
          break;
        }
      }

      if (aboveThreshold) {
        LOGGER.info("Passed threshold");
        valWritable.set(aggregateSeries.toBytes(), 0, aggregateSeries.toBytes().length);
        context.write(topkRollupKey, valWritable);
      }
    }
    @Override
    public void setup(Context context) throws IOException, InterruptedException {

      LOGGER.info("TopKRollupPhaseOneJob.TopKRollupPhaseOneReducer.setup()");

      Configuration configuration = context.getConfiguration();
      FileSystem fileSystem = FileSystem.get(configuration);
      Path configPath = new Path(configuration.get(TOPK_ROLLUP_PHASE1_CONFIG_PATH.toString()));
      try {
        starTreeConfig = StarTreeConfig.decode(fileSystem.open(configPath));
        config = TopKRollupPhaseOneConfig.fromStarTreeConfig(starTreeConfig);
        dimensionNames = config.getDimensionNames();
        metricTypes = config.getMetricTypes();
        metricSchema = new MetricSchema(config.getMetricNames(), metricTypes);
        metricThresholds = config.getMetricThresholds();
        keyWritable = new BytesWritable();
        valWritable = new BytesWritable();

        MetricSums metricSumsObj =
            OBJECT_MAPPER.readValue(
                fileSystem.open(
                    new Path(configuration.get(TOPK_ROLLUP_PHASE1_METRIC_SUMS_PATH.toString()))),
                MetricSums.class);
        metricSums = metricSumsObj.getMetricSum();

      } catch (Exception e) {
        throw new IOException(e);
      }
    }
  @Override
  public List<DimensionKey> getDimensionKeys() {
    List<DimensionKey> dimensionKeys = new ArrayList<DimensionKey>();

    ByteBuffer tmpBuffer = buffer.duplicate();

    while (tmpBuffer.position() < tmpBuffer.limit()) {
      String[] dimensionValues = new String[config.getDimensions().size()];

      for (int i = 0; i < config.getDimensions().size(); i++) {
        DimensionSpec dimensionSpec = config.getDimensions().get(i);
        Integer valueId = tmpBuffer.getInt();
        String dimensionValue = dictionary.getDimensionValue(dimensionSpec.getName(), valueId);
        dimensionValues[i] = dimensionValue;
      }

      dimensionKeys.add(new DimensionKey(dimensionValues));
    }

    return dimensionKeys;
  }
    @Override
    public void setup(Context context) throws IOException, InterruptedException {
      LOGGER.info("TopKRollupPhaseOneJob.TopKRollupPhaseOneMapper.setup()");
      Configuration configuration = context.getConfiguration();
      FileSystem fileSystem = FileSystem.get(configuration);
      Path configPath = new Path(configuration.get(TOPK_ROLLUP_PHASE1_CONFIG_PATH.toString()));
      try {
        starTreeConfig = StarTreeConfig.decode(fileSystem.open(configPath));
        config = TopKRollupPhaseOneConfig.fromStarTreeConfig(starTreeConfig);
        dimensionNames = config.getDimensionNames();
        keyWritable = new BytesWritable();
        valWritable = new BytesWritable();
        dimensionNameToIndexMapping = new HashMap<String, Integer>();
        for (int i = 0; i < dimensionNames.size(); i++) {
          dimensionNameToIndexMapping.put(dimensionNames.get(i), i);
        }

      } catch (Exception e) {
        throw new IOException(e);
      }
    }
  @Override
  public Map<DimensionKey, Integer> findMatchingKeys(DimensionKey dimensionKey) {
    Map<DimensionKey, Integer> matchingKeys = new HashMap<DimensionKey, Integer>();

    int[] translatedKey = dictionary.translate(config.getDimensions(), dimensionKey);
    int[] currentKey = new int[config.getDimensions().size()];

    int idx = 0;

    ByteBuffer tmpBuffer = buffer.duplicate();

    while (tmpBuffer.position() < tmpBuffer.limit()) {
      boolean matches = true;

      for (int i = 0; i < config.getDimensions().size(); i++) {
        Integer valueId = tmpBuffer.getInt();

        currentKey[i] = valueId;

        if (translatedKey[i] != valueId && translatedKey[i] != StarTreeConstants.STAR_VALUE) {
          matches = false;
        }
      }

      if (matches) {
        matchingKeys.put(dictionary.translate(config.getDimensions(), currentKey), idx);
      }

      idx++;
    }

    // If matching keys is empty, use record with least others!
    if (matchingKeys.isEmpty()) {
      idx = 0;
      tmpBuffer.rewind();

      int leastNumOthers = config.getDimensions().size() + 1;
      int leastOthersIdx = -1;
      int[] leastOthersKey = null;

      while (tmpBuffer.position() < tmpBuffer.limit()) {
        boolean matches = true;
        int currentNumOthers = 0;

        for (int i = 0; i < config.getDimensions().size(); i++) {
          Integer valueId = tmpBuffer.getInt();

          currentKey[i] = valueId;

          if (translatedKey[i] != valueId
              && valueId != StarTreeConstants.STAR_VALUE
              && valueId != StarTreeConstants.OTHER_VALUE) {
            matches = false;
          }

          if (valueId == StarTreeConstants.OTHER_VALUE) {
            currentNumOthers++;
          }
        }

        if (matches && currentNumOthers < leastNumOthers) {
          leastOthersKey = Arrays.copyOf(currentKey, currentKey.length);
          leastNumOthers = currentNumOthers;
          leastOthersIdx = idx;
        }

        idx++;
      }

      if (leastOthersKey == null) {
        throw new IllegalStateException(
            "Could not find alternative dimension combination for " + dimensionKey);
      }

      matchingKeys.put(
          dictionary.translate(config.getDimensions(), leastOthersKey), leastOthersIdx);
    }

    return matchingKeys;
  }