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    private static final int VERSION = -1;
48  
49    //  The encoded region name.
50    private byte [] encodedRegionName;
51    private byte [] tablename;
52    private long logSeqNum;
53    // Time at which this edit was written.
54    private long writeTime;
55  
56    private UUID clusterId;
57  
58    /** Writable Consructor -- Do not use. */
59    public HLogKey() {
60      this(null, null, 0L, HConstants.LATEST_TIMESTAMP,
61          HConstants.DEFAULT_CLUSTER_ID);
62    }
63  
64    /**
65     * Create the log key!
66     * We maintain the tablename mainly for debugging purposes.
67     * A regionName is always a sub-table object.
68     *
69     * @param encodedRegionName Encoded name of the region as returned by
70     * <code>HRegionInfo#getEncodedNameAsBytes()</code>.
71     * @param tablename   - name of table
72     * @param logSeqNum   - log sequence number
73     * @param now Time at which this edit was written.
74     * @param clusterId of the cluster (used in Replication)
75     */
76    public HLogKey(final byte [] encodedRegionName, final byte [] tablename,
77        long logSeqNum, final long now, UUID clusterId) {
78      this.encodedRegionName = encodedRegionName;
79      this.tablename = tablename;
80      this.logSeqNum = logSeqNum;
81      this.writeTime = now;
82      this.clusterId = clusterId;
83    }
84  
85    /** @return encoded region name */
86    public byte [] getEncodedRegionName() {
87      return encodedRegionName;
88    }
89  
90    /** @return table name */
91    public byte [] getTablename() {
92      return tablename;
93    }
94  
95    /** @return log sequence number */
96    public long getLogSeqNum() {
97      return logSeqNum;
98    }
99  
100   void setLogSeqNum(long logSeqNum) {
101     this.logSeqNum = logSeqNum;
102   }
103 
104   /**
105    * @return the write time
106    */
107   public long getWriteTime() {
108     return this.writeTime;
109   }
110 
111   /**
112    * Get the id of the original cluster
113    * @return Cluster id.
114    */
115   public UUID getClusterId() {
116     return clusterId;
117   }
118 
119   /**
120    * Set the cluster id of this key
121    * @param clusterId
122    */
123   public void setClusterId(UUID clusterId) {
124     this.clusterId = clusterId;
125   }
126 
127   @Override
128   public String toString() {
129     return Bytes.toString(tablename) + "/" + Bytes.toString(encodedRegionName) + "/" +
130       logSeqNum;
131   }
132 
133   /**
134    * Produces a string map for this key. Useful for programmatic use and
135    * manipulation of the data stored in an HLogKey, for example, printing 
136    * as JSON.
137    * 
138    * @return a Map containing data from this key
139    */
140   public Map<String, Object> toStringMap() {
141     Map<String, Object> stringMap = new HashMap<String, Object>();
142     stringMap.put("table", Bytes.toStringBinary(tablename));
143     stringMap.put("region", Bytes.toStringBinary(encodedRegionName));
144     stringMap.put("sequence", logSeqNum);
145     return stringMap;
146   }
147 
148   @Override
149   public boolean equals(Object obj) {
150     if (this == obj) {
151       return true;
152     }
153     if (obj == null || getClass() != obj.getClass()) {
154       return false;
155     }
156     return compareTo((HLogKey)obj) == 0;
157   }
158 
159   @Override
160   public int hashCode() {
161     int result = Bytes.hashCode(this.encodedRegionName);
162     result ^= this.logSeqNum;
163     result ^= this.writeTime;
164     result ^= this.clusterId.hashCode();
165     return result;
166   }
167 
168   public int compareTo(HLogKey o) {
169     int result = Bytes.compareTo(this.encodedRegionName, o.encodedRegionName);
170     if (result == 0) {
171       if (this.logSeqNum < o.logSeqNum) {
172         result = -1;
173       } else if (this.logSeqNum > o.logSeqNum) {
174         result = 1;
175       }
176       if (result == 0) {
177         if (this.writeTime < o.writeTime) {
178           result = -1;
179         } else if (this.writeTime > o.writeTime) {
180           return 1;
181         }
182       }
183     }
184     // why isn't cluster id accounted for?
185     return result;
186   }
187 
188   /**
189    * Drop this instance's tablename byte array and instead
190    * hold a reference to the provided tablename. This is not
191    * meant to be a general purpose setter - it's only used
192    * to collapse references to conserve memory.
193    */
194   void internTableName(byte []tablename) {
195     // We should not use this as a setter - only to swap
196     // in a new reference to the same table name.
197     assert Bytes.equals(tablename, this.tablename);
198     this.tablename = tablename;
199   }
200 
201   /**
202    * Drop this instance's region name byte array and instead
203    * hold a reference to the provided region name. This is not
204    * meant to be a general purpose setter - it's only used
205    * to collapse references to conserve memory.
206    */
207   void internEncodedRegionName(byte []encodedRegionName) {
208     // We should not use this as a setter - only to swap
209     // in a new reference to the same table name.
210     assert Bytes.equals(this.encodedRegionName, encodedRegionName);
211     this.encodedRegionName = encodedRegionName;
212   }
213 
214   @Override
215   public void write(DataOutput out) throws IOException {
216     WritableUtils.writeVInt(out, VERSION);
217     Bytes.writeByteArray(out, this.encodedRegionName);
218     Bytes.writeByteArray(out, this.tablename);
219     out.writeLong(this.logSeqNum);
220     out.writeLong(this.writeTime);
221     // avoid storing 16 bytes when replication is not enabled
222     if (this.clusterId == HConstants.DEFAULT_CLUSTER_ID) {
223       out.writeBoolean(false);
224     } else {
225       out.writeBoolean(true);
226       out.writeLong(this.clusterId.getMostSignificantBits());
227       out.writeLong(this.clusterId.getLeastSignificantBits());
228     }
229   }
230 
231   @Override
232   public void readFields(DataInput in) throws IOException {
233     int version = 0;
234     // HLogKey was not versioned in the beginning.
235     // In order to introduce it now, we make use of the fact
236     // that encodedRegionName was written with Bytes.writeByteArray,
237     // which encodes the array length as a vint which is >= 0.
238     // Hence if the vint is >= 0 we have an old version and the vint
239     // encodes the length of encodedRegionName.
240     // If < 0 we just read the version and the next vint is the length.
241     // @see Bytes#readByteArray(DataInput)
242     int len = WritableUtils.readVInt(in);
243     if (len < 0) {
244       // what we just read was the version
245       version = len;
246       len = WritableUtils.readVInt(in);
247     }
248     this.encodedRegionName = new byte[len];
249     in.readFully(this.encodedRegionName);
250     this.tablename = Bytes.readByteArray(in);
251     this.logSeqNum = in.readLong();
252     this.writeTime = in.readLong();
253     this.clusterId = HConstants.DEFAULT_CLUSTER_ID;
254     if (version < 0) {
255       if (in.readBoolean()) {
256         this.clusterId = new UUID(in.readLong(), in.readLong());
257       }
258     } else {
259       try {
260         // dummy read (former byte cluster id)
261         in.readByte();
262       } catch(EOFException e) {
263         // Means it's a very old key, just continue
264       }
265     }
266   }
267 }