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;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Arrays;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.KeyValue.KVComparator;
30  import org.apache.hadoop.hbase.util.Bytes;
31  import org.apache.hadoop.hbase.util.JenkinsHash;
32  import org.apache.hadoop.hbase.util.MD5Hash;
33  import org.apache.hadoop.io.VersionedWritable;
34  import org.apache.hadoop.io.WritableComparable;
35  
36  /**
37   * HRegion information.
38   * Contains HRegion id, start and end keys, a reference to this
39   * HRegions' table descriptor, etc.
40   */
41  public class HRegionInfo extends VersionedWritable implements WritableComparable<HRegionInfo>{
42    private static final byte VERSION = 0;
43    private static final Log LOG = LogFactory.getLog(HRegionInfo.class);
44  
45    /**
46     * The new format for a region name contains its encodedName at the end.
47     * The encoded name also serves as the directory name for the region
48     * in the filesystem.
49     *
50     * New region name format:
51     *    &lt;tablename>,,&lt;startkey>,&lt;regionIdTimestamp>.&lt;encodedName>.
52     * where,
53     *    &lt;encodedName> is a hex version of the MD5 hash of
54     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
55     * 
56     * The old region name format:
57     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
58     * For region names in the old format, the encoded name is a 32-bit
59     * JenkinsHash integer value (in its decimal notation, string form). 
60     *<p>
61     * **NOTE**
62     *
63     * ROOT, the first META region, and regions created by an older
64     * version of HBase (0.20 or prior) will continue to use the
65     * old region name format.
66     */
67  
68    /** Separator used to demarcate the encodedName in a region name
69     * in the new format. See description on new format above. 
70     */ 
71    private static final int ENC_SEPARATOR = '.';
72    public  static final int MD5_HEX_LENGTH   = 32;
73  
74    /**
75     * Does region name contain its encoded name?
76     * @param regionName region name
77     * @return boolean indicating if this a new format region
78     *         name which contains its encoded name.
79     */
80    private static boolean hasEncodedName(final byte[] regionName) {
81      // check if region name ends in ENC_SEPARATOR
82      if ((regionName.length >= 1)
83          && (regionName[regionName.length - 1] == ENC_SEPARATOR)) {
84        // region name is new format. it contains the encoded name.
85        return true; 
86      }
87      return false;
88    }
89    
90    /**
91     * @param regionName
92     * @return the encodedName
93     */
94    public static String encodeRegionName(final byte [] regionName) {
95      String encodedName;
96      if (hasEncodedName(regionName)) {
97        // region is in new format:
98        // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
99        encodedName = Bytes.toString(regionName,
100           regionName.length - MD5_HEX_LENGTH - 1,
101           MD5_HEX_LENGTH);
102     } else {
103       // old format region name. ROOT and first META region also 
104       // use this format.EncodedName is the JenkinsHash value.
105       int hashVal = Math.abs(JenkinsHash.getInstance().hash(regionName,
106         regionName.length, 0));
107       encodedName = String.valueOf(hashVal);
108     }
109     return encodedName;
110   }
111 
112   /**
113    * Use logging.
114    * @param encodedRegionName The encoded regionname.
115    * @return <code>-ROOT-</code> if passed <code>70236052</code> or
116    * <code>.META.</code> if passed </code>1028785192</code> else returns
117    * <code>encodedRegionName</code>
118    */
119   public static String prettyPrint(final String encodedRegionName) {
120     if (encodedRegionName.equals("70236052")) {
121       return encodedRegionName + "/-ROOT-";
122     } else if (encodedRegionName.equals("1028785192")) {
123       return encodedRegionName + "/.META.";
124     }
125     return encodedRegionName;
126   }
127 
128   /** delimiter used between portions of a region name */
129   public static final int DELIMITER = ',';
130 
131   /** HRegionInfo for root region */
132   public static final HRegionInfo ROOT_REGIONINFO =
133     new HRegionInfo(0L, HTableDescriptor.ROOT_TABLEDESC);
134 
135   /** HRegionInfo for first meta region */
136   public static final HRegionInfo FIRST_META_REGIONINFO =
137     new HRegionInfo(1L, HTableDescriptor.META_TABLEDESC);
138 
139   private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
140   // This flag is in the parent of a split while the parent is still referenced
141   // by daughter regions.  We USED to set this flag when we disabled a table
142   // but now table state is kept up in zookeeper as of 0.90.0 HBase.
143   private boolean offLine = false;
144   private long regionId = -1;
145   private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY;
146   private String regionNameStr = "";
147   private boolean split = false;
148   private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
149   protected HTableDescriptor tableDesc = null;
150   private int hashCode = -1;
151   //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
152   public static final String NO_HASH = null;
153   private volatile String encodedName = NO_HASH;
154   private byte [] encodedNameAsBytes = null;
155 
156   private void setHashCode() {
157     int result = Arrays.hashCode(this.regionName);
158     result ^= this.regionId;
159     result ^= Arrays.hashCode(this.startKey);
160     result ^= Arrays.hashCode(this.endKey);
161     result ^= Boolean.valueOf(this.offLine).hashCode();
162     result ^= this.tableDesc.hashCode();
163     this.hashCode = result;
164   }
165 
166   /**
167    * Private constructor used constructing HRegionInfo for the catalog root and
168    * first meta regions
169    */
170   private HRegionInfo(long regionId, HTableDescriptor tableDesc) {
171     super();
172     this.regionId = regionId;
173     this.tableDesc = tableDesc;
174     
175     // Note: Root & First Meta regions names are still in old format   
176     this.regionName = createRegionName(tableDesc.getName(), null,
177                                        regionId, false);
178     this.regionNameStr = Bytes.toStringBinary(this.regionName);
179     setHashCode();
180   }
181 
182   /** Default constructor - creates empty object */
183   public HRegionInfo() {
184     super();
185     this.tableDesc = new HTableDescriptor();
186   }
187 
188   /**
189    * Construct HRegionInfo with explicit parameters
190    *
191    * @param tableDesc the table descriptor
192    * @param startKey first key in region
193    * @param endKey end of key range
194    * @throws IllegalArgumentException
195    */
196   public HRegionInfo(final HTableDescriptor tableDesc, final byte [] startKey,
197       final byte [] endKey)
198   throws IllegalArgumentException {
199     this(tableDesc, startKey, endKey, false);
200   }
201 
202   /**
203    * Construct HRegionInfo with explicit parameters
204    *
205    * @param tableDesc the table descriptor
206    * @param startKey first key in region
207    * @param endKey end of key range
208    * @param split true if this region has split and we have daughter regions
209    * regions that may or may not hold references to this region.
210    * @throws IllegalArgumentException
211    */
212   public HRegionInfo(HTableDescriptor tableDesc, final byte [] startKey,
213       final byte [] endKey, final boolean split)
214   throws IllegalArgumentException {
215     this(tableDesc, startKey, endKey, split, System.currentTimeMillis());
216   }
217 
218   /**
219    * Construct HRegionInfo with explicit parameters
220    *
221    * @param tableDesc the table descriptor
222    * @param startKey first key in region
223    * @param endKey end of key range
224    * @param split true if this region has split and we have daughter regions
225    * regions that may or may not hold references to this region.
226    * @param regionid Region id to use.
227    * @throws IllegalArgumentException
228    */
229   public HRegionInfo(HTableDescriptor tableDesc, final byte [] startKey,
230     final byte [] endKey, final boolean split, final long regionid)
231   throws IllegalArgumentException {
232     super();
233     if (tableDesc == null) {
234       throw new IllegalArgumentException("tableDesc cannot be null");
235     }
236     this.offLine = false;
237     this.regionId = regionid;
238     this.regionName = createRegionName(tableDesc.getName(), startKey, regionId, true);
239     this.regionNameStr = Bytes.toStringBinary(this.regionName);
240     this.split = split;
241     this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone();
242     this.startKey = startKey == null?
243       HConstants.EMPTY_START_ROW: startKey.clone();
244     this.tableDesc = tableDesc;
245     setHashCode();
246   }
247 
248   /**
249    * Costruct a copy of another HRegionInfo
250    *
251    * @param other
252    */
253   public HRegionInfo(HRegionInfo other) {
254     super();
255     this.endKey = other.getEndKey();
256     this.offLine = other.isOffline();
257     this.regionId = other.getRegionId();
258     this.regionName = other.getRegionName();
259     this.regionNameStr = Bytes.toStringBinary(this.regionName);
260     this.split = other.isSplit();
261     this.startKey = other.getStartKey();
262     this.tableDesc = other.getTableDesc();
263     this.hashCode = other.hashCode();
264     this.encodedName = other.getEncodedName();
265   }
266 
267   /**
268    * Make a region name of passed parameters.
269    * @param tableName
270    * @param startKey Can be null
271    * @param regionid Region id (Usually timestamp from when region was created).
272    * @param newFormat should we create the region name in the new format
273    *                  (such that it contains its encoded name?).
274    * @return Region name made of passed tableName, startKey and id
275    */
276   public static byte [] createRegionName(final byte [] tableName,
277       final byte [] startKey, final long regionid, boolean newFormat) {
278     return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
279   }
280 
281   /**
282    * Make a region name of passed parameters.
283    * @param tableName
284    * @param startKey Can be null
285    * @param id Region id (Usually timestamp from when region was created).
286    * @param newFormat should we create the region name in the new format
287    *                  (such that it contains its encoded name?).
288    * @return Region name made of passed tableName, startKey and id
289    */
290   public static byte [] createRegionName(final byte [] tableName,
291       final byte [] startKey, final String id, boolean newFormat) {
292     return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
293   }
294 
295   /**
296    * Make a region name of passed parameters.
297    * @param tableName
298    * @param startKey Can be null
299    * @param id Region id (Usually timestamp from when region was created).
300    * @param newFormat should we create the region name in the new format
301    *                  (such that it contains its encoded name?).
302    * @return Region name made of passed tableName, startKey and id
303    */
304   public static byte [] createRegionName(final byte [] tableName,
305       final byte [] startKey, final byte [] id, boolean newFormat) {
306     byte [] b = new byte [tableName.length + 2 + id.length +
307        (startKey == null? 0: startKey.length) +
308        (newFormat ? (MD5_HEX_LENGTH + 2) : 0)];
309 
310     int offset = tableName.length;
311     System.arraycopy(tableName, 0, b, 0, offset);
312     b[offset++] = DELIMITER;
313     if (startKey != null && startKey.length > 0) {
314       System.arraycopy(startKey, 0, b, offset, startKey.length);
315       offset += startKey.length;
316     }
317     b[offset++] = DELIMITER;
318     System.arraycopy(id, 0, b, offset, id.length);
319     offset += id.length;
320 
321     if (newFormat) {
322       //
323       // Encoded name should be built into the region name.
324       //
325       // Use the region name thus far (namely, <tablename>,<startKey>,<id>)
326       // to compute a MD5 hash to be used as the encoded name, and append
327       // it to the byte buffer.
328       //
329       String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
330       byte [] md5HashBytes = Bytes.toBytes(md5Hash);
331 
332       if (md5HashBytes.length != MD5_HEX_LENGTH) {
333         LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
334                   "; Got=" + md5HashBytes.length); 
335       }
336 
337       // now append the bytes '.<encodedName>.' to the end
338       b[offset++] = ENC_SEPARATOR;
339       System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
340       offset += MD5_HEX_LENGTH;
341       b[offset++] = ENC_SEPARATOR;
342     }
343     
344     return b;
345   }
346 
347   /**
348    * Gets the table name from the specified region name.
349    * @param regionName
350    * @return Table name.
351    */
352   public static byte [] getTableName(byte [] regionName) {
353     int offset = -1;
354     for (int i = 0; i < regionName.length; i++) {
355       if (regionName[i] == DELIMITER) {
356         offset = i;
357         break;
358       }
359     }
360     byte [] tableName = new byte[offset];
361     System.arraycopy(regionName, 0, tableName, 0, offset);
362     return tableName;
363   }
364 
365   /**
366    * Separate elements of a regionName.
367    * @param regionName
368    * @return Array of byte[] containing tableName, startKey and id
369    * @throws IOException
370    */
371   public static byte [][] parseRegionName(final byte [] regionName)
372   throws IOException {
373     int offset = -1;
374     for (int i = 0; i < regionName.length; i++) {
375       if (regionName[i] == DELIMITER) {
376         offset = i;
377         break;
378       }
379     }
380     if(offset == -1) throw new IOException("Invalid regionName format");
381     byte [] tableName = new byte[offset];
382     System.arraycopy(regionName, 0, tableName, 0, offset);
383     offset = -1;
384     for (int i = regionName.length - 1; i > 0; i--) {
385       if(regionName[i] == DELIMITER) {
386         offset = i;
387         break;
388       }
389     }
390     if(offset == -1) throw new IOException("Invalid regionName format");
391     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
392     if(offset != tableName.length + 1) {
393       startKey = new byte[offset - tableName.length - 1];
394       System.arraycopy(regionName, tableName.length + 1, startKey, 0,
395           offset - tableName.length - 1);
396     }
397     byte [] id = new byte[regionName.length - offset - 1];
398     System.arraycopy(regionName, offset + 1, id, 0,
399         regionName.length - offset - 1);
400     byte [][] elements = new byte[3][];
401     elements[0] = tableName;
402     elements[1] = startKey;
403     elements[2] = id;
404     return elements;
405   }
406 
407   /** @return the regionId */
408   public long getRegionId(){
409     return regionId;
410   }
411 
412   /**
413    * @return the regionName as an array of bytes.
414    * @see #getRegionNameAsString()
415    */
416   public byte [] getRegionName(){
417     return regionName;
418   }
419 
420   /**
421    * @return Region name as a String for use in logging, etc.
422    */
423   public String getRegionNameAsString() {
424     if (hasEncodedName(this.regionName)) {
425       // new format region names already have their encoded name.
426       return this.regionNameStr;
427     }
428 
429     // old format. regionNameStr doesn't have the region name.
430     //
431     //
432     return this.regionNameStr + "." + this.getEncodedName();
433   }
434 
435   /** @return the encoded region name */
436   public synchronized String getEncodedName() {
437     if (this.encodedName == NO_HASH) {
438       this.encodedName = encodeRegionName(this.regionName);
439     }
440     return this.encodedName;
441   }
442 
443   public synchronized byte [] getEncodedNameAsBytes() {
444     if (this.encodedNameAsBytes == null) {
445       this.encodedNameAsBytes = Bytes.toBytes(getEncodedName());
446     }
447     return this.encodedNameAsBytes;
448   }
449 
450   /** @return the startKey */
451   public byte [] getStartKey(){
452     return startKey;
453   }
454   
455   /** @return the endKey */
456   public byte [] getEndKey(){
457     return endKey;
458   }
459 
460   /**
461    * Returns true if the given inclusive range of rows is fully contained
462    * by this region. For example, if the region is foo,a,g and this is
463    * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
464    * ["b","z"] it will return false.
465    * @throws IllegalArgumentException if the range passed is invalid (ie end < start)
466    */
467   public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
468     if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
469       throw new IllegalArgumentException(
470       "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
471       " > " + Bytes.toStringBinary(rangeEndKey));
472     }
473 
474     boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
475     boolean lastKeyInRange =
476       Bytes.compareTo(rangeEndKey, endKey) < 0 ||
477       Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
478     return firstKeyInRange && lastKeyInRange;
479   }
480   
481   /**
482    * Return true if the given row falls in this region.
483    */
484   public boolean containsRow(byte[] row) {
485     return Bytes.compareTo(row, startKey) >= 0 &&
486       (Bytes.compareTo(row, endKey) < 0 ||
487        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
488   }
489 
490   /** @return the tableDesc */
491   public HTableDescriptor getTableDesc(){
492     return tableDesc;
493   }
494 
495   /**
496    * @param newDesc new table descriptor to use
497    */
498   public void setTableDesc(HTableDescriptor newDesc) {
499     this.tableDesc = newDesc;
500   }
501 
502   /** @return true if this is the root region */
503   public boolean isRootRegion() {
504     return this.tableDesc.isRootRegion();
505   }
506 
507   /** @return true if this is the meta table */
508   public boolean isMetaTable() {
509     return this.tableDesc.isMetaTable();
510   }
511 
512   /** @return true if this region is a meta region */
513   public boolean isMetaRegion() {
514     return this.tableDesc.isMetaRegion();
515   }
516 
517   /**
518    * @return True if has been split and has daughters.
519    */
520   public boolean isSplit() {
521     return this.split;
522   }
523 
524   /**
525    * @param split set split status
526    */
527   public void setSplit(boolean split) {
528     this.split = split;
529   }
530 
531   /**
532    * @return True if this region is offline.
533    */
534   public boolean isOffline() {
535     return this.offLine;
536   }
537 
538   /**
539    * The parent of a region split is offline while split daughters hold
540    * references to the parent. Offlined regions are closed.
541    * @param offLine Set online/offline status.
542    */
543   public void setOffline(boolean offLine) {
544     this.offLine = offLine;
545   }
546 
547 
548   /**
549    * @return True if this is a split parent region.
550    */
551   public boolean isSplitParent() {
552     if (!isSplit()) return false;
553     if (!isOffline()) {
554       LOG.warn("Region is split but NOT offline: " + getRegionNameAsString());
555     }
556     return true;
557   }
558 
559   /**
560    * @see java.lang.Object#toString()
561    */
562   @Override
563   public String toString() {
564     return "REGION => {" + HConstants.NAME + " => '" +
565       this.regionNameStr +
566       "', STARTKEY => '" +
567       Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
568       Bytes.toStringBinary(this.endKey) +
569       "', ENCODED => " + getEncodedName() + "," +
570       (isOffline()? " OFFLINE => true,": "") +
571       (isSplit()? " SPLIT => true,": "") +
572       " TABLE => {" + this.tableDesc.toString() + "}";
573   }
574 
575   /**
576    * @see java.lang.Object#equals(java.lang.Object)
577    */
578   @Override
579   public boolean equals(Object o) {
580     if (this == o) {
581       return true;
582     }
583     if (o == null) {
584       return false;
585     }
586     if (!(o instanceof HRegionInfo)) {
587       return false;
588     }
589     return this.compareTo((HRegionInfo)o) == 0;
590   }
591 
592   /**
593    * @see java.lang.Object#hashCode()
594    */
595   @Override
596   public int hashCode() {
597     return this.hashCode;
598   }
599 
600   /** @return the object version number */
601   @Override
602   public byte getVersion() {
603     return VERSION;
604   }
605 
606   //
607   // Writable
608   //
609 
610   @Override
611   public void write(DataOutput out) throws IOException {
612     super.write(out);
613     Bytes.writeByteArray(out, endKey);
614     out.writeBoolean(offLine);
615     out.writeLong(regionId);
616     Bytes.writeByteArray(out, regionName);
617     out.writeBoolean(split);
618     Bytes.writeByteArray(out, startKey);
619     tableDesc.write(out);
620     out.writeInt(hashCode);
621   }
622 
623   @Override
624   public void readFields(DataInput in) throws IOException {
625     super.readFields(in);
626     this.endKey = Bytes.readByteArray(in);
627     this.offLine = in.readBoolean();
628     this.regionId = in.readLong();
629     this.regionName = Bytes.readByteArray(in);
630     this.regionNameStr = Bytes.toStringBinary(this.regionName);
631     this.split = in.readBoolean();
632     this.startKey = Bytes.readByteArray(in);
633     this.tableDesc.readFields(in);
634     this.hashCode = in.readInt();
635   }
636 
637   //
638   // Comparable
639   //
640 
641   public int compareTo(HRegionInfo o) {
642     if (o == null) {
643       return 1;
644     }
645 
646     // Are regions of same table?
647     int result = Bytes.compareTo(this.tableDesc.getName(), o.tableDesc.getName());
648     if (result != 0) {
649       return result;
650     }
651 
652     // Compare start keys.
653     result = Bytes.compareTo(this.startKey, o.startKey);
654     if (result != 0) {
655       return result;
656     }
657 
658     // Compare end keys.
659     result = Bytes.compareTo(this.endKey, o.endKey);
660     if (result != 0) {
661       return result;
662     }
663     if (this.offLine == o.offLine)
664         return 0;
665     if (this.offLine == true) return -1;
666         
667     return 1;
668   }
669 
670   /**
671    * @return Comparator to use comparing {@link KeyValue}s.
672    */
673   public KVComparator getComparator() {
674     return isRootRegion()? KeyValue.ROOT_COMPARATOR: isMetaRegion()?
675       KeyValue.META_COMPARATOR: KeyValue.COMPARATOR;
676   }
677 }