View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver.wal;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.EOFException;
24  import java.io.IOException;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.UUID;
28  
29  import org.apache.hadoop.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.util.Bytes;
32  import org.apache.hadoop.io.WritableComparable;
33  import org.apache.hadoop.io.WritableUtils;
34  
35  /**
36   * A Key for an entry in the change log.
37   *
38   * The log intermingles edits to many tables and rows, so each log entry
39   * identifies the appropriate table and row.  Within a table and row, they're
40   * also sorted.
41   *
42   * <p>Some Transactional edits (START, COMMIT, ABORT) will not have an
43   * associated row.
44   */
45  @InterfaceAudience.Private
46  public class HLogKey implements WritableComparable<HLogKey> {
47    // should be < 0 (@see #readFields(DataInput))
48    // version 2 supports HLog compression
49    enum Version {
50      UNVERSIONED(0),
51      // Initial number we put on HLogKey when we introduced versioning.
52      INITIAL(-1),
53      // Version -2 introduced a dictionary compression facility.  Only this
54      // dictionary-based compression is available in version -2.
55      COMPRESSED(-2);
56  
57      final int code;
58      static final Version[] byCode;
59      static {
60        byCode = Version.values();
61        for (int i = 0; i < byCode.length; i++) {
62          if (byCode[i].code != -1 * i) {
63            throw new AssertionError("Values in this enum should be descending by one");
64          }
65        }
66      }
67  
68      Version(int code) {
69        this.code = code;
70      }
71  
72      boolean atLeast(Version other) {
73        return code <= other.code;
74      }
75  
76      static Version fromCode(int code) {
77        return byCode[code * -1];
78      }
79    }
80  
81    private static final Version VERSION = Version.COMPRESSED;
82  
83    //  The encoded region name.
84    private byte [] encodedRegionName;
85    private byte [] tablename;
86    private long logSeqNum;
87    // Time at which this edit was written.
88    private long writeTime;
89  
90    private UUID clusterId;
91  
92    private CompressionContext compressionContext;
93  
94    /** Writable Constructor -- Do not use. */
95    public HLogKey() {
96      this(null, null, 0L, HConstants.LATEST_TIMESTAMP,
97          HConstants.DEFAULT_CLUSTER_ID);
98    }
99  
100   /**
101    * Create the log key!
102    * We maintain the tablename mainly for debugging purposes.
103    * A regionName is always a sub-table object.
104    *
105    * @param encodedRegionName Encoded name of the region as returned by
106    * <code>HRegionInfo#getEncodedNameAsBytes()</code>.
107    * @param tablename   - name of table
108    * @param logSeqNum   - log sequence number
109    * @param now Time at which this edit was written.
110    * @param clusterId of the cluster (used in Replication)
111    */
112   public HLogKey(final byte [] encodedRegionName, final byte [] tablename,
113       long logSeqNum, final long now, UUID clusterId) {
114     this.encodedRegionName = encodedRegionName;
115     this.tablename = tablename;
116     this.logSeqNum = logSeqNum;
117     this.writeTime = now;
118     this.clusterId = clusterId;
119   }
120 
121   /**
122    * @param compressionContext Compression context to use
123    */
124   public void setCompressionContext(CompressionContext compressionContext) {
125     this.compressionContext = compressionContext;
126   }
127 
128   /** @return encoded region name */
129   public byte [] getEncodedRegionName() {
130     return encodedRegionName;
131   }
132 
133   /** @return table name */
134   public byte [] getTablename() {
135     return tablename;
136   }
137 
138   /** @return log sequence number */
139   public long getLogSeqNum() {
140     return logSeqNum;
141   }
142 
143   void setLogSeqNum(long logSeqNum) {
144     this.logSeqNum = logSeqNum;
145   }
146 
147   /**
148    * @return the write time
149    */
150   public long getWriteTime() {
151     return this.writeTime;
152   }
153 
154   /**
155    * Get the id of the original cluster
156    * @return Cluster id.
157    */
158   public UUID getClusterId() {
159     return clusterId;
160   }
161 
162   /**
163    * Set the cluster id of this key
164    * @param clusterId
165    */
166   public void setClusterId(UUID clusterId) {
167     this.clusterId = clusterId;
168   }
169 
170   @Override
171   public String toString() {
172     return Bytes.toString(tablename) + "/" + Bytes.toString(encodedRegionName) + "/" +
173       logSeqNum;
174   }
175 
176   /**
177    * Produces a string map for this key. Useful for programmatic use and
178    * manipulation of the data stored in an HLogKey, for example, printing 
179    * as JSON.
180    * 
181    * @return a Map containing data from this key
182    */
183   public Map<String, Object> toStringMap() {
184     Map<String, Object> stringMap = new HashMap<String, Object>();
185     stringMap.put("table", Bytes.toStringBinary(tablename));
186     stringMap.put("region", Bytes.toStringBinary(encodedRegionName));
187     stringMap.put("sequence", logSeqNum);
188     return stringMap;
189   }
190 
191   @Override
192   public boolean equals(Object obj) {
193     if (this == obj) {
194       return true;
195     }
196     if (obj == null || getClass() != obj.getClass()) {
197       return false;
198     }
199     return compareTo((HLogKey)obj) == 0;
200   }
201 
202   @Override
203   public int hashCode() {
204     int result = Bytes.hashCode(this.encodedRegionName);
205     result ^= this.logSeqNum;
206     result ^= this.writeTime;
207     result ^= this.clusterId.hashCode();
208     return result;
209   }
210 
211   public int compareTo(HLogKey o) {
212     int result = Bytes.compareTo(this.encodedRegionName, o.encodedRegionName);
213     if (result == 0) {
214       if (this.logSeqNum < o.logSeqNum) {
215         result = -1;
216       } else if (this.logSeqNum > o.logSeqNum) {
217         result = 1;
218       }
219       if (result == 0) {
220         if (this.writeTime < o.writeTime) {
221           result = -1;
222         } else if (this.writeTime > o.writeTime) {
223           return 1;
224         }
225       }
226     }
227     // why isn't cluster id accounted for?
228     return result;
229   }
230 
231   /**
232    * Drop this instance's tablename byte array and instead
233    * hold a reference to the provided tablename. This is not
234    * meant to be a general purpose setter - it's only used
235    * to collapse references to conserve memory.
236    */
237   void internTableName(byte []tablename) {
238     // We should not use this as a setter - only to swap
239     // in a new reference to the same table name.
240     assert Bytes.equals(tablename, this.tablename);
241     this.tablename = tablename;
242   }
243 
244   /**
245    * Drop this instance's region name byte array and instead
246    * hold a reference to the provided region name. This is not
247    * meant to be a general purpose setter - it's only used
248    * to collapse references to conserve memory.
249    */
250   void internEncodedRegionName(byte []encodedRegionName) {
251     // We should not use this as a setter - only to swap
252     // in a new reference to the same table name.
253     assert Bytes.equals(this.encodedRegionName, encodedRegionName);
254     this.encodedRegionName = encodedRegionName;
255   }
256 
257   @Override
258   public void write(DataOutput out) throws IOException {
259     WritableUtils.writeVInt(out, VERSION.code);
260     if (compressionContext == null) {
261       Bytes.writeByteArray(out, this.encodedRegionName);
262       Bytes.writeByteArray(out, this.tablename);
263     } else {
264       Compressor.writeCompressed(this.encodedRegionName, 0,
265           this.encodedRegionName.length, out,
266           compressionContext.regionDict);
267       Compressor.writeCompressed(this.tablename, 0, this.tablename.length, out,
268           compressionContext.tableDict);
269     }
270     out.writeLong(this.logSeqNum);
271     out.writeLong(this.writeTime);
272     // avoid storing 16 bytes when replication is not enabled
273     if (this.clusterId == HConstants.DEFAULT_CLUSTER_ID) {
274       out.writeBoolean(false);
275     } else {
276       out.writeBoolean(true);
277       out.writeLong(this.clusterId.getMostSignificantBits());
278       out.writeLong(this.clusterId.getLeastSignificantBits());
279     }
280   }
281 
282   @Override
283   public void readFields(DataInput in) throws IOException {
284     Version version = Version.UNVERSIONED;
285     // HLogKey was not versioned in the beginning.
286     // In order to introduce it now, we make use of the fact
287     // that encodedRegionName was written with Bytes.writeByteArray,
288     // which encodes the array length as a vint which is >= 0.
289     // Hence if the vint is >= 0 we have an old version and the vint
290     // encodes the length of encodedRegionName.
291     // If < 0 we just read the version and the next vint is the length.
292     // @see Bytes#readByteArray(DataInput)
293     int len = WritableUtils.readVInt(in);
294     if (len < 0) {
295       // what we just read was the version
296       version = Version.fromCode(len);
297       // We only compress V2 of HLogkey.
298       // If compression is on, the length is handled by the dictionary
299       if (compressionContext == null || !version.atLeast(Version.COMPRESSED)) {
300         len = WritableUtils.readVInt(in);
301       }
302     }
303     if (compressionContext == null || !version.atLeast(Version.COMPRESSED)) {
304       this.encodedRegionName = new byte[len];
305       in.readFully(this.encodedRegionName);
306       this.tablename = Bytes.readByteArray(in);
307     } else {
308       this.encodedRegionName = Compressor.readCompressed(in, compressionContext.regionDict);
309       this.tablename = Compressor.readCompressed(in, compressionContext.tableDict);
310     }
311     
312     this.logSeqNum = in.readLong();
313     this.writeTime = in.readLong();
314     this.clusterId = HConstants.DEFAULT_CLUSTER_ID;
315     if (version.atLeast(Version.INITIAL)) {
316       if (in.readBoolean()) {
317         this.clusterId = new UUID(in.readLong(), in.readLong());
318       }
319     } else {
320       try {
321         // dummy read (former byte cluster id)
322         in.readByte();
323       } catch(EOFException e) {
324         // Means it's a very old key, just continue
325       }
326     }
327   }
328 }