View Javadoc

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