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  
20  package org.apache.hadoop.hbase.client;
21  
22  import org.apache.hadoop.classification.InterfaceAudience;
23  import org.apache.hadoop.classification.InterfaceStability;
24  import org.apache.hadoop.hbase.HConstants;
25  import org.apache.hadoop.hbase.filter.Filter;
26  import org.apache.hadoop.hbase.filter.IncompatibleFilterException;
27  import org.apache.hadoop.hbase.io.TimeRange;
28  import org.apache.hadoop.hbase.util.Bytes;
29  
30  import java.io.IOException;
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.NavigableSet;
36  import java.util.TreeMap;
37  import java.util.TreeSet;
38  
39  /**
40   * Used to perform Scan operations.
41   * <p>
42   * All operations are identical to {@link Get} with the exception of
43   * instantiation.  Rather than specifying a single row, an optional startRow
44   * and stopRow may be defined.  If rows are not specified, the Scanner will
45   * iterate over all rows.
46   * <p>
47   * To scan everything for each row, instantiate a Scan object.
48   * <p>
49   * To modify scanner caching for just this scan, use {@link #setCaching(int) setCaching}.
50   * If caching is NOT set, we will use the caching value of the hosting {@link HTable}.  See
51   * {@link HTable#setScannerCaching(int)}. In addition to row caching, it is possible to specify a
52   * maximum result size, using {@link #setMaxResultSize(long)}. When both are used,
53   * single server requests are limited by either number of rows or maximum result size, whichever
54   * limit comes first.
55   * <p>
56   * To further define the scope of what to get when scanning, perform additional
57   * methods as outlined below.
58   * <p>
59   * To get all columns from specific families, execute {@link #addFamily(byte[]) addFamily}
60   * for each family to retrieve.
61   * <p>
62   * To get specific columns, execute {@link #addColumn(byte[], byte[]) addColumn}
63   * for each column to retrieve.
64   * <p>
65   * To only retrieve columns within a specific range of version timestamps,
66   * execute {@link #setTimeRange(long, long) setTimeRange}.
67   * <p>
68   * To only retrieve columns with a specific timestamp, execute
69   * {@link #setTimeStamp(long) setTimestamp}.
70   * <p>
71   * To limit the number of versions of each column to be returned, execute
72   * {@link #setMaxVersions(int) setMaxVersions}.
73   * <p>
74   * To limit the maximum number of values returned for each call to next(),
75   * execute {@link #setBatch(int) setBatch}.
76   * <p>
77   * To add a filter, execute {@link #setFilter(org.apache.hadoop.hbase.filter.Filter) setFilter}.
78   * <p>
79   * Expert: To explicitly disable server-side block caching for this scan,
80   * execute {@link #setCacheBlocks(boolean)}.
81   */
82  @InterfaceAudience.Public
83  @InterfaceStability.Stable
84  public class Scan extends OperationWithAttributes {
85    private static final String RAW_ATTR = "_raw_";
86    private static final String ISOLATION_LEVEL = "_isolationlevel_";
87  
88    /**
89     * EXPERT ONLY.
90     * An integer (not long) indicating to the scanner logic how many times we attempt to retrieve the
91     * next KV before we schedule a reseek.
92     * The right value depends on the size of the average KV. A reseek is more efficient when
93     * it can skip 5-10 KVs or 512B-1KB, or when the next KV is likely found in another HFile block.
94     * Setting this only has any effect when columns were added with
95     * {@link #addColumn(byte[], byte[])}
96     * <pre>{@code
97     * Scan s = new Scan(...);
98     * s.addColumn(...);
99     * s.setAttribute(Scan.HINT_LOOKAHEAD, Bytes.toBytes(2));
100    * }</pre>
101    * Default is 0 (always reseek).
102    */
103   public static final String HINT_LOOKAHEAD = "_look_ahead_";
104 
105   private byte [] startRow = HConstants.EMPTY_START_ROW;
106   private byte [] stopRow  = HConstants.EMPTY_END_ROW;
107   private int maxVersions = 1;
108   private int batch = -1;
109 
110   private int storeLimit = -1;
111   private int storeOffset = 0;
112   private boolean getScan;
113 
114   // If application wants to collect scan metrics, it needs to
115   // call scan.setAttribute(SCAN_ATTRIBUTES_ENABLE, Bytes.toBytes(Boolean.TRUE))
116   static public final String SCAN_ATTRIBUTES_METRICS_ENABLE = "scan.attributes.metrics.enable";
117   static public final String SCAN_ATTRIBUTES_METRICS_DATA = "scan.attributes.metrics.data";
118   
119   // If an application wants to use multiple scans over different tables each scan must
120   // define this attribute with the appropriate table name by calling
121   // scan.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, Bytes.toBytes(tableName))
122   static public final String SCAN_ATTRIBUTES_TABLE_NAME = "scan.attributes.table.name";
123   
124   /*
125    * -1 means no caching
126    */
127   private int caching = -1;
128   private long maxResultSize = -1;
129   private boolean cacheBlocks = true;
130   private Filter filter = null;
131   private TimeRange tr = new TimeRange();
132   private Map<byte [], NavigableSet<byte []>> familyMap =
133     new TreeMap<byte [], NavigableSet<byte []>>(Bytes.BYTES_COMPARATOR);
134   private Boolean loadColumnFamiliesOnDemand = null;
135 
136   /**
137    * Set it true for small scan to get better performance
138    * 
139    * Small scan should use pread and big scan can use seek + read
140    * 
141    * seek + read is fast but can cause two problem (1) resource contention (2)
142    * cause too much network io
143    * 
144    * [89-fb] Using pread for non-compaction read request
145    * https://issues.apache.org/jira/browse/HBASE-7266
146    * 
147    * On the other hand, if setting it true, we would do
148    * openScanner,next,closeScanner in one RPC call. It means the better
149    * performance for small scan. [HBASE-9488].
150    * 
151    * Generally, if the scan range is within one data block(64KB), it could be
152    * considered as a small scan.
153    */
154   private boolean small = false;
155 
156   /**
157    * Create a Scan operation across all rows.
158    */
159   public Scan() {}
160 
161   public Scan(byte [] startRow, Filter filter) {
162     this(startRow);
163     this.filter = filter;
164   }
165 
166   /**
167    * Create a Scan operation starting at the specified row.
168    * <p>
169    * If the specified row does not exist, the Scanner will start from the
170    * next closest row after the specified row.
171    * @param startRow row to start scanner at or after
172    */
173   public Scan(byte [] startRow) {
174     this.startRow = startRow;
175   }
176 
177   /**
178    * Create a Scan operation for the range of rows specified.
179    * @param startRow row to start scanner at or after (inclusive)
180    * @param stopRow row to stop scanner before (exclusive)
181    */
182   public Scan(byte [] startRow, byte [] stopRow) {
183     this.startRow = startRow;
184     this.stopRow = stopRow;
185     //if the startRow and stopRow both are empty, it is not a Get
186     this.getScan = isStartRowAndEqualsStopRow();
187   }
188 
189   /**
190    * Creates a new instance of this class while copying all values.
191    *
192    * @param scan  The scan instance to copy from.
193    * @throws IOException When copying the values fails.
194    */
195   public Scan(Scan scan) throws IOException {
196     startRow = scan.getStartRow();
197     stopRow  = scan.getStopRow();
198     maxVersions = scan.getMaxVersions();
199     batch = scan.getBatch();
200     storeLimit = scan.getMaxResultsPerColumnFamily();
201     storeOffset = scan.getRowOffsetPerColumnFamily();
202     caching = scan.getCaching();
203     maxResultSize = scan.getMaxResultSize();
204     cacheBlocks = scan.getCacheBlocks();
205     getScan = scan.isGetScan();
206     filter = scan.getFilter(); // clone?
207     loadColumnFamiliesOnDemand = scan.getLoadColumnFamiliesOnDemandValue();
208     TimeRange ctr = scan.getTimeRange();
209     tr = new TimeRange(ctr.getMin(), ctr.getMax());
210     Map<byte[], NavigableSet<byte[]>> fams = scan.getFamilyMap();
211     for (Map.Entry<byte[],NavigableSet<byte[]>> entry : fams.entrySet()) {
212       byte [] fam = entry.getKey();
213       NavigableSet<byte[]> cols = entry.getValue();
214       if (cols != null && cols.size() > 0) {
215         for (byte[] col : cols) {
216           addColumn(fam, col);
217         }
218       } else {
219         addFamily(fam);
220       }
221     }
222     for (Map.Entry<String, byte[]> attr : scan.getAttributesMap().entrySet()) {
223       setAttribute(attr.getKey(), attr.getValue());
224     }
225   }
226 
227   /**
228    * Builds a scan object with the same specs as get.
229    * @param get get to model scan after
230    */
231   public Scan(Get get) {
232     this.startRow = get.getRow();
233     this.stopRow = get.getRow();
234     this.filter = get.getFilter();
235     this.cacheBlocks = get.getCacheBlocks();
236     this.maxVersions = get.getMaxVersions();
237     this.storeLimit = get.getMaxResultsPerColumnFamily();
238     this.storeOffset = get.getRowOffsetPerColumnFamily();
239     this.tr = get.getTimeRange();
240     this.familyMap = get.getFamilyMap();
241     this.getScan = true;
242   }
243 
244   public boolean isGetScan() {
245     return this.getScan || isStartRowAndEqualsStopRow();
246   }
247 
248   private boolean isStartRowAndEqualsStopRow() {
249     return this.startRow != null && this.startRow.length > 0 &&
250         Bytes.equals(this.startRow, this.stopRow);
251   }
252   /**
253    * Get all columns from the specified family.
254    * <p>
255    * Overrides previous calls to addColumn for this family.
256    * @param family family name
257    * @return this
258    */
259   public Scan addFamily(byte [] family) {
260     familyMap.remove(family);
261     familyMap.put(family, null);
262     return this;
263   }
264 
265   /**
266    * Get the column from the specified family with the specified qualifier.
267    * <p>
268    * Overrides previous calls to addFamily for this family.
269    * @param family family name
270    * @param qualifier column qualifier
271    * @return this
272    */
273   public Scan addColumn(byte [] family, byte [] qualifier) {
274     NavigableSet<byte []> set = familyMap.get(family);
275     if(set == null) {
276       set = new TreeSet<byte []>(Bytes.BYTES_COMPARATOR);
277     }
278     if (qualifier == null) {
279       qualifier = HConstants.EMPTY_BYTE_ARRAY;
280     }
281     set.add(qualifier);
282     familyMap.put(family, set);
283     return this;
284   }
285 
286   /**
287    * Get versions of columns only within the specified timestamp range,
288    * [minStamp, maxStamp).  Note, default maximum versions to return is 1.  If
289    * your time range spans more than one version and you want all versions
290    * returned, up the number of versions beyond the defaut.
291    * @param minStamp minimum timestamp value, inclusive
292    * @param maxStamp maximum timestamp value, exclusive
293    * @throws IOException if invalid time range
294    * @see #setMaxVersions()
295    * @see #setMaxVersions(int)
296    * @return this
297    */
298   public Scan setTimeRange(long minStamp, long maxStamp)
299   throws IOException {
300     tr = new TimeRange(minStamp, maxStamp);
301     return this;
302   }
303 
304   /**
305    * Get versions of columns with the specified timestamp. Note, default maximum
306    * versions to return is 1.  If your time range spans more than one version
307    * and you want all versions returned, up the number of versions beyond the
308    * defaut.
309    * @param timestamp version timestamp
310    * @see #setMaxVersions()
311    * @see #setMaxVersions(int)
312    * @return this
313    */
314   public Scan setTimeStamp(long timestamp) {
315     try {
316       tr = new TimeRange(timestamp, timestamp+1);
317     } catch(IOException e) {
318       // Will never happen
319     }
320     return this;
321   }
322 
323   /**
324    * Set the start row of the scan.
325    * @param startRow row to start scan on (inclusive)
326    * Note: In order to make startRow exclusive add a trailing 0 byte
327    * @return this
328    */
329   public Scan setStartRow(byte [] startRow) {
330     this.startRow = startRow;
331     return this;
332   }
333 
334   /**
335    * Set the stop row.
336    * @param stopRow row to end at (exclusive)
337    * Note: In order to make stopRow inclusive add a trailing 0 byte
338    * @return this
339    */
340   public Scan setStopRow(byte [] stopRow) {
341     this.stopRow = stopRow;
342     return this;
343   }
344 
345   /**
346    * Get all available versions.
347    * @return this
348    */
349   public Scan setMaxVersions() {
350     this.maxVersions = Integer.MAX_VALUE;
351     return this;
352   }
353 
354   /**
355    * Get up to the specified number of versions of each column.
356    * @param maxVersions maximum versions for each column
357    * @return this
358    */
359   public Scan setMaxVersions(int maxVersions) {
360     this.maxVersions = maxVersions;
361     return this;
362   }
363 
364   /**
365    * Set the maximum number of values to return for each call to next()
366    * @param batch the maximum number of values
367    */
368   public void setBatch(int batch) {
369     if (this.hasFilter() && this.filter.hasFilterRow()) {
370       throw new IncompatibleFilterException(
371         "Cannot set batch on a scan using a filter" +
372         " that returns true for filter.hasFilterRow");
373     }
374     this.batch = batch;
375   }
376 
377   /**
378    * Set the maximum number of values to return per row per Column Family
379    * @param limit the maximum number of values returned / row / CF
380    */
381   public void setMaxResultsPerColumnFamily(int limit) {
382     this.storeLimit = limit;
383   }
384 
385   /**
386    * Set offset for the row per Column Family.
387    * @param offset is the number of kvs that will be skipped.
388    */
389   public void setRowOffsetPerColumnFamily(int offset) {
390     this.storeOffset = offset;
391   }
392 
393   /**
394    * Set the number of rows for caching that will be passed to scanners.
395    * If not set, the default setting from {@link HTable#getScannerCaching()} will apply.
396    * Higher caching values will enable faster scanners but will use more memory.
397    * @param caching the number of rows for caching
398    */
399   public void setCaching(int caching) {
400     this.caching = caching;
401   }
402 
403   /**
404    * @return the maximum result size in bytes. See {@link #setMaxResultSize(long)}
405    */
406   public long getMaxResultSize() {
407     return maxResultSize;
408   }
409 
410   /**
411    * Set the maximum result size. The default is -1; this means that no specific
412    * maximum result size will be set for this scan, and the global configured
413    * value will be used instead. (Defaults to unlimited).
414    *
415    * @param maxResultSize The maximum result size in bytes.
416    */
417   public void setMaxResultSize(long maxResultSize) {
418     this.maxResultSize = maxResultSize;
419   }
420 
421   /**
422    * Apply the specified server-side filter when performing the Scan.
423    * @param filter filter to run on the server
424    * @return this
425    */
426   public Scan setFilter(Filter filter) {
427     this.filter = filter;
428     return this;
429   }
430 
431   /**
432    * Setting the familyMap
433    * @param familyMap map of family to qualifier
434    * @return this
435    */
436   public Scan setFamilyMap(Map<byte [], NavigableSet<byte []>> familyMap) {
437     this.familyMap = familyMap;
438     return this;
439   }
440 
441   /**
442    * Getting the familyMap
443    * @return familyMap
444    */
445   public Map<byte [], NavigableSet<byte []>> getFamilyMap() {
446     return this.familyMap;
447   }
448 
449   /**
450    * @return the number of families in familyMap
451    */
452   public int numFamilies() {
453     if(hasFamilies()) {
454       return this.familyMap.size();
455     }
456     return 0;
457   }
458 
459   /**
460    * @return true if familyMap is non empty, false otherwise
461    */
462   public boolean hasFamilies() {
463     return !this.familyMap.isEmpty();
464   }
465 
466   /**
467    * @return the keys of the familyMap
468    */
469   public byte[][] getFamilies() {
470     if(hasFamilies()) {
471       return this.familyMap.keySet().toArray(new byte[0][0]);
472     }
473     return null;
474   }
475 
476   /**
477    * @return the startrow
478    */
479   public byte [] getStartRow() {
480     return this.startRow;
481   }
482 
483   /**
484    * @return the stoprow
485    */
486   public byte [] getStopRow() {
487     return this.stopRow;
488   }
489 
490   /**
491    * @return the max number of versions to fetch
492    */
493   public int getMaxVersions() {
494     return this.maxVersions;
495   }
496 
497   /**
498    * @return maximum number of values to return for a single call to next()
499    */
500   public int getBatch() {
501     return this.batch;
502   }
503 
504   /**
505    * @return maximum number of values to return per row per CF
506    */
507   public int getMaxResultsPerColumnFamily() {
508     return this.storeLimit;
509   }
510 
511   /**
512    * Method for retrieving the scan's offset per row per column
513    * family (#kvs to be skipped)
514    * @return row offset
515    */
516   public int getRowOffsetPerColumnFamily() {
517     return this.storeOffset;
518   }
519 
520   /**
521    * @return caching the number of rows fetched when calling next on a scanner
522    */
523   public int getCaching() {
524     return this.caching;
525   }
526 
527   /**
528    * @return TimeRange
529    */
530   public TimeRange getTimeRange() {
531     return this.tr;
532   }
533 
534   /**
535    * @return RowFilter
536    */
537   public Filter getFilter() {
538     return filter;
539   }
540 
541   /**
542    * @return true is a filter has been specified, false if not
543    */
544   public boolean hasFilter() {
545     return filter != null;
546   }
547 
548   /**
549    * Set whether blocks should be cached for this Scan.
550    * <p>
551    * This is true by default.  When true, default settings of the table and
552    * family are used (this will never override caching blocks if the block
553    * cache is disabled for that family or entirely).
554    *
555    * @param cacheBlocks if false, default settings are overridden and blocks
556    * will not be cached
557    */
558   public void setCacheBlocks(boolean cacheBlocks) {
559     this.cacheBlocks = cacheBlocks;
560   }
561 
562   /**
563    * Get whether blocks should be cached for this Scan.
564    * @return true if default caching should be used, false if blocks should not
565    * be cached
566    */
567   public boolean getCacheBlocks() {
568     return cacheBlocks;
569   }
570 
571   /**
572    * Set the value indicating whether loading CFs on demand should be allowed (cluster
573    * default is false). On-demand CF loading doesn't load column families until necessary, e.g.
574    * if you filter on one column, the other column family data will be loaded only for the rows
575    * that are included in result, not all rows like in normal case.
576    * With column-specific filters, like SingleColumnValueFilter w/filterIfMissing == true,
577    * this can deliver huge perf gains when there's a cf with lots of data; however, it can
578    * also lead to some inconsistent results, as follows:
579    * - if someone does a concurrent update to both column families in question you may get a row
580    *   that never existed, e.g. for { rowKey = 5, { cat_videos => 1 }, { video => "my cat" } }
581    *   someone puts rowKey 5 with { cat_videos => 0 }, { video => "my dog" }, concurrent scan
582    *   filtering on "cat_videos == 1" can get { rowKey = 5, { cat_videos => 1 },
583    *   { video => "my dog" } }.
584    * - if there's a concurrent split and you have more than 2 column families, some rows may be
585    *   missing some column families.
586    */
587   public void setLoadColumnFamiliesOnDemand(boolean value) {
588     this.loadColumnFamiliesOnDemand = value;
589   }
590 
591   /**
592    * Get the raw loadColumnFamiliesOnDemand setting; if it's not set, can be null.
593    */
594   public Boolean getLoadColumnFamiliesOnDemandValue() {
595     return this.loadColumnFamiliesOnDemand;
596   }
597 
598   /**
599    * Get the logical value indicating whether on-demand CF loading should be allowed.
600    */
601   public boolean doLoadColumnFamiliesOnDemand() {
602     return (this.loadColumnFamiliesOnDemand != null)
603       && this.loadColumnFamiliesOnDemand.booleanValue();
604   }
605 
606   /**
607    * Compile the table and column family (i.e. schema) information
608    * into a String. Useful for parsing and aggregation by debugging,
609    * logging, and administration tools.
610    * @return Map
611    */
612   @Override
613   public Map<String, Object> getFingerprint() {
614     Map<String, Object> map = new HashMap<String, Object>();
615     List<String> families = new ArrayList<String>();
616     if(this.familyMap.size() == 0) {
617       map.put("families", "ALL");
618       return map;
619     } else {
620       map.put("families", families);
621     }
622     for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
623         this.familyMap.entrySet()) {
624       families.add(Bytes.toStringBinary(entry.getKey()));
625     }
626     return map;
627   }
628 
629   /**
630    * Compile the details beyond the scope of getFingerprint (row, columns,
631    * timestamps, etc.) into a Map along with the fingerprinted information.
632    * Useful for debugging, logging, and administration tools.
633    * @param maxCols a limit on the number of columns output prior to truncation
634    * @return Map
635    */
636   @Override
637   public Map<String, Object> toMap(int maxCols) {
638     // start with the fingerpring map and build on top of it
639     Map<String, Object> map = getFingerprint();
640     // map from families to column list replaces fingerprint's list of families
641     Map<String, List<String>> familyColumns =
642       new HashMap<String, List<String>>();
643     map.put("families", familyColumns);
644     // add scalar information first
645     map.put("startRow", Bytes.toStringBinary(this.startRow));
646     map.put("stopRow", Bytes.toStringBinary(this.stopRow));
647     map.put("maxVersions", this.maxVersions);
648     map.put("batch", this.batch);
649     map.put("caching", this.caching);
650     map.put("maxResultSize", this.maxResultSize);
651     map.put("cacheBlocks", this.cacheBlocks);
652     map.put("loadColumnFamiliesOnDemand", this.loadColumnFamiliesOnDemand);
653     List<Long> timeRange = new ArrayList<Long>();
654     timeRange.add(this.tr.getMin());
655     timeRange.add(this.tr.getMax());
656     map.put("timeRange", timeRange);
657     int colCount = 0;
658     // iterate through affected families and list out up to maxCols columns
659     for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
660       this.familyMap.entrySet()) {
661       List<String> columns = new ArrayList<String>();
662       familyColumns.put(Bytes.toStringBinary(entry.getKey()), columns);
663       if(entry.getValue() == null) {
664         colCount++;
665         --maxCols;
666         columns.add("ALL");
667       } else {
668         colCount += entry.getValue().size();
669         if (maxCols <= 0) {
670           continue;
671         } 
672         for (byte [] column : entry.getValue()) {
673           if (--maxCols <= 0) {
674             continue;
675           }
676           columns.add(Bytes.toStringBinary(column));
677         }
678       } 
679     }       
680     map.put("totalColumns", colCount);
681     if (this.filter != null) {
682       map.put("filter", this.filter.toString());
683     }
684     // add the id if set
685     if (getId() != null) {
686       map.put("id", getId());
687     }
688     return map;
689   }
690 
691   /**
692    * Enable/disable "raw" mode for this scan.
693    * If "raw" is enabled the scan will return all
694    * delete marker and deleted rows that have not
695    * been collected, yet.
696    * This is mostly useful for Scan on column families
697    * that have KEEP_DELETED_ROWS enabled.
698    * It is an error to specify any column when "raw" is set.
699    * @param raw True/False to enable/disable "raw" mode.
700    */
701   public void setRaw(boolean raw) {
702     setAttribute(RAW_ATTR, Bytes.toBytes(raw));
703   }
704 
705   /**
706    * @return True if this Scan is in "raw" mode.
707    */
708   public boolean isRaw() {
709     byte[] attr = getAttribute(RAW_ATTR);
710     return attr == null ? false : Bytes.toBoolean(attr);
711   }
712 
713   /*
714    * Set the isolation level for this scan. If the
715    * isolation level is set to READ_UNCOMMITTED, then
716    * this scan will return data from committed and
717    * uncommitted transactions. If the isolation level 
718    * is set to READ_COMMITTED, then this scan will return 
719    * data from committed transactions only. If a isolation
720    * level is not explicitly set on a Scan, then it 
721    * is assumed to be READ_COMMITTED.
722    * @param level IsolationLevel for this scan
723    */
724   public void setIsolationLevel(IsolationLevel level) {
725     setAttribute(ISOLATION_LEVEL, level.toBytes());
726   }
727   /*
728    * @return The isolation level of this scan.
729    * If no isolation level was set for this scan object, 
730    * then it returns READ_COMMITTED.
731    * @return The IsolationLevel for this scan
732    */
733   public IsolationLevel getIsolationLevel() {
734     byte[] attr = getAttribute(ISOLATION_LEVEL);
735     return attr == null ? IsolationLevel.READ_COMMITTED :
736                           IsolationLevel.fromBytes(attr);
737   }
738 
739   /**
740    * Set whether this scan is a small scan
741    * <p>
742    * Small scan should use pread and big scan can use seek + read
743    * 
744    * seek + read is fast but can cause two problem (1) resource contention (2)
745    * cause too much network io
746    * 
747    * [89-fb] Using pread for non-compaction read request
748    * https://issues.apache.org/jira/browse/HBASE-7266
749    * 
750    * On the other hand, if setting it true, we would do
751    * openScanner,next,closeScanner in one RPC call. It means the better
752    * performance for small scan. [HBASE-9488].
753    * 
754    * Generally, if the scan range is within one data block(64KB), it could be
755    * considered as a small scan.
756    * 
757    * @param small
758    */
759   public void setSmall(boolean small) {
760     this.small = small;
761   }
762 
763   /**
764    * Get whether this scan is a small scan
765    * @return true if small scan
766    */
767   public boolean isSmall() {
768     return small;
769   }
770 }