001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.regionserver;
020
021import java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.DataInputStream;
024import java.io.DataOutputStream;
025import java.io.IOException;
026import java.util.concurrent.atomic.AtomicLong;
027
028import org.apache.hadoop.hbase.Cell;
029import org.apache.hadoop.hbase.CellUtil;
030import org.apache.hadoop.hbase.PrivateCellUtil;
031import org.apache.hadoop.hbase.io.TimeRange;
032import org.apache.yetus.audience.InterfaceAudience;
033
034import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
035import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
036import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
037import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
038
039/**
040 * Stores minimum and maximum timestamp values, it is [minimumTimestamp, maximumTimestamp] in
041 * interval notation.
042 * Use this class at write-time ONLY. Too much synchronization to use at read time
043 * Use {@link TimeRange} at read time instead of this. See toTimeRange() to make TimeRange to use.
044 * MemStores use this class to track minimum and maximum timestamps. The TimeRangeTracker made by
045 * the MemStore is passed to the StoreFile for it to write out as part a flush in the the file
046 * metadata. If no memstore involved -- i.e. a compaction -- then the StoreFile will calculate its
047 * own TimeRangeTracker as it appends. The StoreFile serialized TimeRangeTracker is used
048 * at read time via an instance of {@link TimeRange} to test if Cells fit the StoreFile TimeRange.
049 */
050@InterfaceAudience.Private
051public abstract class TimeRangeTracker {
052
053  public enum Type {
054    // thread-unsafe
055    NON_SYNC,
056    // thread-safe
057    SYNC
058  }
059
060  static final long INITIAL_MIN_TIMESTAMP = Long.MAX_VALUE;
061  static final long INITIAL_MAX_TIMESTAMP = -1L;
062
063  public static TimeRangeTracker create(Type type) {
064    switch (type) {
065      case NON_SYNC:
066        return new NonSyncTimeRangeTracker();
067      case SYNC:
068        return new SyncTimeRangeTracker();
069      default:
070        throw new UnsupportedOperationException("The type:" + type + " is unsupported");
071    }
072  }
073
074  public static TimeRangeTracker create(Type type, TimeRangeTracker trt) {
075    switch (type) {
076      case NON_SYNC:
077        return new NonSyncTimeRangeTracker(trt);
078      case SYNC:
079        return new SyncTimeRangeTracker(trt);
080      default:
081        throw new UnsupportedOperationException("The type:" + type + " is unsupported");
082    }
083  }
084
085  public static TimeRangeTracker create(Type type, long minimumTimestamp, long maximumTimestamp) {
086    switch (type) {
087      case NON_SYNC:
088        return new NonSyncTimeRangeTracker(minimumTimestamp, maximumTimestamp);
089      case SYNC:
090        return new SyncTimeRangeTracker(minimumTimestamp, maximumTimestamp);
091      default:
092        throw new UnsupportedOperationException("The type:" + type + " is unsupported");
093    }
094  }
095
096  protected abstract void setMax(long ts);
097  protected abstract void setMin(long ts);
098  protected abstract boolean compareAndSetMin(long expect, long update);
099  protected abstract boolean compareAndSetMax(long expect, long update);
100  /**
101   * Update the current TimestampRange to include the timestamp from <code>cell</code>.
102   * If the Key is of type DeleteColumn or DeleteFamily, it includes the
103   * entire time range from 0 to timestamp of the key.
104   * @param cell the Cell to include
105   */
106  public void includeTimestamp(final Cell cell) {
107    includeTimestamp(cell.getTimestamp());
108    if (PrivateCellUtil.isDeleteColumnOrFamily(cell)) {
109      includeTimestamp(0);
110    }
111  }
112
113  /**
114   * If required, update the current TimestampRange to include timestamp
115   * @param timestamp the timestamp value to include
116   */
117  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MT_CORRECTNESS",
118      justification="Intentional")
119  void includeTimestamp(final long timestamp) {
120    long initialMinTimestamp = getMin();
121    if (timestamp < initialMinTimestamp) {
122      long curMinTimestamp = initialMinTimestamp;
123      while (timestamp < curMinTimestamp) {
124        if (!compareAndSetMin(curMinTimestamp, timestamp)) {
125          curMinTimestamp = getMin();
126        } else {
127          // successfully set minimumTimestamp, break.
128          break;
129        }
130      }
131
132      // When it reaches here, there are two possibilities:
133      //  1). timestamp >= curMinTimestamp, someone already sets the minimumTimestamp. In this case,
134      //      it still needs to check if initialMinTimestamp == INITIAL_MIN_TIMESTAMP to see
135      //      if it needs to update minimumTimestamp. Someone may already set both
136      //      minimumTimestamp/minimumTimestamp to the same value(curMinTimestamp),
137      //      need to check if maximumTimestamp needs to be updated.
138      //  2). timestamp < curMinTimestamp, it sets the minimumTimestamp successfully.
139      //      In this case,it still needs to check if initialMinTimestamp == INITIAL_MIN_TIMESTAMP
140      //      to see if it needs to set maximumTimestamp.
141      if (initialMinTimestamp != INITIAL_MIN_TIMESTAMP) {
142        // Someone already sets minimumTimestamp and timestamp is less than minimumTimestamp.
143        // In this case, no need to set maximumTimestamp as it will be set to at least
144        // initialMinTimestamp.
145        return;
146      }
147    }
148
149    long curMaxTimestamp = getMax();
150
151    if (timestamp > curMaxTimestamp) {
152      while (timestamp > curMaxTimestamp) {
153        if (!compareAndSetMax(curMaxTimestamp, timestamp)) {
154          curMaxTimestamp = getMax();
155        } else {
156          // successfully set maximumTimestamp, break
157          break;
158        }
159      }
160    }
161  }
162
163  /**
164   * Check if the range has ANY overlap with TimeRange
165   * @param tr TimeRange, it expects [minStamp, maxStamp)
166   * @return True if there is overlap, false otherwise
167   */
168  public boolean includesTimeRange(final TimeRange tr) {
169    return (getMin() < tr.getMax() && getMax() >= tr.getMin());
170  }
171
172  /**
173   * @return the minimumTimestamp
174   */
175  public abstract long getMin();
176
177  /**
178   * @return the maximumTimestamp
179   */
180  public abstract long getMax();
181
182  @Override
183  public String toString() {
184    return "[" + getMin() + "," + getMax() + "]";
185  }
186
187  /**
188   * @param data the serialization data. It can't be null!
189   * @return An instance of NonSyncTimeRangeTracker filled w/ the content of serialized
190   * NonSyncTimeRangeTracker in <code>timeRangeTrackerBytes</code>.
191   * @throws IOException
192   */
193  public static TimeRangeTracker parseFrom(final byte[] data) throws IOException {
194    return parseFrom(data, Type.NON_SYNC);
195  }
196
197  public static TimeRangeTracker parseFrom(final byte[] data, Type type) throws IOException {
198    Preconditions.checkNotNull(data, "input data is null!");
199    if (ProtobufUtil.isPBMagicPrefix(data)) {
200      int pblen = ProtobufUtil.lengthOfPBMagic();
201      HBaseProtos.TimeRangeTracker.Builder builder = HBaseProtos.TimeRangeTracker.newBuilder();
202      ProtobufUtil.mergeFrom(builder, data, pblen, data.length - pblen);
203      return TimeRangeTracker.create(type, builder.getFrom(), builder.getTo());
204    } else {
205      try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(data))) {
206        return TimeRangeTracker.create(type, in.readLong(), in.readLong());
207      }
208    }
209  }
210
211  /**
212   * This method used to serialize TimeRangeTracker (TRT) by protobuf while this breaks the
213   * forward compatibility on HFile.(See HBASE-21008) In previous hbase version ( < 2.0.0 ) we use
214   * DataOutput to serialize TRT, these old versions don't have capability to deserialize TRT
215   * which is serialized by protobuf. So we need to revert the change of serializing
216   * TimeRangeTracker back to DataOutput. For more information, please check HBASE-21012.
217   * @param tracker TimeRangeTracker needed to be serialized.
218   * @return byte array filled with serialized TimeRangeTracker.
219   * @throws IOException if something goes wrong in writeLong.
220   */
221  public static byte[] toByteArray(TimeRangeTracker tracker) throws IOException {
222    try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
223      try (DataOutputStream dos = new DataOutputStream(bos)) {
224        dos.writeLong(tracker.getMin());
225        dos.writeLong(tracker.getMax());
226        return bos.toByteArray();
227      }
228    }
229  }
230
231  /**
232   * @return Make a TimeRange from current state of <code>this</code>.
233   */
234  TimeRange toTimeRange() {
235    long min = getMin();
236    long max = getMax();
237    // Initial TimeRangeTracker timestamps are the opposite of what you want for a TimeRange. Fix!
238    if (min == INITIAL_MIN_TIMESTAMP) {
239      min = TimeRange.INITIAL_MIN_TIMESTAMP;
240    }
241    if (max == INITIAL_MAX_TIMESTAMP) {
242      max = TimeRange.INITIAL_MAX_TIMESTAMP;
243    }
244    return new TimeRange(min, max);
245  }
246
247  @VisibleForTesting
248  //In order to estimate the heap size, this inner class need to be accessible to TestHeapSize.
249  public static class NonSyncTimeRangeTracker extends TimeRangeTracker {
250    private long minimumTimestamp = INITIAL_MIN_TIMESTAMP;
251    private long maximumTimestamp = INITIAL_MAX_TIMESTAMP;
252
253    NonSyncTimeRangeTracker() {
254    }
255
256    NonSyncTimeRangeTracker(final TimeRangeTracker trt) {
257      this.minimumTimestamp = trt.getMin();
258      this.maximumTimestamp = trt.getMax();
259    }
260
261    NonSyncTimeRangeTracker(long minimumTimestamp, long maximumTimestamp) {
262      this.minimumTimestamp = minimumTimestamp;
263      this.maximumTimestamp = maximumTimestamp;
264    }
265
266    @Override
267    protected void setMax(long ts) {
268      maximumTimestamp = ts;
269    }
270
271    @Override
272    protected void setMin(long ts) {
273      minimumTimestamp = ts;
274    }
275
276    @Override
277    protected boolean compareAndSetMin(long expect, long update) {
278      if (minimumTimestamp != expect) {
279        return false;
280      }
281      minimumTimestamp = update;
282      return true;
283    }
284
285    @Override
286    protected boolean compareAndSetMax(long expect, long update) {
287      if (maximumTimestamp != expect) {
288        return false;
289      }
290      maximumTimestamp = update;
291      return true;
292    }
293
294    @Override
295    public long getMin() {
296      return minimumTimestamp;
297    }
298
299    @Override
300    public long getMax() {
301      return maximumTimestamp;
302    }
303  }
304
305  @VisibleForTesting
306  //In order to estimate the heap size, this inner class need to be accessible to TestHeapSize.
307  public static class SyncTimeRangeTracker extends TimeRangeTracker {
308    private final AtomicLong minimumTimestamp = new AtomicLong(INITIAL_MIN_TIMESTAMP);
309    private final AtomicLong maximumTimestamp = new AtomicLong(INITIAL_MAX_TIMESTAMP);
310
311    private SyncTimeRangeTracker() {
312    }
313
314    SyncTimeRangeTracker(final TimeRangeTracker trt) {
315      this.minimumTimestamp.set(trt.getMin());
316      this.maximumTimestamp.set(trt.getMax());
317    }
318
319    SyncTimeRangeTracker(long minimumTimestamp, long maximumTimestamp) {
320      this.minimumTimestamp.set(minimumTimestamp);
321      this.maximumTimestamp.set(maximumTimestamp);
322    }
323
324    @Override
325    protected void setMax(long ts) {
326      maximumTimestamp.set(ts);
327    }
328
329    @Override
330    protected void setMin(long ts) {
331      minimumTimestamp.set(ts);
332    }
333
334    @Override
335    protected boolean compareAndSetMin(long expect, long update) {
336      return minimumTimestamp.compareAndSet(expect, update);
337    }
338
339    @Override
340    protected boolean compareAndSetMax(long expect, long update) {
341      return maximumTimestamp.compareAndSet(expect, update);
342    }
343
344    @Override
345    public long getMin() {
346      return minimumTimestamp.get();
347    }
348
349    @Override
350    public long getMax() {
351      return maximumTimestamp.get();
352    }
353  }
354}