View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.client;
19  
20  import static org.apache.hadoop.hbase.client.ConnectionUtils.filterCells;
21  
22  import java.io.IOException;
23  import java.util.ArrayDeque;
24  import java.util.Deque;
25  import java.util.List;
26  
27  import org.apache.hadoop.hbase.Cell;
28  import org.apache.hadoop.hbase.CellUtil;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.util.Bytes;
31  
32  /**
33   * A scan result cache for batched scan, i.e,
34   * {@code scan.getBatch() > 0 && !scan.getAllowPartialResults()}.
35   * <p>
36   * If user setBatch(5) and rpc returns 3+5+5+5+3 cells, we should return 5+5+5+5+1 to user. setBatch
37   * doesn't mean setAllowPartialResult(true).
38   */
39  @InterfaceAudience.Private
40  public class BatchScanResultCache extends ScanResultCache {
41  
42    private final int batch;
43  
44    // used to filter out the cells that already returned to user as we always start from the
45    // beginning of a row when retry.
46    private Cell lastCell;
47  
48    private boolean lastResultPartial;
49  
50    private final Deque<Result> partialResults = new ArrayDeque<>();
51  
52    private int numCellsOfPartialResults;
53  
54    public BatchScanResultCache(List<Result> cache, int batch) {
55      super(cache);
56      this.batch = batch;
57    }
58  
59    private void recordLastResult(Result result) {
60      lastCell = result.rawCells()[result.rawCells().length - 1];
61      lastResultPartial = result.mayHaveMoreCellsInRow();
62    }
63  
64    private Result createCompletedResult() throws IOException {
65      numberOfCompleteRows++;
66      Result result = Result.createCompleteResult(partialResults);
67      partialResults.clear();
68      numCellsOfPartialResults = 0;
69      return result;
70    }
71  
72    // Add new result to the partial list and return a batched Result if caching size exceed batching
73    // limit. As the RS will also respect the scan.getBatch, we can make sure that we will get only
74    // one Result back at most(or null, which means we do not have enough cells).
75    private Result regroupResults(Result result) {
76      partialResults.addLast(result);
77      numCellsOfPartialResults += result.size();
78      if (numCellsOfPartialResults < batch) {
79        return null;
80      }
81      Cell[] cells = new Cell[batch];
82      int cellCount = 0;
83      boolean stale = false;
84      for (;;) {
85        Result r = partialResults.pollFirst();
86        stale = stale || r.isStale();
87        int newCellCount = cellCount + r.size();
88        if (newCellCount > batch) {
89          // We have more cells than expected, so split the current result
90          int len = batch - cellCount;
91          System.arraycopy(r.rawCells(), 0, cells, cellCount, len);
92          Cell[] remainingCells = new Cell[r.size() - len];
93          System.arraycopy(r.rawCells(), len, remainingCells, 0, r.size() - len);
94          partialResults.addFirst(
95            Result.create(remainingCells, r.getExists(), r.isStale(), r.mayHaveMoreCellsInRow()));
96          break;
97        }
98        System.arraycopy(r.rawCells(), 0, cells, cellCount, r.size());
99        if (newCellCount == batch) {
100         break;
101       }
102       cellCount = newCellCount;
103     }
104     numCellsOfPartialResults -= batch;
105     return Result.create(cells, null, stale,
106       result.mayHaveMoreCellsInRow() || !partialResults.isEmpty());
107   }
108 
109   @Override
110   public void loadResultsToCache(Result[] results, boolean isHeartbeatMessage) throws IOException {
111     if (results.length == 0) {
112       if (!isHeartbeatMessage) {
113         if (!partialResults.isEmpty()) {
114           checkUpdateNumberOfCompleteRowsAndCache(createCompletedResult());
115           return;
116         }
117         if (lastResultPartial) {
118           // An empty non heartbeat result indicate that there must be a row change. So if the
119           // lastResultPartial is true then we need to increase numberOfCompleteRows.
120           numberOfCompleteRows++;
121         }
122       }
123       return;
124     }
125     for (Result result : results) {
126       result = filterCells(result, lastCell);
127       if (result == null) {
128         continue;
129       }
130       if (!partialResults.isEmpty()) {
131         if (!Bytes.equals(partialResults.peek().getRow(), result.getRow())) {
132           // there is a row change
133           checkUpdateNumberOfCompleteRowsAndCache(createCompletedResult());
134         }
135       } else if (lastResultPartial && !CellUtil.matchingRow(lastCell, result.getRow())) {
136         // As for batched scan we may return partial results to user if we reach the batch limit, so
137         // here we need to use lastCell to determine if there is row change and increase
138         // numberOfCompleteRows.
139         numberOfCompleteRows++;
140       }
141       // check if we have a row change
142       if (!partialResults.isEmpty() &&
143           !Bytes.equals(partialResults.peek().getRow(), result.getRow())) {
144         checkUpdateNumberOfCompleteRowsAndCache(createCompletedResult());
145       }
146       Result regroupedResult = regroupResults(result);
147       if (regroupedResult != null) {
148         if (!regroupedResult.mayHaveMoreCellsInRow()) {
149           numberOfCompleteRows++;
150         }
151         checkUpdateNumberOfCompleteRowsAndCache(regroupedResult);
152         // only update last cell when we actually return it to user.
153         recordLastResult(regroupedResult);
154       }
155       if (!result.mayHaveMoreCellsInRow() && !partialResults.isEmpty()) {
156         // We are done for this row
157         checkUpdateNumberOfCompleteRowsAndCache(createCompletedResult());
158       }
159     }
160   }
161 
162   @Override
163   protected void checkUpdateNumberOfCompleteRowsAndCache(Result rs) {
164     // Number of Complete rows already updated
165     addResultToCache(rs);
166   }
167 
168   @Override
169   public void clear() {
170     partialResults.clear();
171     numCellsOfPartialResults = 0;
172     super.clear();
173   }
174 }