1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.regionserver;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.List;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.hadoop.hbase.classification.InterfaceAudience;
29 import org.apache.hadoop.fs.Path;
30 import org.apache.hadoop.hbase.KeyValue;
31 import org.apache.hadoop.hbase.KeyValue.KVComparator;
32 import org.apache.hadoop.hbase.regionserver.compactions.Compactor;
33 import org.apache.hadoop.hbase.util.Bytes;
34
35
36
37
38 @InterfaceAudience.Private
39 public abstract class StripeMultiFileWriter implements Compactor.CellSink {
40 private static final Log LOG = LogFactory.getLog(StripeMultiFileWriter.class);
41
42
43 protected WriterFactory writerFactory;
44 protected KVComparator comparator;
45
46 protected List<StoreFile.Writer> existingWriters;
47 protected List<byte[]> boundaries;
48
49 protected StoreScanner sourceScanner;
50
51
52 private boolean doWriteStripeMetadata = true;
53
54 public interface WriterFactory {
55 public StoreFile.Writer createWriter() throws IOException;
56 }
57
58
59
60
61
62
63
64 public void init(StoreScanner sourceScanner, WriterFactory factory, KVComparator comparator)
65 throws IOException {
66 this.writerFactory = factory;
67 this.sourceScanner = sourceScanner;
68 this.comparator = comparator;
69 }
70
71 public void setNoStripeMetadata() {
72 this.doWriteStripeMetadata = false;
73 }
74
75 public List<Path> commitWriters(long maxSeqId, boolean isMajor) throws IOException {
76 assert this.existingWriters != null;
77 commitWritersInternal();
78 assert this.boundaries.size() == (this.existingWriters.size() + 1);
79 LOG.debug((this.doWriteStripeMetadata ? "W" : "Not w")
80 + "riting out metadata for " + this.existingWriters.size() + " writers");
81 List<Path> paths = new ArrayList<Path>();
82 for (int i = 0; i < this.existingWriters.size(); ++i) {
83 StoreFile.Writer writer = this.existingWriters.get(i);
84 if (writer == null) continue;
85 if (doWriteStripeMetadata) {
86 writer.appendFileInfo(StripeStoreFileManager.STRIPE_START_KEY, this.boundaries.get(i));
87 writer.appendFileInfo(StripeStoreFileManager.STRIPE_END_KEY, this.boundaries.get(i + 1));
88 }
89 writer.appendMetadata(maxSeqId, isMajor);
90 paths.add(writer.getPath());
91 writer.close();
92 }
93 this.existingWriters = null;
94 return paths;
95 }
96
97 public List<Path> abortWriters() {
98 assert this.existingWriters != null;
99 List<Path> paths = new ArrayList<Path>();
100 for (StoreFile.Writer writer : this.existingWriters) {
101 try {
102 paths.add(writer.getPath());
103 writer.close();
104 } catch (Exception ex) {
105 LOG.error("Failed to close the writer after an unfinished compaction.", ex);
106 }
107 }
108 this.existingWriters = null;
109 return paths;
110 }
111
112
113
114
115
116
117
118
119 protected void sanityCheckLeft(
120 byte[] left, byte[] row, int rowOffset, int rowLength) throws IOException {
121 if (StripeStoreFileManager.OPEN_KEY != left &&
122 comparator.compareRows(row, rowOffset, rowLength, left, 0, left.length) < 0) {
123 String error = "The first row is lower than the left boundary of [" + Bytes.toString(left)
124 + "]: [" + Bytes.toString(row, rowOffset, rowLength) + "]";
125 LOG.error(error);
126 throw new IOException(error);
127 }
128 }
129
130
131
132
133
134
135
136
137 protected void sanityCheckRight(
138 byte[] right, byte[] row, int rowOffset, int rowLength) throws IOException {
139 if (StripeStoreFileManager.OPEN_KEY != right &&
140 comparator.compareRows(row, rowOffset, rowLength, right, 0, right.length) >= 0) {
141 String error = "The last row is higher or equal than the right boundary of ["
142 + Bytes.toString(right) + "]: [" + Bytes.toString(row, rowOffset, rowLength) + "]";
143 LOG.error(error);
144 throw new IOException(error);
145 }
146 }
147
148
149
150
151
152 protected abstract void commitWritersInternal() throws IOException;
153
154
155
156
157
158
159 public static class BoundaryMultiWriter extends StripeMultiFileWriter {
160 private StoreFile.Writer currentWriter;
161 private byte[] currentWriterEndKey;
162
163 private KeyValue lastKv;
164 private long kvsInCurrentWriter = 0;
165 private int majorRangeFromIndex = -1, majorRangeToIndex = -1;
166 private boolean hasAnyWriter = false;
167
168
169
170
171
172
173
174
175 public BoundaryMultiWriter(List<byte[]> targetBoundaries,
176 byte[] majorRangeFrom, byte[] majorRangeTo) throws IOException {
177 super();
178 this.boundaries = targetBoundaries;
179 this.existingWriters = new ArrayList<StoreFile.Writer>(this.boundaries.size() - 1);
180
181
182 assert (majorRangeFrom == null) == (majorRangeTo == null);
183 if (majorRangeFrom != null) {
184 majorRangeFromIndex = (majorRangeFrom == StripeStoreFileManager.OPEN_KEY) ? 0
185 : Collections.binarySearch(this.boundaries, majorRangeFrom, Bytes.BYTES_COMPARATOR);
186 majorRangeToIndex = (majorRangeTo == StripeStoreFileManager.OPEN_KEY) ? boundaries.size()
187 : Collections.binarySearch(this.boundaries, majorRangeTo, Bytes.BYTES_COMPARATOR);
188 if (this.majorRangeFromIndex < 0 || this.majorRangeToIndex < 0) {
189 throw new IOException("Major range does not match writer boundaries: [" +
190 Bytes.toString(majorRangeFrom) + "] [" + Bytes.toString(majorRangeTo) + "]; from "
191 + majorRangeFromIndex + " to " + majorRangeToIndex);
192 }
193 }
194 }
195
196 @Override
197 public void append(KeyValue kv) throws IOException {
198 if (currentWriter == null && existingWriters.isEmpty()) {
199
200 sanityCheckLeft(this.boundaries.get(0),
201 kv.getRowArray(), kv.getRowOffset(), kv.getRowLength());
202 }
203 prepareWriterFor(kv);
204 currentWriter.append(kv);
205 lastKv = kv;
206 ++kvsInCurrentWriter;
207 }
208
209 private boolean isKvAfterCurrentWriter(KeyValue kv) {
210 return ((currentWriterEndKey != StripeStoreFileManager.OPEN_KEY) &&
211 (comparator.compareRows(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(),
212 currentWriterEndKey, 0, currentWriterEndKey.length) >= 0));
213 }
214
215 @Override
216 protected void commitWritersInternal() throws IOException {
217 stopUsingCurrentWriter();
218 while (existingWriters.size() < boundaries.size() - 1) {
219 createEmptyWriter();
220 }
221 if (lastKv != null) {
222 sanityCheckRight(boundaries.get(boundaries.size() - 1),
223 lastKv.getRowArray(), lastKv.getRowOffset(), lastKv.getRowLength());
224 }
225 }
226
227 private void prepareWriterFor(KeyValue kv) throws IOException {
228 if (currentWriter != null && !isKvAfterCurrentWriter(kv)) return;
229
230 stopUsingCurrentWriter();
231
232 while (isKvAfterCurrentWriter(kv)) {
233 checkCanCreateWriter();
234 createEmptyWriter();
235 }
236 checkCanCreateWriter();
237 hasAnyWriter = true;
238 currentWriter = writerFactory.createWriter();
239 existingWriters.add(currentWriter);
240 }
241
242
243
244
245
246
247
248
249
250
251
252 private void createEmptyWriter() throws IOException {
253 int index = existingWriters.size();
254 boolean isInMajorRange = (index >= majorRangeFromIndex) && (index < majorRangeToIndex);
255
256 boolean isLastWriter = !hasAnyWriter && (index == (boundaries.size() - 2));
257 boolean needEmptyFile = isInMajorRange || isLastWriter;
258 existingWriters.add(needEmptyFile ? writerFactory.createWriter() : null);
259 hasAnyWriter |= needEmptyFile;
260 currentWriterEndKey = (existingWriters.size() + 1 == boundaries.size())
261 ? null : boundaries.get(existingWriters.size() + 1);
262 }
263
264 private void checkCanCreateWriter() throws IOException {
265 int maxWriterCount = boundaries.size() - 1;
266 assert existingWriters.size() <= maxWriterCount;
267 if (existingWriters.size() >= maxWriterCount) {
268 throw new IOException("Cannot create any more writers (created " + existingWriters.size()
269 + " out of " + maxWriterCount + " - row might be out of range of all valid writers");
270 }
271 }
272
273 private void stopUsingCurrentWriter() {
274 if (currentWriter != null) {
275 if (LOG.isDebugEnabled()) {
276 LOG.debug("Stopping to use a writer after [" + Bytes.toString(currentWriterEndKey)
277 + "] row; wrote out " + kvsInCurrentWriter + " kvs");
278 }
279 kvsInCurrentWriter = 0;
280 }
281 currentWriter = null;
282 currentWriterEndKey = (existingWriters.size() + 1 == boundaries.size())
283 ? null : boundaries.get(existingWriters.size() + 1);
284 }
285 }
286
287
288
289
290
291
292
293 public static class SizeMultiWriter extends StripeMultiFileWriter {
294 private int targetCount;
295 private long targetKvs;
296 private byte[] left;
297 private byte[] right;
298
299 private KeyValue lastKv;
300 private StoreFile.Writer currentWriter;
301 protected byte[] lastRowInCurrentWriter = null;
302 private long kvsInCurrentWriter = 0;
303 private long kvsSeen = 0;
304 private long kvsSeenInPrevious = 0;
305
306
307
308
309
310
311
312 public SizeMultiWriter(int targetCount, long targetKvs, byte[] left, byte[] right) {
313 super();
314 this.targetCount = targetCount;
315 this.targetKvs = targetKvs;
316 this.left = left;
317 this.right = right;
318 int preallocate = Math.min(this.targetCount, 64);
319 this.existingWriters = new ArrayList<StoreFile.Writer>(preallocate);
320 this.boundaries = new ArrayList<byte[]>(preallocate + 1);
321 }
322
323 @Override
324 public void append(KeyValue kv) throws IOException {
325
326
327 boolean doCreateWriter = false;
328 if (currentWriter == null) {
329
330 sanityCheckLeft(left, kv.getRowArray(), kv.getRowOffset(), kv.getRowLength());
331 doCreateWriter = true;
332 } else if (lastRowInCurrentWriter != null
333 && !comparator.matchingRows(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(),
334 lastRowInCurrentWriter, 0, lastRowInCurrentWriter.length)) {
335 if (LOG.isDebugEnabled()) {
336 LOG.debug("Stopping to use a writer after [" + Bytes.toString(lastRowInCurrentWriter)
337 + "] row; wrote out " + kvsInCurrentWriter + " kvs");
338 }
339 lastRowInCurrentWriter = null;
340 kvsInCurrentWriter = 0;
341 kvsSeenInPrevious += kvsSeen;
342 doCreateWriter = true;
343 }
344 if (doCreateWriter) {
345 byte[] boundary = existingWriters.isEmpty() ? left : kv.getRow();
346 if (LOG.isDebugEnabled()) {
347 LOG.debug("Creating new writer starting at [" + Bytes.toString(boundary) + "]");
348 }
349 currentWriter = writerFactory.createWriter();
350 boundaries.add(boundary);
351 existingWriters.add(currentWriter);
352 }
353
354 currentWriter.append(kv);
355 lastKv = kv;
356 ++kvsInCurrentWriter;
357 kvsSeen = kvsInCurrentWriter;
358 if (this.sourceScanner != null) {
359 kvsSeen = Math.max(kvsSeen,
360 this.sourceScanner.getEstimatedNumberOfKvsScanned() - kvsSeenInPrevious);
361 }
362
363
364
365 if (lastRowInCurrentWriter == null
366 && existingWriters.size() < targetCount
367 && kvsSeen >= targetKvs) {
368 lastRowInCurrentWriter = kv.getRow();
369 if (LOG.isDebugEnabled()) {
370 LOG.debug("Preparing to start a new writer after [" + Bytes.toString(
371 lastRowInCurrentWriter) + "] row; observed " + kvsSeen + " kvs and wrote out "
372 + kvsInCurrentWriter + " kvs");
373 }
374 }
375 }
376
377 @Override
378 protected void commitWritersInternal() throws IOException {
379 if (LOG.isDebugEnabled()) {
380 LOG.debug("Stopping with " + kvsInCurrentWriter + " kvs in last writer" +
381 ((this.sourceScanner == null) ? "" : ("; observed estimated "
382 + this.sourceScanner.getEstimatedNumberOfKvsScanned() + " KVs total")));
383 }
384 if (lastKv != null) {
385 sanityCheckRight(
386 right, lastKv.getRowArray(), lastKv.getRowOffset(), lastKv.getRowLength());
387 }
388
389
390
391 if (existingWriters.isEmpty() && 1 == targetCount) {
392 if (LOG.isDebugEnabled()) {
393 LOG.debug("Merge expired stripes into one, create an empty file to preserve metadata.");
394 }
395 boundaries.add(left);
396 existingWriters.add(writerFactory.createWriter());
397 }
398
399 this.boundaries.add(right);
400 }
401 }
402 }