All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.druid.indexing.seekablestream.SeekableStreamStartSequenceNumbers Maven / Gradle / Ivy

There is a newer version: 31.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.druid.indexing.seekablestream;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

/**
 * Represents the start sequenceNumber per partition of a sequence. This class keeps an additional set of
 * {@link #exclusivePartitions} for Kinesis indexing service in where each start offset can be either inclusive
 * or exclusive.
 */
public class SeekableStreamStartSequenceNumbers implements
    SeekableStreamSequenceNumbers
{
  public static final String TYPE = "start";

  // stream/topic
  private final String stream;
  // partitionId -> sequence number
  private final Map partitionSequenceNumberMap;
  private final Set exclusivePartitions;

  @JsonCreator
  public SeekableStreamStartSequenceNumbers(
      @JsonProperty("stream") final String stream,
      // kept for backward compatibility
      @JsonProperty("topic") final String topic,
      @JsonProperty("partitionSequenceNumberMap")
      final Map partitionSequenceNumberMap,
      // kept for backward compatibility
      @JsonProperty("partitionOffsetMap") final Map partitionOffsetMap,
      @JsonProperty("exclusivePartitions") @Nullable final Set exclusivePartitions
  )
  {
    this.stream = stream == null ? topic : stream;
    this.partitionSequenceNumberMap = partitionOffsetMap == null ? partitionSequenceNumberMap : partitionOffsetMap;

    Preconditions.checkNotNull(this.stream, "stream");
    Preconditions.checkNotNull(this.partitionSequenceNumberMap, "partitionIdToSequenceNumberMap");

    // exclusiveOffset can be null if this class is deserialized from metadata store. Note that only end offsets are
    // stored in metadata store.
    // The default is true because there was only Kafka indexing service before in which the end offset is always
    // exclusive.
    this.exclusivePartitions = exclusivePartitions == null ? Collections.emptySet() : exclusivePartitions;
  }

  public SeekableStreamStartSequenceNumbers(
      String stream,
      Map partitionSequenceNumberMap,
      Set exclusivePartitions
  )
  {
    this(stream, null, partitionSequenceNumberMap, null, exclusivePartitions);
  }

  @Override
  @JsonProperty
  public String getStream()
  {
    return stream;
  }

  /**
   * Identical to {@link #getStream()}. Here for backwards compatibility, so a serialized
   * SeekableStreamStartSequenceNumbers can be read by older Druid versions as a KafkaPartitions object.
   */
  @JsonProperty
  public String getTopic()
  {
    return stream;
  }

  @Override
  @JsonProperty
  public Map getPartitionSequenceNumberMap()
  {
    return partitionSequenceNumberMap;
  }

  /**
   * Identical to {@link #getPartitionSequenceNumberMap()} ()}. Here for backwards compatibility, so a serialized
   * SeekableStreamStartSequenceNumbers can be read by older Druid versions as a KafkaPartitions object.
   */
  @JsonProperty
  public Map getPartitionOffsetMap()
  {
    return partitionSequenceNumberMap;
  }

  @Override
  public SeekableStreamSequenceNumbers plus(
      SeekableStreamSequenceNumbers other
  )
  {
    validateSequenceNumbersBaseType(other);

    final SeekableStreamStartSequenceNumbers otherStart =
        (SeekableStreamStartSequenceNumbers) other;

    if (stream.equals(otherStart.stream)) {
      // Same stream, merge sequences.
      final Map newMap = new HashMap<>(partitionSequenceNumberMap);
      newMap.putAll(otherStart.partitionSequenceNumberMap);

      // A partition is exclusive if it's
      // 1) exclusive in "this" and it's not in "other"'s partitionSequenceNumberMap or
      // 2) exclusive in "other"
      final Set newExclusivePartitions = new HashSet<>();
      partitionSequenceNumberMap.forEach(
          (partitionId, sequenceOffset) -> {
            if (exclusivePartitions.contains(partitionId)
                && !otherStart.partitionSequenceNumberMap.containsKey(partitionId)) {
              newExclusivePartitions.add(partitionId);
            }
          }
      );
      newExclusivePartitions.addAll(otherStart.exclusivePartitions);

      return new SeekableStreamStartSequenceNumbers<>(
          stream,
          newMap,
          newExclusivePartitions
      );
    } else {
      // Different stream, prefer "other".
      return other;
    }
  }

  @Override
  public int compareTo(SeekableStreamSequenceNumbers other, Comparator comparator)
  {
    validateSequenceNumbersBaseType(other);

    final SeekableStreamStartSequenceNumbers otherStart =
        (SeekableStreamStartSequenceNumbers) other;

    if (stream.equals(otherStart.stream)) {
      //Same stream, compare the offset
      boolean res = false;
      for (Map.Entry entry : partitionSequenceNumberMap.entrySet()) {
        PartitionIdType partitionId = entry.getKey();
        SequenceOffsetType sequenceOffset = entry.getValue();
        if (otherStart.partitionSequenceNumberMap.get(partitionId) != null && comparator.compare(sequenceOffset, otherStart.partitionSequenceNumberMap.get(partitionId)) > 0) {
          res = true;
          break;
        }
      }
      if (res) {
        return 1;
      }
    }
    return 0;
  }

  @Override
  public SeekableStreamSequenceNumbers minus(
      SeekableStreamSequenceNumbers other
  )
  {
    validateSequenceNumbersBaseType(other);

    final SeekableStreamStartSequenceNumbers otherStart =
        (SeekableStreamStartSequenceNumbers) other;

    if (stream.equals(otherStart.stream)) {
      // Same stream, remove partitions present in "that" from "this"
      final Map newMap = new HashMap<>();
      final Set newExclusivePartitions = new HashSet<>();

      for (Entry entry : partitionSequenceNumberMap.entrySet()) {
        if (!otherStart.partitionSequenceNumberMap.containsKey(entry.getKey())) {
          newMap.put(entry.getKey(), entry.getValue());
          // A partition is exclusive if it's exclusive in "this" and not in "other"'s partitionSequenceNumberMap
          if (exclusivePartitions.contains(entry.getKey())) {
            newExclusivePartitions.add(entry.getKey());
          }
        }
      }

      return new SeekableStreamStartSequenceNumbers<>(
          stream,
          newMap,
          newExclusivePartitions
      );
    } else {
      // Different stream, prefer "this".
      return this;
    }
  }

  @JsonProperty
  public Set getExclusivePartitions()
  {
    return exclusivePartitions;
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    SeekableStreamStartSequenceNumbers that = (SeekableStreamStartSequenceNumbers) o;
    return Objects.equals(stream, that.stream) &&
           Objects.equals(partitionSequenceNumberMap, that.partitionSequenceNumberMap) &&
           Objects.equals(exclusivePartitions, that.exclusivePartitions);
  }

  @Override
  public int hashCode()
  {
    return Objects.hash(stream, partitionSequenceNumberMap, exclusivePartitions);
  }

  @Override
  public String toString()
  {
    return "SeekableStreamStartSequenceNumbers{" +
           "stream='" + stream + '\'' +
           ", partitionSequenceNumberMap=" + partitionSequenceNumberMap +
           ", exclusivePartitions=" + exclusivePartitions +
           '}';
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy