001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.regionserver; 019 020import static org.apache.hadoop.hbase.HConstants.REPLICATION_SCOPE_LOCAL; 021import static org.apache.hadoop.hbase.regionserver.HStoreFile.MAJOR_COMPACTION_KEY; 022import static org.apache.hadoop.hbase.util.CollectionUtils.computeIfAbsent; 023 024import edu.umd.cs.findbugs.annotations.Nullable; 025import java.io.EOFException; 026import java.io.FileNotFoundException; 027import java.io.IOException; 028import java.io.InterruptedIOException; 029import java.lang.reflect.Constructor; 030import java.nio.ByteBuffer; 031import java.nio.charset.StandardCharsets; 032import java.text.ParseException; 033import java.util.AbstractList; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.HashMap; 039import java.util.HashSet; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Map; 043import java.util.Map.Entry; 044import java.util.NavigableMap; 045import java.util.NavigableSet; 046import java.util.Optional; 047import java.util.RandomAccess; 048import java.util.Set; 049import java.util.TreeMap; 050import java.util.UUID; 051import java.util.concurrent.Callable; 052import java.util.concurrent.CompletionService; 053import java.util.concurrent.ConcurrentHashMap; 054import java.util.concurrent.ConcurrentMap; 055import java.util.concurrent.ConcurrentSkipListMap; 056import java.util.concurrent.ExecutionException; 057import java.util.concurrent.ExecutorCompletionService; 058import java.util.concurrent.ExecutorService; 059import java.util.concurrent.Executors; 060import java.util.concurrent.Future; 061import java.util.concurrent.FutureTask; 062import java.util.concurrent.ThreadFactory; 063import java.util.concurrent.ThreadPoolExecutor; 064import java.util.concurrent.TimeUnit; 065import java.util.concurrent.TimeoutException; 066import java.util.concurrent.atomic.AtomicBoolean; 067import java.util.concurrent.atomic.AtomicInteger; 068import java.util.concurrent.atomic.LongAdder; 069import java.util.concurrent.locks.Lock; 070import java.util.concurrent.locks.ReadWriteLock; 071import java.util.concurrent.locks.ReentrantReadWriteLock; 072import java.util.function.Function; 073import org.apache.hadoop.conf.Configuration; 074import org.apache.hadoop.fs.FileStatus; 075import org.apache.hadoop.fs.FileSystem; 076import org.apache.hadoop.fs.LocatedFileStatus; 077import org.apache.hadoop.fs.Path; 078import org.apache.hadoop.hbase.Cell; 079import org.apache.hadoop.hbase.CellBuilderType; 080import org.apache.hadoop.hbase.CellComparator; 081import org.apache.hadoop.hbase.CellComparatorImpl; 082import org.apache.hadoop.hbase.CellScanner; 083import org.apache.hadoop.hbase.CellUtil; 084import org.apache.hadoop.hbase.CompareOperator; 085import org.apache.hadoop.hbase.CompoundConfiguration; 086import org.apache.hadoop.hbase.DoNotRetryIOException; 087import org.apache.hadoop.hbase.DroppedSnapshotException; 088import org.apache.hadoop.hbase.ExtendedCellBuilderFactory; 089import org.apache.hadoop.hbase.HConstants; 090import org.apache.hadoop.hbase.HConstants.OperationStatusCode; 091import org.apache.hadoop.hbase.HDFSBlocksDistribution; 092import org.apache.hadoop.hbase.KeyValue; 093import org.apache.hadoop.hbase.KeyValueUtil; 094import org.apache.hadoop.hbase.NamespaceDescriptor; 095import org.apache.hadoop.hbase.NotServingRegionException; 096import org.apache.hadoop.hbase.PrivateCellUtil; 097import org.apache.hadoop.hbase.RegionTooBusyException; 098import org.apache.hadoop.hbase.TableName; 099import org.apache.hadoop.hbase.Tag; 100import org.apache.hadoop.hbase.TagUtil; 101import org.apache.hadoop.hbase.UnknownScannerException; 102import org.apache.hadoop.hbase.client.Append; 103import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 104import org.apache.hadoop.hbase.client.CompactionState; 105import org.apache.hadoop.hbase.client.Delete; 106import org.apache.hadoop.hbase.client.Durability; 107import org.apache.hadoop.hbase.client.Get; 108import org.apache.hadoop.hbase.client.Increment; 109import org.apache.hadoop.hbase.client.IsolationLevel; 110import org.apache.hadoop.hbase.client.Mutation; 111import org.apache.hadoop.hbase.client.PackagePrivateFieldAccessor; 112import org.apache.hadoop.hbase.client.Put; 113import org.apache.hadoop.hbase.client.RegionInfo; 114import org.apache.hadoop.hbase.client.RegionInfoBuilder; 115import org.apache.hadoop.hbase.client.RegionReplicaUtil; 116import org.apache.hadoop.hbase.client.Result; 117import org.apache.hadoop.hbase.client.RowMutations; 118import org.apache.hadoop.hbase.client.Scan; 119import org.apache.hadoop.hbase.client.TableDescriptor; 120import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 121import org.apache.hadoop.hbase.conf.ConfigurationManager; 122import org.apache.hadoop.hbase.conf.PropagatingConfigurationObserver; 123import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 124import org.apache.hadoop.hbase.coprocessor.RegionObserver.MutationType; 125import org.apache.hadoop.hbase.errorhandling.ForeignExceptionSnare; 126import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException; 127import org.apache.hadoop.hbase.exceptions.TimeoutIOException; 128import org.apache.hadoop.hbase.exceptions.UnknownProtocolException; 129import org.apache.hadoop.hbase.filter.ByteArrayComparable; 130import org.apache.hadoop.hbase.filter.FilterWrapper; 131import org.apache.hadoop.hbase.filter.IncompatibleFilterException; 132import org.apache.hadoop.hbase.io.HFileLink; 133import org.apache.hadoop.hbase.io.HeapSize; 134import org.apache.hadoop.hbase.io.TimeRange; 135import org.apache.hadoop.hbase.io.hfile.HFile; 136import org.apache.hadoop.hbase.ipc.CallerDisconnectedException; 137import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils; 138import org.apache.hadoop.hbase.ipc.RpcCall; 139import org.apache.hadoop.hbase.ipc.RpcServer; 140import org.apache.hadoop.hbase.monitoring.MonitoredTask; 141import org.apache.hadoop.hbase.monitoring.TaskMonitor; 142import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl.WriteEntry; 143import org.apache.hadoop.hbase.regionserver.ScannerContext.LimitScope; 144import org.apache.hadoop.hbase.regionserver.ScannerContext.NextState; 145import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext; 146import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker; 147import org.apache.hadoop.hbase.regionserver.throttle.CompactionThroughputControllerFactory; 148import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController; 149import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController; 150import org.apache.hadoop.hbase.regionserver.wal.WALUtil; 151import org.apache.hadoop.hbase.replication.ReplicationUtils; 152import org.apache.hadoop.hbase.replication.regionserver.ReplicationObserver; 153import org.apache.hadoop.hbase.security.User; 154import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 155import org.apache.hadoop.hbase.snapshot.SnapshotManifest; 156import org.apache.hadoop.hbase.trace.TraceUtil; 157import org.apache.hadoop.hbase.util.Bytes; 158import org.apache.hadoop.hbase.util.CancelableProgressable; 159import org.apache.hadoop.hbase.util.ClassSize; 160import org.apache.hadoop.hbase.util.CollectionUtils; 161import org.apache.hadoop.hbase.util.CompressionTest; 162import org.apache.hadoop.hbase.util.EncryptionTest; 163import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 164import org.apache.hadoop.hbase.util.FSUtils; 165import org.apache.hadoop.hbase.util.HashedBytes; 166import org.apache.hadoop.hbase.util.NonceKey; 167import org.apache.hadoop.hbase.util.Pair; 168import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil; 169import org.apache.hadoop.hbase.util.Threads; 170import org.apache.hadoop.hbase.wal.WAL; 171import org.apache.hadoop.hbase.wal.WALEdit; 172import org.apache.hadoop.hbase.wal.WALFactory; 173import org.apache.hadoop.hbase.wal.WALKey; 174import org.apache.hadoop.hbase.wal.WALKeyImpl; 175import org.apache.hadoop.hbase.wal.WALSplitter; 176import org.apache.hadoop.hbase.wal.WALSplitter.MutationReplay; 177import org.apache.hadoop.io.MultipleIOException; 178import org.apache.hadoop.util.StringUtils; 179import org.apache.htrace.core.TraceScope; 180import org.apache.yetus.audience.InterfaceAudience; 181import org.slf4j.Logger; 182import org.slf4j.LoggerFactory; 183 184import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 185import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 186import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 187import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 188import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 189import org.apache.hbase.thirdparty.com.google.protobuf.Service; 190import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; 191import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 192 193import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 194import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; 195import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceCall; 196import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionLoad; 197import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.StoreSequenceId; 198import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 199import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos; 200import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.CompactionDescriptor; 201import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor; 202import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.FlushAction; 203import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptor; 204import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor; 205import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor.EventType; 206import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.StoreDescriptor; 207 208/** 209 * Regions store data for a certain region of a table. It stores all columns 210 * for each row. A given table consists of one or more Regions. 211 * 212 * <p>An Region is defined by its table and its key extent. 213 * 214 * <p>Locking at the Region level serves only one purpose: preventing the 215 * region from being closed (and consequently split) while other operations 216 * are ongoing. Each row level operation obtains both a row lock and a region 217 * read lock for the duration of the operation. While a scanner is being 218 * constructed, getScanner holds a read lock. If the scanner is successfully 219 * constructed, it holds a read lock until it is closed. A close takes out a 220 * write lock and consequently will block for ongoing operations and will block 221 * new operations from starting while the close is in progress. 222 */ 223@SuppressWarnings("deprecation") 224@InterfaceAudience.Private 225public class HRegion implements HeapSize, PropagatingConfigurationObserver, Region { 226 private static final Logger LOG = LoggerFactory.getLogger(HRegion.class); 227 228 public static final String LOAD_CFS_ON_DEMAND_CONFIG_KEY = 229 "hbase.hregion.scan.loadColumnFamiliesOnDemand"; 230 231 public static final String HBASE_MAX_CELL_SIZE_KEY = "hbase.server.keyvalue.maxsize"; 232 public static final int DEFAULT_MAX_CELL_SIZE = 10485760; 233 234 public static final String HBASE_REGIONSERVER_MINIBATCH_SIZE = 235 "hbase.regionserver.minibatch.size"; 236 public static final int DEFAULT_HBASE_REGIONSERVER_MINIBATCH_SIZE = 20000; 237 238 /** 239 * This is the global default value for durability. All tables/mutations not 240 * defining a durability or using USE_DEFAULT will default to this value. 241 */ 242 private static final Durability DEFAULT_DURABILITY = Durability.SYNC_WAL; 243 244 final AtomicBoolean closed = new AtomicBoolean(false); 245 246 /* Closing can take some time; use the closing flag if there is stuff we don't 247 * want to do while in closing state; e.g. like offer this region up to the 248 * master as a region to close if the carrying regionserver is overloaded. 249 * Once set, it is never cleared. 250 */ 251 final AtomicBoolean closing = new AtomicBoolean(false); 252 253 /** 254 * The max sequence id of flushed data on this region. There is no edit in memory that is 255 * less that this sequence id. 256 */ 257 private volatile long maxFlushedSeqId = HConstants.NO_SEQNUM; 258 259 /** 260 * Record the sequence id of last flush operation. Can be in advance of 261 * {@link #maxFlushedSeqId} when flushing a single column family. In this case, 262 * {@link #maxFlushedSeqId} will be older than the oldest edit in memory. 263 */ 264 private volatile long lastFlushOpSeqId = HConstants.NO_SEQNUM; 265 266 /** 267 * The sequence id of the last replayed open region event from the primary region. This is used 268 * to skip entries before this due to the possibility of replay edits coming out of order from 269 * replication. 270 */ 271 protected volatile long lastReplayedOpenRegionSeqId = -1L; 272 protected volatile long lastReplayedCompactionSeqId = -1L; 273 274 ////////////////////////////////////////////////////////////////////////////// 275 // Members 276 ////////////////////////////////////////////////////////////////////////////// 277 278 // map from a locked row to the context for that lock including: 279 // - CountDownLatch for threads waiting on that row 280 // - the thread that owns the lock (allow reentrancy) 281 // - reference count of (reentrant) locks held by the thread 282 // - the row itself 283 private final ConcurrentHashMap<HashedBytes, RowLockContext> lockedRows = 284 new ConcurrentHashMap<>(); 285 286 protected final Map<byte[], HStore> stores = 287 new ConcurrentSkipListMap<>(Bytes.BYTES_RAWCOMPARATOR); 288 289 // TODO: account for each registered handler in HeapSize computation 290 private Map<String, com.google.protobuf.Service> coprocessorServiceHandlers = Maps.newHashMap(); 291 292 // Track data size in all memstores 293 private final MemStoreSizing memStoreSizing = new ThreadSafeMemStoreSizing(); 294 private final RegionServicesForStores regionServicesForStores = new RegionServicesForStores(this); 295 296 // Debug possible data loss due to WAL off 297 final LongAdder numMutationsWithoutWAL = new LongAdder(); 298 final LongAdder dataInMemoryWithoutWAL = new LongAdder(); 299 300 // Debug why CAS operations are taking a while. 301 final LongAdder checkAndMutateChecksPassed = new LongAdder(); 302 final LongAdder checkAndMutateChecksFailed = new LongAdder(); 303 304 // Number of requests 305 // Count rows for scan 306 final LongAdder readRequestsCount = new LongAdder(); 307 final LongAdder filteredReadRequestsCount = new LongAdder(); 308 // Count rows for multi row mutations 309 final LongAdder writeRequestsCount = new LongAdder(); 310 311 // Number of requests blocked by memstore size. 312 private final LongAdder blockedRequestsCount = new LongAdder(); 313 314 // Compaction LongAdders 315 final LongAdder compactionsFinished = new LongAdder(); 316 final LongAdder compactionsFailed = new LongAdder(); 317 final LongAdder compactionNumFilesCompacted = new LongAdder(); 318 final LongAdder compactionNumBytesCompacted = new LongAdder(); 319 final LongAdder compactionsQueued = new LongAdder(); 320 final LongAdder flushesQueued = new LongAdder(); 321 322 private final WAL wal; 323 private final HRegionFileSystem fs; 324 protected final Configuration conf; 325 private final Configuration baseConf; 326 private final int rowLockWaitDuration; 327 static final int DEFAULT_ROWLOCK_WAIT_DURATION = 30000; 328 329 private Path regionDir; 330 private FileSystem walFS; 331 332 // The internal wait duration to acquire a lock before read/update 333 // from the region. It is not per row. The purpose of this wait time 334 // is to avoid waiting a long time while the region is busy, so that 335 // we can release the IPC handler soon enough to improve the 336 // availability of the region server. It can be adjusted by 337 // tuning configuration "hbase.busy.wait.duration". 338 final long busyWaitDuration; 339 static final long DEFAULT_BUSY_WAIT_DURATION = HConstants.DEFAULT_HBASE_RPC_TIMEOUT; 340 341 // If updating multiple rows in one call, wait longer, 342 // i.e. waiting for busyWaitDuration * # of rows. However, 343 // we can limit the max multiplier. 344 final int maxBusyWaitMultiplier; 345 346 // Max busy wait duration. There is no point to wait longer than the RPC 347 // purge timeout, when a RPC call will be terminated by the RPC engine. 348 final long maxBusyWaitDuration; 349 350 // Max cell size. If nonzero, the maximum allowed size for any given cell 351 // in bytes 352 final long maxCellSize; 353 354 // Number of mutations for minibatch processing. 355 private final int miniBatchSize; 356 357 // negative number indicates infinite timeout 358 static final long DEFAULT_ROW_PROCESSOR_TIMEOUT = 60 * 1000L; 359 final ExecutorService rowProcessorExecutor = Executors.newCachedThreadPool(); 360 361 private final ConcurrentHashMap<RegionScanner, Long> scannerReadPoints; 362 363 /** 364 * The sequence ID that was enLongAddered when this region was opened. 365 */ 366 private long openSeqNum = HConstants.NO_SEQNUM; 367 368 /** 369 * The default setting for whether to enable on-demand CF loading for 370 * scan requests to this region. Requests can override it. 371 */ 372 private boolean isLoadingCfsOnDemandDefault = false; 373 374 private final AtomicInteger majorInProgress = new AtomicInteger(0); 375 private final AtomicInteger minorInProgress = new AtomicInteger(0); 376 377 // 378 // Context: During replay we want to ensure that we do not lose any data. So, we 379 // have to be conservative in how we replay wals. For each store, we calculate 380 // the maxSeqId up to which the store was flushed. And, skip the edits which 381 // are equal to or lower than maxSeqId for each store. 382 // The following map is populated when opening the region 383 Map<byte[], Long> maxSeqIdInStores = new TreeMap<>(Bytes.BYTES_COMPARATOR); 384 385 /** Saved state from replaying prepare flush cache */ 386 private PrepareFlushResult prepareFlushResult = null; 387 388 private volatile Optional<ConfigurationManager> configurationManager; 389 390 // Used for testing. 391 private volatile Long timeoutForWriteLock = null; 392 393 /** 394 * @return The smallest mvcc readPoint across all the scanners in this 395 * region. Writes older than this readPoint, are included in every 396 * read operation. 397 */ 398 public long getSmallestReadPoint() { 399 long minimumReadPoint; 400 // We need to ensure that while we are calculating the smallestReadPoint 401 // no new RegionScanners can grab a readPoint that we are unaware of. 402 // We achieve this by synchronizing on the scannerReadPoints object. 403 synchronized (scannerReadPoints) { 404 minimumReadPoint = mvcc.getReadPoint(); 405 for (Long readPoint : this.scannerReadPoints.values()) { 406 if (readPoint < minimumReadPoint) { 407 minimumReadPoint = readPoint; 408 } 409 } 410 } 411 return minimumReadPoint; 412 } 413 414 /* 415 * Data structure of write state flags used coordinating flushes, 416 * compactions and closes. 417 */ 418 static class WriteState { 419 // Set while a memstore flush is happening. 420 volatile boolean flushing = false; 421 // Set when a flush has been requested. 422 volatile boolean flushRequested = false; 423 // Number of compactions running. 424 AtomicInteger compacting = new AtomicInteger(0); 425 // Gets set in close. If set, cannot compact or flush again. 426 volatile boolean writesEnabled = true; 427 // Set if region is read-only 428 volatile boolean readOnly = false; 429 // whether the reads are enabled. This is different than readOnly, because readOnly is 430 // static in the lifetime of the region, while readsEnabled is dynamic 431 volatile boolean readsEnabled = true; 432 433 /** 434 * Set flags that make this region read-only. 435 * 436 * @param onOff flip value for region r/o setting 437 */ 438 synchronized void setReadOnly(final boolean onOff) { 439 this.writesEnabled = !onOff; 440 this.readOnly = onOff; 441 } 442 443 boolean isReadOnly() { 444 return this.readOnly; 445 } 446 447 boolean isFlushRequested() { 448 return this.flushRequested; 449 } 450 451 void setReadsEnabled(boolean readsEnabled) { 452 this.readsEnabled = readsEnabled; 453 } 454 455 static final long HEAP_SIZE = ClassSize.align( 456 ClassSize.OBJECT + 5 * Bytes.SIZEOF_BOOLEAN); 457 } 458 459 /** 460 * Objects from this class are created when flushing to describe all the different states that 461 * that method ends up in. The Result enum describes those states. The sequence id should only 462 * be specified if the flush was successful, and the failure message should only be specified 463 * if it didn't flush. 464 */ 465 public static class FlushResultImpl implements FlushResult { 466 final Result result; 467 final String failureReason; 468 final long flushSequenceId; 469 final boolean wroteFlushWalMarker; 470 471 /** 472 * Convenience constructor to use when the flush is successful, the failure message is set to 473 * null. 474 * @param result Expecting FLUSHED_NO_COMPACTION_NEEDED or FLUSHED_COMPACTION_NEEDED. 475 * @param flushSequenceId Generated sequence id that comes right after the edits in the 476 * memstores. 477 */ 478 FlushResultImpl(Result result, long flushSequenceId) { 479 this(result, flushSequenceId, null, false); 480 assert result == Result.FLUSHED_NO_COMPACTION_NEEDED || result == Result 481 .FLUSHED_COMPACTION_NEEDED; 482 } 483 484 /** 485 * Convenience constructor to use when we cannot flush. 486 * @param result Expecting CANNOT_FLUSH_MEMSTORE_EMPTY or CANNOT_FLUSH. 487 * @param failureReason Reason why we couldn't flush. 488 */ 489 FlushResultImpl(Result result, String failureReason, boolean wroteFlushMarker) { 490 this(result, -1, failureReason, wroteFlushMarker); 491 assert result == Result.CANNOT_FLUSH_MEMSTORE_EMPTY || result == Result.CANNOT_FLUSH; 492 } 493 494 /** 495 * Constructor with all the parameters. 496 * @param result Any of the Result. 497 * @param flushSequenceId Generated sequence id if the memstores were flushed else -1. 498 * @param failureReason Reason why we couldn't flush, or null. 499 */ 500 FlushResultImpl(Result result, long flushSequenceId, String failureReason, 501 boolean wroteFlushMarker) { 502 this.result = result; 503 this.flushSequenceId = flushSequenceId; 504 this.failureReason = failureReason; 505 this.wroteFlushWalMarker = wroteFlushMarker; 506 } 507 508 /** 509 * Convenience method, the equivalent of checking if result is 510 * FLUSHED_NO_COMPACTION_NEEDED or FLUSHED_NO_COMPACTION_NEEDED. 511 * @return true if the memstores were flushed, else false. 512 */ 513 @Override 514 public boolean isFlushSucceeded() { 515 return result == Result.FLUSHED_NO_COMPACTION_NEEDED || result == Result 516 .FLUSHED_COMPACTION_NEEDED; 517 } 518 519 /** 520 * Convenience method, the equivalent of checking if result is FLUSHED_COMPACTION_NEEDED. 521 * @return True if the flush requested a compaction, else false (doesn't even mean it flushed). 522 */ 523 @Override 524 public boolean isCompactionNeeded() { 525 return result == Result.FLUSHED_COMPACTION_NEEDED; 526 } 527 528 @Override 529 public String toString() { 530 return new StringBuilder() 531 .append("flush result:").append(result).append(", ") 532 .append("failureReason:").append(failureReason).append(",") 533 .append("flush seq id").append(flushSequenceId).toString(); 534 } 535 536 @Override 537 public Result getResult() { 538 return result; 539 } 540 } 541 542 /** A result object from prepare flush cache stage */ 543 @VisibleForTesting 544 static class PrepareFlushResult { 545 final FlushResultImpl result; // indicating a failure result from prepare 546 final TreeMap<byte[], StoreFlushContext> storeFlushCtxs; 547 final TreeMap<byte[], List<Path>> committedFiles; 548 final TreeMap<byte[], MemStoreSize> storeFlushableSize; 549 final long startTime; 550 final long flushOpSeqId; 551 final long flushedSeqId; 552 final MemStoreSizing totalFlushableSize; 553 554 /** Constructs an early exit case */ 555 PrepareFlushResult(FlushResultImpl result, long flushSeqId) { 556 this(result, null, null, null, Math.max(0, flushSeqId), 0, 0, MemStoreSizing.DUD); 557 } 558 559 /** Constructs a successful prepare flush result */ 560 PrepareFlushResult( 561 TreeMap<byte[], StoreFlushContext> storeFlushCtxs, 562 TreeMap<byte[], List<Path>> committedFiles, 563 TreeMap<byte[], MemStoreSize> storeFlushableSize, long startTime, long flushSeqId, 564 long flushedSeqId, MemStoreSizing totalFlushableSize) { 565 this(null, storeFlushCtxs, committedFiles, storeFlushableSize, startTime, 566 flushSeqId, flushedSeqId, totalFlushableSize); 567 } 568 569 private PrepareFlushResult( 570 FlushResultImpl result, 571 TreeMap<byte[], StoreFlushContext> storeFlushCtxs, 572 TreeMap<byte[], List<Path>> committedFiles, 573 TreeMap<byte[], MemStoreSize> storeFlushableSize, long startTime, long flushSeqId, 574 long flushedSeqId, MemStoreSizing totalFlushableSize) { 575 this.result = result; 576 this.storeFlushCtxs = storeFlushCtxs; 577 this.committedFiles = committedFiles; 578 this.storeFlushableSize = storeFlushableSize; 579 this.startTime = startTime; 580 this.flushOpSeqId = flushSeqId; 581 this.flushedSeqId = flushedSeqId; 582 this.totalFlushableSize = totalFlushableSize; 583 } 584 585 public FlushResult getResult() { 586 return this.result; 587 } 588 } 589 590 /** 591 * A class that tracks exceptions that have been observed in one batch. Not thread safe. 592 */ 593 static class ObservedExceptionsInBatch { 594 private boolean wrongRegion = false; 595 private boolean failedSanityCheck = false; 596 private boolean wrongFamily = false; 597 598 /** 599 * @return If a {@link WrongRegionException} has been observed. 600 */ 601 boolean hasSeenWrongRegion() { 602 return wrongRegion; 603 } 604 605 /** 606 * Records that a {@link WrongRegionException} has been observed. 607 */ 608 void sawWrongRegion() { 609 wrongRegion = true; 610 } 611 612 /** 613 * @return If a {@link FailedSanityCheckException} has been observed. 614 */ 615 boolean hasSeenFailedSanityCheck() { 616 return failedSanityCheck; 617 } 618 619 /** 620 * Records that a {@link FailedSanityCheckException} has been observed. 621 */ 622 void sawFailedSanityCheck() { 623 failedSanityCheck = true; 624 } 625 626 /** 627 * @return If a {@link NoSuchColumnFamilyException} has been observed. 628 */ 629 boolean hasSeenNoSuchFamily() { 630 return wrongFamily; 631 } 632 633 /** 634 * Records that a {@link NoSuchColumnFamilyException} has been observed. 635 */ 636 void sawNoSuchFamily() { 637 wrongFamily = true; 638 } 639 } 640 641 final WriteState writestate = new WriteState(); 642 643 long memstoreFlushSize; 644 final long timestampSlop; 645 final long rowProcessorTimeout; 646 647 // Last flush time for each Store. Useful when we are flushing for each column 648 private final ConcurrentMap<HStore, Long> lastStoreFlushTimeMap = new ConcurrentHashMap<>(); 649 650 final RegionServerServices rsServices; 651 private RegionServerAccounting rsAccounting; 652 private long flushCheckInterval; 653 // flushPerChanges is to prevent too many changes in memstore 654 private long flushPerChanges; 655 private long blockingMemStoreSize; 656 // Used to guard closes 657 final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 658 659 // Stop updates lock 660 private final ReentrantReadWriteLock updatesLock = new ReentrantReadWriteLock(); 661 private boolean splitRequest; 662 private byte[] explicitSplitPoint = null; 663 664 private final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(); 665 666 // Coprocessor host 667 private RegionCoprocessorHost coprocessorHost; 668 669 private TableDescriptor htableDescriptor = null; 670 private RegionSplitPolicy splitPolicy; 671 private FlushPolicy flushPolicy; 672 673 private final MetricsRegion metricsRegion; 674 private final MetricsRegionWrapperImpl metricsRegionWrapper; 675 private final Durability regionDurability; 676 private final boolean regionStatsEnabled; 677 // Stores the replication scope of the various column families of the table 678 // that has non-default scope 679 private final NavigableMap<byte[], Integer> replicationScope = new TreeMap<>( 680 Bytes.BYTES_COMPARATOR); 681 682 /** 683 * HRegion constructor. This constructor should only be used for testing and 684 * extensions. Instances of HRegion should be instantiated with the 685 * {@link HRegion#createHRegion} or {@link HRegion#openHRegion} method. 686 * 687 * @param tableDir qualified path of directory where region should be located, 688 * usually the table directory. 689 * @param wal The WAL is the outbound log for any updates to the HRegion 690 * The wal file is a logfile from the previous execution that's 691 * custom-computed for this HRegion. The HRegionServer computes and sorts the 692 * appropriate wal info for this HRegion. If there is a previous wal file 693 * (implying that the HRegion has been written-to before), then read it from 694 * the supplied path. 695 * @param fs is the filesystem. 696 * @param confParam is global configuration settings. 697 * @param regionInfo - RegionInfo that describes the region 698 * is new), then read them from the supplied path. 699 * @param htd the table descriptor 700 * @param rsServices reference to {@link RegionServerServices} or null 701 * @deprecated Use other constructors. 702 */ 703 @Deprecated 704 @VisibleForTesting 705 public HRegion(final Path tableDir, final WAL wal, final FileSystem fs, 706 final Configuration confParam, final RegionInfo regionInfo, 707 final TableDescriptor htd, final RegionServerServices rsServices) { 708 this(new HRegionFileSystem(confParam, fs, tableDir, regionInfo), 709 wal, confParam, htd, rsServices); 710 } 711 712 /** 713 * HRegion constructor. This constructor should only be used for testing and 714 * extensions. Instances of HRegion should be instantiated with the 715 * {@link HRegion#createHRegion} or {@link HRegion#openHRegion} method. 716 * 717 * @param fs is the filesystem. 718 * @param wal The WAL is the outbound log for any updates to the HRegion 719 * The wal file is a logfile from the previous execution that's 720 * custom-computed for this HRegion. The HRegionServer computes and sorts the 721 * appropriate wal info for this HRegion. If there is a previous wal file 722 * (implying that the HRegion has been written-to before), then read it from 723 * the supplied path. 724 * @param confParam is global configuration settings. 725 * @param htd the table descriptor 726 * @param rsServices reference to {@link RegionServerServices} or null 727 */ 728 public HRegion(final HRegionFileSystem fs, final WAL wal, final Configuration confParam, 729 final TableDescriptor htd, final RegionServerServices rsServices) { 730 if (htd == null) { 731 throw new IllegalArgumentException("Need table descriptor"); 732 } 733 734 if (confParam instanceof CompoundConfiguration) { 735 throw new IllegalArgumentException("Need original base configuration"); 736 } 737 738 this.wal = wal; 739 this.fs = fs; 740 741 // 'conf' renamed to 'confParam' b/c we use this.conf in the constructor 742 this.baseConf = confParam; 743 this.conf = new CompoundConfiguration() 744 .add(confParam) 745 .addBytesMap(htd.getValues()); 746 this.flushCheckInterval = conf.getInt(MEMSTORE_PERIODIC_FLUSH_INTERVAL, 747 DEFAULT_CACHE_FLUSH_INTERVAL); 748 this.flushPerChanges = conf.getLong(MEMSTORE_FLUSH_PER_CHANGES, DEFAULT_FLUSH_PER_CHANGES); 749 if (this.flushPerChanges > MAX_FLUSH_PER_CHANGES) { 750 throw new IllegalArgumentException(MEMSTORE_FLUSH_PER_CHANGES + " can not exceed " 751 + MAX_FLUSH_PER_CHANGES); 752 } 753 this.rowLockWaitDuration = conf.getInt("hbase.rowlock.wait.duration", 754 DEFAULT_ROWLOCK_WAIT_DURATION); 755 756 this.isLoadingCfsOnDemandDefault = conf.getBoolean(LOAD_CFS_ON_DEMAND_CONFIG_KEY, true); 757 this.htableDescriptor = htd; 758 Set<byte[]> families = this.htableDescriptor.getColumnFamilyNames(); 759 for (byte[] family : families) { 760 if (!replicationScope.containsKey(family)) { 761 int scope = htd.getColumnFamily(family).getScope(); 762 // Only store those families that has NON-DEFAULT scope 763 if (scope != REPLICATION_SCOPE_LOCAL) { 764 // Do a copy before storing it here. 765 replicationScope.put(Bytes.copy(family), scope); 766 } 767 } 768 } 769 this.rsServices = rsServices; 770 setHTableSpecificConf(); 771 this.scannerReadPoints = new ConcurrentHashMap<>(); 772 773 this.busyWaitDuration = conf.getLong( 774 "hbase.busy.wait.duration", DEFAULT_BUSY_WAIT_DURATION); 775 this.maxBusyWaitMultiplier = conf.getInt("hbase.busy.wait.multiplier.max", 2); 776 if (busyWaitDuration * maxBusyWaitMultiplier <= 0L) { 777 throw new IllegalArgumentException("Invalid hbase.busy.wait.duration (" 778 + busyWaitDuration + ") or hbase.busy.wait.multiplier.max (" 779 + maxBusyWaitMultiplier + "). Their product should be positive"); 780 } 781 this.maxBusyWaitDuration = conf.getLong("hbase.ipc.client.call.purge.timeout", 782 2 * HConstants.DEFAULT_HBASE_RPC_TIMEOUT); 783 784 /* 785 * timestamp.slop provides a server-side constraint on the timestamp. This 786 * assumes that you base your TS around currentTimeMillis(). In this case, 787 * throw an error to the user if the user-specified TS is newer than now + 788 * slop. LATEST_TIMESTAMP == don't use this functionality 789 */ 790 this.timestampSlop = conf.getLong( 791 "hbase.hregion.keyvalue.timestamp.slop.millisecs", 792 HConstants.LATEST_TIMESTAMP); 793 794 /** 795 * Timeout for the process time in processRowsWithLocks(). 796 * Use -1 to switch off time bound. 797 */ 798 this.rowProcessorTimeout = conf.getLong( 799 "hbase.hregion.row.processor.timeout", DEFAULT_ROW_PROCESSOR_TIMEOUT); 800 this.regionDurability = htd.getDurability() == Durability.USE_DEFAULT ? 801 DEFAULT_DURABILITY : htd.getDurability(); 802 803 decorateRegionConfiguration(conf); 804 if (rsServices != null) { 805 this.rsAccounting = this.rsServices.getRegionServerAccounting(); 806 // don't initialize coprocessors if not running within a regionserver 807 // TODO: revisit if coprocessors should load in other cases 808 this.coprocessorHost = new RegionCoprocessorHost(this, rsServices, conf); 809 this.metricsRegionWrapper = new MetricsRegionWrapperImpl(this); 810 this.metricsRegion = new MetricsRegion(this.metricsRegionWrapper); 811 } else { 812 this.metricsRegionWrapper = null; 813 this.metricsRegion = null; 814 } 815 if (LOG.isDebugEnabled()) { 816 // Write out region name as string and its encoded name. 817 LOG.debug("Instantiated " + this); 818 } 819 820 configurationManager = Optional.empty(); 821 822 // disable stats tracking system tables, but check the config for everything else 823 this.regionStatsEnabled = htd.getTableName().getNamespaceAsString().equals( 824 NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR) ? 825 false : 826 conf.getBoolean(HConstants.ENABLE_CLIENT_BACKPRESSURE, 827 HConstants.DEFAULT_ENABLE_CLIENT_BACKPRESSURE); 828 829 this.maxCellSize = conf.getLong(HBASE_MAX_CELL_SIZE_KEY, DEFAULT_MAX_CELL_SIZE); 830 this.miniBatchSize = conf.getInt(HBASE_REGIONSERVER_MINIBATCH_SIZE, 831 DEFAULT_HBASE_REGIONSERVER_MINIBATCH_SIZE); 832 } 833 834 void setHTableSpecificConf() { 835 if (this.htableDescriptor == null) return; 836 long flushSize = this.htableDescriptor.getMemStoreFlushSize(); 837 838 if (flushSize <= 0) { 839 flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 840 TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE); 841 } 842 this.memstoreFlushSize = flushSize; 843 long mult = conf.getLong(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER, 844 HConstants.DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER); 845 this.blockingMemStoreSize = this.memstoreFlushSize * mult; 846 } 847 848 /** 849 * Initialize this region. 850 * Used only by tests and SplitTransaction to reopen the region. 851 * You should use createHRegion() or openHRegion() 852 * @return What the next sequence (edit) id should be. 853 * @throws IOException e 854 * @deprecated use HRegion.createHRegion() or HRegion.openHRegion() 855 */ 856 @Deprecated 857 public long initialize() throws IOException { 858 return initialize(null); 859 } 860 861 /** 862 * Initialize this region. 863 * 864 * @param reporter Tickle every so often if initialize is taking a while. 865 * @return What the next sequence (edit) id should be. 866 * @throws IOException e 867 */ 868 @VisibleForTesting 869 long initialize(final CancelableProgressable reporter) throws IOException { 870 871 //Refuse to open the region if there is no column family in the table 872 if (htableDescriptor.getColumnFamilyCount() == 0) { 873 throw new DoNotRetryIOException("Table " + htableDescriptor.getTableName().getNameAsString()+ 874 " should have at least one column family."); 875 } 876 877 MonitoredTask status = TaskMonitor.get().createStatus("Initializing region " + this); 878 long nextSeqId = -1; 879 try { 880 nextSeqId = initializeRegionInternals(reporter, status); 881 return nextSeqId; 882 } catch (IOException e) { 883 LOG.warn("Failed initialize of region= {}, starting to roll back memstore", 884 getRegionInfo().getRegionNameAsString(), e); 885 // global memstore size will be decreased when dropping memstore 886 try { 887 //drop the memory used by memstore if open region fails 888 dropMemStoreContents(); 889 } catch (IOException ioE) { 890 if (conf.getBoolean(MemStoreLAB.USEMSLAB_KEY, MemStoreLAB.USEMSLAB_DEFAULT)) { 891 LOG.warn("Failed drop memstore of region= {}, " 892 + "some chunks may not released forever since MSLAB is enabled", 893 getRegionInfo().getRegionNameAsString()); 894 } 895 896 } 897 throw e; 898 } finally { 899 // nextSeqid will be -1 if the initialization fails. 900 // At least it will be 0 otherwise. 901 if (nextSeqId == -1) { 902 status.abort("Exception during region " + getRegionInfo().getRegionNameAsString() + 903 " initialization."); 904 } 905 } 906 } 907 908 private long initializeRegionInternals(final CancelableProgressable reporter, 909 final MonitoredTask status) throws IOException { 910 if (coprocessorHost != null) { 911 status.setStatus("Running coprocessor pre-open hook"); 912 coprocessorHost.preOpen(); 913 } 914 915 // Write HRI to a file in case we need to recover hbase:meta 916 // Only the primary replica should write .regioninfo 917 if (this.getRegionInfo().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) { 918 status.setStatus("Writing region info on filesystem"); 919 fs.checkRegionInfoOnFilesystem(); 920 } 921 922 // Initialize all the HStores 923 status.setStatus("Initializing all the Stores"); 924 long maxSeqId = initializeStores(reporter, status); 925 this.mvcc.advanceTo(maxSeqId); 926 if (ServerRegionReplicaUtil.shouldReplayRecoveredEdits(this)) { 927 Collection<HStore> stores = this.stores.values(); 928 try { 929 // update the stores that we are replaying 930 stores.forEach(HStore::startReplayingFromWAL); 931 // Recover any edits if available. 932 maxSeqId = Math.max(maxSeqId, 933 replayRecoveredEditsIfAny(maxSeqIdInStores, reporter, status)); 934 // Make sure mvcc is up to max. 935 this.mvcc.advanceTo(maxSeqId); 936 } finally { 937 // update the stores that we are done replaying 938 stores.forEach(HStore::stopReplayingFromWAL); 939 } 940 } 941 this.lastReplayedOpenRegionSeqId = maxSeqId; 942 943 this.writestate.setReadOnly(ServerRegionReplicaUtil.isReadOnly(this)); 944 this.writestate.flushRequested = false; 945 this.writestate.compacting.set(0); 946 947 if (this.writestate.writesEnabled) { 948 // Remove temporary data left over from old regions 949 status.setStatus("Cleaning up temporary data from old regions"); 950 fs.cleanupTempDir(); 951 } 952 953 if (this.writestate.writesEnabled) { 954 status.setStatus("Cleaning up detritus from prior splits"); 955 // Get rid of any splits or merges that were lost in-progress. Clean out 956 // these directories here on open. We may be opening a region that was 957 // being split but we crashed in the middle of it all. 958 fs.cleanupAnySplitDetritus(); 959 fs.cleanupMergesDir(); 960 } 961 962 // Initialize split policy 963 this.splitPolicy = RegionSplitPolicy.create(this, conf); 964 965 // Initialize flush policy 966 this.flushPolicy = FlushPolicyFactory.create(this, conf); 967 968 long lastFlushTime = EnvironmentEdgeManager.currentTime(); 969 for (HStore store: stores.values()) { 970 this.lastStoreFlushTimeMap.put(store, lastFlushTime); 971 } 972 973 // Use maximum of log sequenceid or that which was found in stores 974 // (particularly if no recovered edits, seqid will be -1). 975 long maxSeqIdFromFile = 976 WALSplitter.getMaxRegionSequenceId(getWalFileSystem(), getWALRegionDir()); 977 long nextSeqId = Math.max(maxSeqId, maxSeqIdFromFile) + 1; 978 // The openSeqNum will always be increase even for read only region, as we rely on it to 979 // determine whether a region has been successfully reopend, so here we always need to update 980 // the max sequence id file. 981 if (RegionReplicaUtil.isDefaultReplica(getRegionInfo())) { 982 LOG.debug("writing seq id for {}", this.getRegionInfo().getEncodedName()); 983 WALSplitter.writeRegionSequenceIdFile(fs.getFileSystem(), getWALRegionDir(), nextSeqId); 984 //WALSplitter.writeRegionSequenceIdFile(getWalFileSystem(), getWALRegionDir(), nextSeqId - 1); 985 } 986 987 LOG.info("Opened {}; next sequenceid={}", this.getRegionInfo().getShortNameToLog(), nextSeqId); 988 989 // A region can be reopened if failed a split; reset flags 990 this.closing.set(false); 991 this.closed.set(false); 992 993 if (coprocessorHost != null) { 994 status.setStatus("Running coprocessor post-open hooks"); 995 coprocessorHost.postOpen(); 996 } 997 998 status.markComplete("Region opened successfully"); 999 return nextSeqId; 1000 } 1001 1002 /** 1003 * Open all Stores. 1004 * @param reporter 1005 * @param status 1006 * @return Highest sequenceId found out in a Store. 1007 * @throws IOException 1008 */ 1009 private long initializeStores(CancelableProgressable reporter, MonitoredTask status) 1010 throws IOException { 1011 // Load in all the HStores. 1012 long maxSeqId = -1; 1013 // initialized to -1 so that we pick up MemstoreTS from column families 1014 long maxMemstoreTS = -1; 1015 1016 if (htableDescriptor.getColumnFamilyCount() != 0) { 1017 // initialize the thread pool for opening stores in parallel. 1018 ThreadPoolExecutor storeOpenerThreadPool = 1019 getStoreOpenAndCloseThreadPool("StoreOpener-" + this.getRegionInfo().getShortNameToLog()); 1020 CompletionService<HStore> completionService = new ExecutorCompletionService<>(storeOpenerThreadPool); 1021 1022 // initialize each store in parallel 1023 for (final ColumnFamilyDescriptor family : htableDescriptor.getColumnFamilies()) { 1024 status.setStatus("Instantiating store for column family " + family); 1025 completionService.submit(new Callable<HStore>() { 1026 @Override 1027 public HStore call() throws IOException { 1028 return instantiateHStore(family); 1029 } 1030 }); 1031 } 1032 boolean allStoresOpened = false; 1033 boolean hasSloppyStores = false; 1034 try { 1035 for (int i = 0; i < htableDescriptor.getColumnFamilyCount(); i++) { 1036 Future<HStore> future = completionService.take(); 1037 HStore store = future.get(); 1038 this.stores.put(store.getColumnFamilyDescriptor().getName(), store); 1039 if (store.isSloppyMemStore()) { 1040 hasSloppyStores = true; 1041 } 1042 1043 long storeMaxSequenceId = store.getMaxSequenceId().orElse(0L); 1044 maxSeqIdInStores.put(Bytes.toBytes(store.getColumnFamilyName()), 1045 storeMaxSequenceId); 1046 if (maxSeqId == -1 || storeMaxSequenceId > maxSeqId) { 1047 maxSeqId = storeMaxSequenceId; 1048 } 1049 long maxStoreMemstoreTS = store.getMaxMemStoreTS().orElse(0L); 1050 if (maxStoreMemstoreTS > maxMemstoreTS) { 1051 maxMemstoreTS = maxStoreMemstoreTS; 1052 } 1053 } 1054 allStoresOpened = true; 1055 if(hasSloppyStores) { 1056 htableDescriptor = TableDescriptorBuilder.newBuilder(htableDescriptor) 1057 .setFlushPolicyClassName(FlushNonSloppyStoresFirstPolicy.class.getName()) 1058 .build(); 1059 LOG.info("Setting FlushNonSloppyStoresFirstPolicy for the region=" + this); 1060 } 1061 } catch (InterruptedException e) { 1062 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1063 } catch (ExecutionException e) { 1064 throw new IOException(e.getCause()); 1065 } finally { 1066 storeOpenerThreadPool.shutdownNow(); 1067 if (!allStoresOpened) { 1068 // something went wrong, close all opened stores 1069 LOG.error("Could not initialize all stores for the region=" + this); 1070 for (HStore store : this.stores.values()) { 1071 try { 1072 store.close(); 1073 } catch (IOException e) { 1074 LOG.warn("close store failed", e); 1075 } 1076 } 1077 } 1078 } 1079 } 1080 return Math.max(maxSeqId, maxMemstoreTS + 1); 1081 } 1082 1083 private void initializeWarmup(final CancelableProgressable reporter) throws IOException { 1084 MonitoredTask status = TaskMonitor.get().createStatus("Initializing region " + this); 1085 // Initialize all the HStores 1086 status.setStatus("Warming up all the Stores"); 1087 try { 1088 initializeStores(reporter, status); 1089 } finally { 1090 status.markComplete("Done warming up."); 1091 } 1092 } 1093 1094 /** 1095 * @return Map of StoreFiles by column family 1096 */ 1097 private NavigableMap<byte[], List<Path>> getStoreFiles() { 1098 NavigableMap<byte[], List<Path>> allStoreFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR); 1099 for (HStore store : stores.values()) { 1100 Collection<HStoreFile> storeFiles = store.getStorefiles(); 1101 if (storeFiles == null) { 1102 continue; 1103 } 1104 List<Path> storeFileNames = new ArrayList<>(); 1105 for (HStoreFile storeFile : storeFiles) { 1106 storeFileNames.add(storeFile.getPath()); 1107 } 1108 allStoreFiles.put(store.getColumnFamilyDescriptor().getName(), storeFileNames); 1109 } 1110 return allStoreFiles; 1111 } 1112 1113 private void writeRegionOpenMarker(WAL wal, long openSeqId) throws IOException { 1114 Map<byte[], List<Path>> storeFiles = getStoreFiles(); 1115 RegionEventDescriptor regionOpenDesc = ProtobufUtil.toRegionEventDescriptor( 1116 RegionEventDescriptor.EventType.REGION_OPEN, getRegionInfo(), openSeqId, 1117 getRegionServerServices().getServerName(), storeFiles); 1118 WALUtil.writeRegionEventMarker(wal, getReplicationScope(), getRegionInfo(), regionOpenDesc, 1119 mvcc); 1120 } 1121 1122 private void writeRegionCloseMarker(WAL wal) throws IOException { 1123 Map<byte[], List<Path>> storeFiles = getStoreFiles(); 1124 RegionEventDescriptor regionEventDesc = ProtobufUtil.toRegionEventDescriptor( 1125 RegionEventDescriptor.EventType.REGION_CLOSE, getRegionInfo(), mvcc.getReadPoint(), 1126 getRegionServerServices().getServerName(), storeFiles); 1127 WALUtil.writeRegionEventMarker(wal, getReplicationScope(), getRegionInfo(), regionEventDesc, 1128 mvcc); 1129 1130 // Store SeqId in WAL FileSystem when a region closes 1131 // checking region folder exists is due to many tests which delete the table folder while a 1132 // table is still online 1133 if (getWalFileSystem().exists(getWALRegionDir())) { 1134 WALSplitter.writeRegionSequenceIdFile(getWalFileSystem(), getWALRegionDir(), 1135 mvcc.getReadPoint()); 1136 } 1137 } 1138 1139 /** 1140 * @return True if this region has references. 1141 */ 1142 public boolean hasReferences() { 1143 return stores.values().stream().anyMatch(HStore::hasReferences); 1144 } 1145 1146 public void blockUpdates() { 1147 this.updatesLock.writeLock().lock(); 1148 } 1149 1150 public void unblockUpdates() { 1151 this.updatesLock.writeLock().unlock(); 1152 } 1153 1154 public HDFSBlocksDistribution getHDFSBlocksDistribution() { 1155 HDFSBlocksDistribution hdfsBlocksDistribution = new HDFSBlocksDistribution(); 1156 stores.values().stream().filter(s -> s.getStorefiles() != null) 1157 .flatMap(s -> s.getStorefiles().stream()).map(HStoreFile::getHDFSBlockDistribution) 1158 .forEachOrdered(hdfsBlocksDistribution::add); 1159 return hdfsBlocksDistribution; 1160 } 1161 1162 /** 1163 * This is a helper function to compute HDFS block distribution on demand 1164 * @param conf configuration 1165 * @param tableDescriptor TableDescriptor of the table 1166 * @param regionInfo encoded name of the region 1167 * @return The HDFS blocks distribution for the given region. 1168 * @throws IOException 1169 */ 1170 public static HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf, 1171 TableDescriptor tableDescriptor, RegionInfo regionInfo) throws IOException { 1172 Path tablePath = FSUtils.getTableDir(FSUtils.getRootDir(conf), tableDescriptor.getTableName()); 1173 return computeHDFSBlocksDistribution(conf, tableDescriptor, regionInfo, tablePath); 1174 } 1175 1176 /** 1177 * This is a helper function to compute HDFS block distribution on demand 1178 * @param conf configuration 1179 * @param tableDescriptor TableDescriptor of the table 1180 * @param regionInfo encoded name of the region 1181 * @param tablePath the table directory 1182 * @return The HDFS blocks distribution for the given region. 1183 * @throws IOException 1184 */ 1185 public static HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf, 1186 TableDescriptor tableDescriptor, RegionInfo regionInfo, Path tablePath) throws IOException { 1187 HDFSBlocksDistribution hdfsBlocksDistribution = new HDFSBlocksDistribution(); 1188 FileSystem fs = tablePath.getFileSystem(conf); 1189 1190 HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tablePath, regionInfo); 1191 for (ColumnFamilyDescriptor family : tableDescriptor.getColumnFamilies()) { 1192 List<LocatedFileStatus> locatedFileStatusList = HRegionFileSystem 1193 .getStoreFilesLocatedStatus(regionFs, family.getNameAsString(), true); 1194 if (locatedFileStatusList == null) { 1195 continue; 1196 } 1197 1198 for (LocatedFileStatus status : locatedFileStatusList) { 1199 Path p = status.getPath(); 1200 if (StoreFileInfo.isReference(p) || HFileLink.isHFileLink(p)) { 1201 // Only construct StoreFileInfo object if its not a hfile, save obj 1202 // creation 1203 StoreFileInfo storeFileInfo = new StoreFileInfo(conf, fs, status); 1204 hdfsBlocksDistribution.add(storeFileInfo 1205 .computeHDFSBlocksDistribution(fs)); 1206 } else if (StoreFileInfo.isHFile(p)) { 1207 // If its a HFile, then lets just add to the block distribution 1208 // lets not create more objects here, not even another HDFSBlocksDistribution 1209 FSUtils.addToHDFSBlocksDistribution(hdfsBlocksDistribution, 1210 status.getBlockLocations()); 1211 } else { 1212 throw new IOException("path=" + p 1213 + " doesn't look like a valid StoreFile"); 1214 } 1215 } 1216 } 1217 return hdfsBlocksDistribution; 1218 } 1219 1220 /** 1221 * Increase the size of mem store in this region and the size of global mem 1222 * store 1223 */ 1224 void incMemStoreSize(MemStoreSize mss) { 1225 incMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize()); 1226 } 1227 1228 void incMemStoreSize(long dataSizeDelta, long heapSizeDelta, long offHeapSizeDelta) { 1229 if (this.rsAccounting != null) { 1230 rsAccounting.incGlobalMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta); 1231 } 1232 long dataSize = 1233 this.memStoreSizing.incMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta); 1234 checkNegativeMemStoreDataSize(dataSize, dataSizeDelta); 1235 } 1236 1237 void decrMemStoreSize(MemStoreSize mss) { 1238 decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize()); 1239 } 1240 1241 void decrMemStoreSize(long dataSizeDelta, long heapSizeDelta, long offHeapSizeDelta) { 1242 if (this.rsAccounting != null) { 1243 rsAccounting.decGlobalMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta); 1244 } 1245 long dataSize = 1246 this.memStoreSizing.decMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta); 1247 checkNegativeMemStoreDataSize(dataSize, -dataSizeDelta); 1248 } 1249 1250 private void checkNegativeMemStoreDataSize(long memStoreDataSize, long delta) { 1251 // This is extremely bad if we make memStoreSizing negative. Log as much info on the offending 1252 // caller as possible. (memStoreSizing might be a negative value already -- freeing memory) 1253 if (memStoreDataSize < 0) { 1254 LOG.error("Asked to modify this region's (" + this.toString() 1255 + ") memStoreSizing to a negative value which is incorrect. Current memStoreSizing=" 1256 + (memStoreDataSize - delta) + ", delta=" + delta, new Exception()); 1257 } 1258 } 1259 1260 @Override 1261 public RegionInfo getRegionInfo() { 1262 return this.fs.getRegionInfo(); 1263 } 1264 1265 /** 1266 * @return Instance of {@link RegionServerServices} used by this HRegion. 1267 * Can be null. 1268 */ 1269 RegionServerServices getRegionServerServices() { 1270 return this.rsServices; 1271 } 1272 1273 @Override 1274 public long getReadRequestsCount() { 1275 return readRequestsCount.sum(); 1276 } 1277 1278 @Override 1279 public long getFilteredReadRequestsCount() { 1280 return filteredReadRequestsCount.sum(); 1281 } 1282 1283 @Override 1284 public long getWriteRequestsCount() { 1285 return writeRequestsCount.sum(); 1286 } 1287 1288 @Override 1289 public long getMemStoreDataSize() { 1290 return memStoreSizing.getDataSize(); 1291 } 1292 1293 @Override 1294 public long getMemStoreHeapSize() { 1295 return memStoreSizing.getHeapSize(); 1296 } 1297 1298 @Override 1299 public long getMemStoreOffHeapSize() { 1300 return memStoreSizing.getOffHeapSize(); 1301 } 1302 1303 /** @return store services for this region, to access services required by store level needs */ 1304 public RegionServicesForStores getRegionServicesForStores() { 1305 return regionServicesForStores; 1306 } 1307 1308 @Override 1309 public long getNumMutationsWithoutWAL() { 1310 return numMutationsWithoutWAL.sum(); 1311 } 1312 1313 @Override 1314 public long getDataInMemoryWithoutWAL() { 1315 return dataInMemoryWithoutWAL.sum(); 1316 } 1317 1318 @Override 1319 public long getBlockedRequestsCount() { 1320 return blockedRequestsCount.sum(); 1321 } 1322 1323 @Override 1324 public long getCheckAndMutateChecksPassed() { 1325 return checkAndMutateChecksPassed.sum(); 1326 } 1327 1328 @Override 1329 public long getCheckAndMutateChecksFailed() { 1330 return checkAndMutateChecksFailed.sum(); 1331 } 1332 1333 // TODO Needs to check whether we should expose our metrics system to CPs. If CPs themselves doing 1334 // the op and bypassing the core, this might be needed? Should be stop supporting the bypass 1335 // feature? 1336 public MetricsRegion getMetrics() { 1337 return metricsRegion; 1338 } 1339 1340 @Override 1341 public boolean isClosed() { 1342 return this.closed.get(); 1343 } 1344 1345 @Override 1346 public boolean isClosing() { 1347 return this.closing.get(); 1348 } 1349 1350 @Override 1351 public boolean isReadOnly() { 1352 return this.writestate.isReadOnly(); 1353 } 1354 1355 @Override 1356 public boolean isAvailable() { 1357 return !isClosed() && !isClosing(); 1358 } 1359 1360 @Override 1361 public boolean isSplittable() { 1362 return isAvailable() && !hasReferences(); 1363 } 1364 1365 @Override 1366 public boolean isMergeable() { 1367 if (!isAvailable()) { 1368 LOG.debug("Region " + this 1369 + " is not mergeable because it is closing or closed"); 1370 return false; 1371 } 1372 if (hasReferences()) { 1373 LOG.debug("Region " + this 1374 + " is not mergeable because it has references"); 1375 return false; 1376 } 1377 1378 return true; 1379 } 1380 1381 public boolean areWritesEnabled() { 1382 synchronized(this.writestate) { 1383 return this.writestate.writesEnabled; 1384 } 1385 } 1386 1387 @VisibleForTesting 1388 public MultiVersionConcurrencyControl getMVCC() { 1389 return mvcc; 1390 } 1391 1392 @Override 1393 public long getMaxFlushedSeqId() { 1394 return maxFlushedSeqId; 1395 } 1396 1397 /** 1398 * @return readpoint considering given IsolationLevel. Pass {@code null} for default 1399 */ 1400 public long getReadPoint(IsolationLevel isolationLevel) { 1401 if (isolationLevel != null && isolationLevel == IsolationLevel.READ_UNCOMMITTED) { 1402 // This scan can read even uncommitted transactions 1403 return Long.MAX_VALUE; 1404 } 1405 return mvcc.getReadPoint(); 1406 } 1407 1408 public boolean isLoadingCfsOnDemandDefault() { 1409 return this.isLoadingCfsOnDemandDefault; 1410 } 1411 1412 /** 1413 * Close down this HRegion. Flush the cache, shut down each HStore, don't 1414 * service any more calls. 1415 * 1416 * <p>This method could take some time to execute, so don't call it from a 1417 * time-sensitive thread. 1418 * 1419 * @return Vector of all the storage files that the HRegion's component 1420 * HStores make use of. It's a list of all StoreFile objects. Returns empty 1421 * vector if already closed and null if judged that it should not close. 1422 * 1423 * @throws IOException e 1424 * @throws DroppedSnapshotException Thrown when replay of wal is required 1425 * because a Snapshot was not properly persisted. The region is put in closing mode, and the 1426 * caller MUST abort after this. 1427 */ 1428 public Map<byte[], List<HStoreFile>> close() throws IOException { 1429 return close(false); 1430 } 1431 1432 private final Object closeLock = new Object(); 1433 1434 /** Conf key for the periodic flush interval */ 1435 public static final String MEMSTORE_PERIODIC_FLUSH_INTERVAL = 1436 "hbase.regionserver.optionalcacheflushinterval"; 1437 /** Default interval for the memstore flush */ 1438 public static final int DEFAULT_CACHE_FLUSH_INTERVAL = 3600000; 1439 /** Default interval for System tables memstore flush */ 1440 public static final int SYSTEM_CACHE_FLUSH_INTERVAL = 300000; // 5 minutes 1441 1442 /** Conf key to force a flush if there are already enough changes for one region in memstore */ 1443 public static final String MEMSTORE_FLUSH_PER_CHANGES = 1444 "hbase.regionserver.flush.per.changes"; 1445 public static final long DEFAULT_FLUSH_PER_CHANGES = 30000000; // 30 millions 1446 /** 1447 * The following MAX_FLUSH_PER_CHANGES is large enough because each KeyValue has 20+ bytes 1448 * overhead. Therefore, even 1G empty KVs occupy at least 20GB memstore size for a single region 1449 */ 1450 public static final long MAX_FLUSH_PER_CHANGES = 1000000000; // 1G 1451 1452 /** 1453 * Close down this HRegion. Flush the cache unless abort parameter is true, 1454 * Shut down each HStore, don't service any more calls. 1455 * 1456 * This method could take some time to execute, so don't call it from a 1457 * time-sensitive thread. 1458 * 1459 * @param abort true if server is aborting (only during testing) 1460 * @return Vector of all the storage files that the HRegion's component 1461 * HStores make use of. It's a list of StoreFile objects. Can be null if 1462 * we are not to close at this time or we are already closed. 1463 * 1464 * @throws IOException e 1465 * @throws DroppedSnapshotException Thrown when replay of wal is required 1466 * because a Snapshot was not properly persisted. The region is put in closing mode, and the 1467 * caller MUST abort after this. 1468 */ 1469 public Map<byte[], List<HStoreFile>> close(boolean abort) throws IOException { 1470 // Only allow one thread to close at a time. Serialize them so dual 1471 // threads attempting to close will run up against each other. 1472 MonitoredTask status = TaskMonitor.get().createStatus( 1473 "Closing region " + this.getRegionInfo().getEncodedName() + 1474 (abort ? " due to abort" : "")); 1475 1476 status.setStatus("Waiting for close lock"); 1477 try { 1478 synchronized (closeLock) { 1479 return doClose(abort, status); 1480 } 1481 } finally { 1482 status.cleanup(); 1483 } 1484 } 1485 1486 /** 1487 * Exposed for some very specific unit tests. 1488 */ 1489 @VisibleForTesting 1490 public void setClosing(boolean closing) { 1491 this.closing.set(closing); 1492 } 1493 1494 /** 1495 * The {@link HRegion#doClose} will block forever if someone tries proving the dead lock via the unit test. 1496 * Instead of blocking, the {@link HRegion#doClose} will throw exception if you set the timeout. 1497 * @param timeoutForWriteLock the second time to wait for the write lock in {@link HRegion#doClose} 1498 */ 1499 @VisibleForTesting 1500 public void setTimeoutForWriteLock(long timeoutForWriteLock) { 1501 assert timeoutForWriteLock >= 0; 1502 this.timeoutForWriteLock = timeoutForWriteLock; 1503 } 1504 1505 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UL_UNRELEASED_LOCK_EXCEPTION_PATH", 1506 justification="I think FindBugs is confused") 1507 private Map<byte[], List<HStoreFile>> doClose(boolean abort, MonitoredTask status) 1508 throws IOException { 1509 if (isClosed()) { 1510 LOG.warn("Region " + this + " already closed"); 1511 return null; 1512 } 1513 1514 if (coprocessorHost != null) { 1515 status.setStatus("Running coprocessor pre-close hooks"); 1516 this.coprocessorHost.preClose(abort); 1517 } 1518 status.setStatus("Disabling compacts and flushes for region"); 1519 boolean canFlush = true; 1520 synchronized (writestate) { 1521 // Disable compacting and flushing by background threads for this 1522 // region. 1523 canFlush = !writestate.readOnly; 1524 writestate.writesEnabled = false; 1525 LOG.debug("Closing {}, disabling compactions & flushes", 1526 this.getRegionInfo().getEncodedName()); 1527 waitForFlushesAndCompactions(); 1528 } 1529 // If we were not just flushing, is it worth doing a preflush...one 1530 // that will clear out of the bulk of the memstore before we put up 1531 // the close flag? 1532 if (!abort && worthPreFlushing() && canFlush) { 1533 status.setStatus("Pre-flushing region before close"); 1534 LOG.info("Running close preflush of {}", this.getRegionInfo().getEncodedName()); 1535 try { 1536 internalFlushcache(status); 1537 } catch (IOException ioe) { 1538 // Failed to flush the region. Keep going. 1539 status.setStatus("Failed pre-flush " + this + "; " + ioe.getMessage()); 1540 } 1541 } 1542 1543 if (timeoutForWriteLock == null 1544 || timeoutForWriteLock == Long.MAX_VALUE) { 1545 // block waiting for the lock for closing 1546 lock.writeLock().lock(); // FindBugs: Complains UL_UNRELEASED_LOCK_EXCEPTION_PATH but seems fine 1547 } else { 1548 try { 1549 boolean succeed = lock.writeLock().tryLock(timeoutForWriteLock, TimeUnit.SECONDS); 1550 if (!succeed) { 1551 throw new IOException("Failed to get write lock when closing region"); 1552 } 1553 } catch (InterruptedException e) { 1554 throw (InterruptedIOException) new InterruptedIOException().initCause(e); 1555 } 1556 } 1557 this.closing.set(true); 1558 status.setStatus("Disabling writes for close"); 1559 try { 1560 if (this.isClosed()) { 1561 status.abort("Already got closed by another process"); 1562 // SplitTransaction handles the null 1563 return null; 1564 } 1565 LOG.debug("Updates disabled for region " + this); 1566 // Don't flush the cache if we are aborting 1567 if (!abort && canFlush) { 1568 int failedfFlushCount = 0; 1569 int flushCount = 0; 1570 long tmp = 0; 1571 long remainingSize = this.memStoreSizing.getDataSize(); 1572 while (remainingSize > 0) { 1573 try { 1574 internalFlushcache(status); 1575 if(flushCount >0) { 1576 LOG.info("Running extra flush, " + flushCount + 1577 " (carrying snapshot?) " + this); 1578 } 1579 flushCount++; 1580 tmp = this.memStoreSizing.getDataSize(); 1581 if (tmp >= remainingSize) { 1582 failedfFlushCount++; 1583 } 1584 remainingSize = tmp; 1585 if (failedfFlushCount > 5) { 1586 // If we failed 5 times and are unable to clear memory, abort 1587 // so we do not lose data 1588 throw new DroppedSnapshotException("Failed clearing memory after " + 1589 flushCount + " attempts on region: " + 1590 Bytes.toStringBinary(getRegionInfo().getRegionName())); 1591 } 1592 } catch (IOException ioe) { 1593 status.setStatus("Failed flush " + this + ", putting online again"); 1594 synchronized (writestate) { 1595 writestate.writesEnabled = true; 1596 } 1597 // Have to throw to upper layers. I can't abort server from here. 1598 throw ioe; 1599 } 1600 } 1601 } 1602 1603 Map<byte[], List<HStoreFile>> result = new TreeMap<>(Bytes.BYTES_COMPARATOR); 1604 if (!stores.isEmpty()) { 1605 // initialize the thread pool for closing stores in parallel. 1606 ThreadPoolExecutor storeCloserThreadPool = 1607 getStoreOpenAndCloseThreadPool("StoreCloserThread-" + 1608 getRegionInfo().getRegionNameAsString()); 1609 CompletionService<Pair<byte[], Collection<HStoreFile>>> completionService = 1610 new ExecutorCompletionService<>(storeCloserThreadPool); 1611 1612 // close each store in parallel 1613 for (HStore store : stores.values()) { 1614 MemStoreSize mss = store.getFlushableSize(); 1615 if (!(abort || mss.getDataSize() == 0 || writestate.readOnly)) { 1616 if (getRegionServerServices() != null) { 1617 getRegionServerServices().abort("Assertion failed while closing store " 1618 + getRegionInfo().getRegionNameAsString() + " " + store 1619 + ". flushableSize expected=0, actual={" + mss 1620 + "}. Current memStoreSize=" + this.memStoreSizing.getMemStoreSize() + 1621 ". Maybe a coprocessor " 1622 + "operation failed and left the memstore in a partially updated state.", null); 1623 } 1624 } 1625 completionService 1626 .submit(new Callable<Pair<byte[], Collection<HStoreFile>>>() { 1627 @Override 1628 public Pair<byte[], Collection<HStoreFile>> call() throws IOException { 1629 return new Pair<>(store.getColumnFamilyDescriptor().getName(), store.close()); 1630 } 1631 }); 1632 } 1633 try { 1634 for (int i = 0; i < stores.size(); i++) { 1635 Future<Pair<byte[], Collection<HStoreFile>>> future = completionService.take(); 1636 Pair<byte[], Collection<HStoreFile>> storeFiles = future.get(); 1637 List<HStoreFile> familyFiles = result.get(storeFiles.getFirst()); 1638 if (familyFiles == null) { 1639 familyFiles = new ArrayList<>(); 1640 result.put(storeFiles.getFirst(), familyFiles); 1641 } 1642 familyFiles.addAll(storeFiles.getSecond()); 1643 } 1644 } catch (InterruptedException e) { 1645 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1646 } catch (ExecutionException e) { 1647 Throwable cause = e.getCause(); 1648 if (cause instanceof IOException) { 1649 throw (IOException) cause; 1650 } 1651 throw new IOException(cause); 1652 } finally { 1653 storeCloserThreadPool.shutdownNow(); 1654 } 1655 } 1656 1657 status.setStatus("Writing region close event to WAL"); 1658 // Always write close marker to wal even for read only table. This is not a big problem as we 1659 // do not write any data into the region. 1660 if (!abort && wal != null && getRegionServerServices() != null && 1661 RegionReplicaUtil.isDefaultReplica(getRegionInfo())) { 1662 writeRegionCloseMarker(wal); 1663 } 1664 1665 this.closed.set(true); 1666 if (!canFlush) { 1667 decrMemStoreSize(this.memStoreSizing.getMemStoreSize()); 1668 } else if (this.memStoreSizing.getDataSize() != 0) { 1669 LOG.error("Memstore data size is {}", this.memStoreSizing.getDataSize()); 1670 } 1671 if (coprocessorHost != null) { 1672 status.setStatus("Running coprocessor post-close hooks"); 1673 this.coprocessorHost.postClose(abort); 1674 } 1675 if (this.metricsRegion != null) { 1676 this.metricsRegion.close(); 1677 } 1678 if (this.metricsRegionWrapper != null) { 1679 Closeables.close(this.metricsRegionWrapper, true); 1680 } 1681 status.markComplete("Closed"); 1682 LOG.info("Closed " + this); 1683 return result; 1684 } finally { 1685 lock.writeLock().unlock(); 1686 } 1687 } 1688 1689 /** Wait for all current flushes and compactions of the region to complete */ 1690 // TODO HBASE-18906. Check the usage (if any) in Phoenix and expose this or give alternate way for 1691 // Phoenix needs. 1692 public void waitForFlushesAndCompactions() { 1693 synchronized (writestate) { 1694 if (this.writestate.readOnly) { 1695 // we should not wait for replayed flushed if we are read only (for example in case the 1696 // region is a secondary replica). 1697 return; 1698 } 1699 boolean interrupted = false; 1700 try { 1701 while (writestate.compacting.get() > 0 || writestate.flushing) { 1702 LOG.debug("waiting for " + writestate.compacting + " compactions" 1703 + (writestate.flushing ? " & cache flush" : "") + " to complete for region " + this); 1704 try { 1705 writestate.wait(); 1706 } catch (InterruptedException iex) { 1707 // essentially ignore and propagate the interrupt back up 1708 LOG.warn("Interrupted while waiting"); 1709 interrupted = true; 1710 break; 1711 } 1712 } 1713 } finally { 1714 if (interrupted) { 1715 Thread.currentThread().interrupt(); 1716 } 1717 } 1718 } 1719 } 1720 1721 /** 1722 * Wait for all current flushes of the region to complete 1723 */ 1724 public void waitForFlushes() { 1725 waitForFlushes(0);// Unbound wait 1726 } 1727 1728 @Override 1729 public boolean waitForFlushes(long timeout) { 1730 synchronized (writestate) { 1731 if (this.writestate.readOnly) { 1732 // we should not wait for replayed flushed if we are read only (for example in case the 1733 // region is a secondary replica). 1734 return true; 1735 } 1736 if (!writestate.flushing) return true; 1737 long start = System.currentTimeMillis(); 1738 long duration = 0; 1739 boolean interrupted = false; 1740 LOG.debug("waiting for cache flush to complete for region " + this); 1741 try { 1742 while (writestate.flushing) { 1743 if (timeout > 0 && duration >= timeout) break; 1744 try { 1745 long toWait = timeout == 0 ? 0 : (timeout - duration); 1746 writestate.wait(toWait); 1747 } catch (InterruptedException iex) { 1748 // essentially ignore and propagate the interrupt back up 1749 LOG.warn("Interrupted while waiting"); 1750 interrupted = true; 1751 break; 1752 } finally { 1753 duration = System.currentTimeMillis() - start; 1754 } 1755 } 1756 } finally { 1757 if (interrupted) { 1758 Thread.currentThread().interrupt(); 1759 } 1760 } 1761 LOG.debug("Waited " + duration + " ms for flush to complete"); 1762 return !(writestate.flushing); 1763 } 1764 } 1765 1766 protected ThreadPoolExecutor getStoreOpenAndCloseThreadPool( 1767 final String threadNamePrefix) { 1768 int numStores = Math.max(1, this.htableDescriptor.getColumnFamilyCount()); 1769 int maxThreads = Math.min(numStores, 1770 conf.getInt(HConstants.HSTORE_OPEN_AND_CLOSE_THREADS_MAX, 1771 HConstants.DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX)); 1772 return getOpenAndCloseThreadPool(maxThreads, threadNamePrefix); 1773 } 1774 1775 protected ThreadPoolExecutor getStoreFileOpenAndCloseThreadPool( 1776 final String threadNamePrefix) { 1777 int numStores = Math.max(1, this.htableDescriptor.getColumnFamilyCount()); 1778 int maxThreads = Math.max(1, 1779 conf.getInt(HConstants.HSTORE_OPEN_AND_CLOSE_THREADS_MAX, 1780 HConstants.DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX) 1781 / numStores); 1782 return getOpenAndCloseThreadPool(maxThreads, threadNamePrefix); 1783 } 1784 1785 static ThreadPoolExecutor getOpenAndCloseThreadPool(int maxThreads, 1786 final String threadNamePrefix) { 1787 return Threads.getBoundedCachedThreadPool(maxThreads, 30L, TimeUnit.SECONDS, 1788 new ThreadFactory() { 1789 private int count = 1; 1790 1791 @Override 1792 public Thread newThread(Runnable r) { 1793 return new Thread(r, threadNamePrefix + "-" + count++); 1794 } 1795 }); 1796 } 1797 1798 /** 1799 * @return True if its worth doing a flush before we put up the close flag. 1800 */ 1801 private boolean worthPreFlushing() { 1802 return this.memStoreSizing.getDataSize() > 1803 this.conf.getLong("hbase.hregion.preclose.flush.size", 1024 * 1024 * 5); 1804 } 1805 1806 ////////////////////////////////////////////////////////////////////////////// 1807 // HRegion accessors 1808 ////////////////////////////////////////////////////////////////////////////// 1809 1810 @Override 1811 public TableDescriptor getTableDescriptor() { 1812 return this.htableDescriptor; 1813 } 1814 1815 @VisibleForTesting 1816 void setTableDescriptor(TableDescriptor desc) { 1817 htableDescriptor = desc; 1818 } 1819 1820 /** @return WAL in use for this region */ 1821 public WAL getWAL() { 1822 return this.wal; 1823 } 1824 1825 /** 1826 * @return split policy for this region. 1827 */ 1828 public RegionSplitPolicy getSplitPolicy() { 1829 return this.splitPolicy; 1830 } 1831 1832 /** 1833 * A split takes the config from the parent region & passes it to the daughter 1834 * region's constructor. If 'conf' was passed, you would end up using the HTD 1835 * of the parent region in addition to the new daughter HTD. Pass 'baseConf' 1836 * to the daughter regions to avoid this tricky dedupe problem. 1837 * @return Configuration object 1838 */ 1839 Configuration getBaseConf() { 1840 return this.baseConf; 1841 } 1842 1843 /** @return {@link FileSystem} being used by this region */ 1844 public FileSystem getFilesystem() { 1845 return fs.getFileSystem(); 1846 } 1847 1848 /** @return the {@link HRegionFileSystem} used by this region */ 1849 public HRegionFileSystem getRegionFileSystem() { 1850 return this.fs; 1851 } 1852 1853 /** @return the WAL {@link HRegionFileSystem} used by this region */ 1854 HRegionFileSystem getRegionWALFileSystem() throws IOException { 1855 return new HRegionFileSystem(conf, getWalFileSystem(), 1856 FSUtils.getWALTableDir(conf, htableDescriptor.getTableName()), fs.getRegionInfo()); 1857 } 1858 1859 /** @return the WAL {@link FileSystem} being used by this region */ 1860 FileSystem getWalFileSystem() throws IOException { 1861 if (walFS == null) { 1862 walFS = FSUtils.getWALFileSystem(conf); 1863 } 1864 return walFS; 1865 } 1866 1867 /** 1868 * @return the Region directory under WALRootDirectory 1869 * @throws IOException if there is an error getting WALRootDir 1870 */ 1871 @VisibleForTesting 1872 public Path getWALRegionDir() throws IOException { 1873 if (regionDir == null) { 1874 regionDir = FSUtils.getWALRegionDir(conf, getRegionInfo().getTable(), 1875 getRegionInfo().getEncodedName()); 1876 } 1877 return regionDir; 1878 } 1879 1880 @Override 1881 public long getEarliestFlushTimeForAllStores() { 1882 return Collections.min(lastStoreFlushTimeMap.values()); 1883 } 1884 1885 @Override 1886 public long getOldestHfileTs(boolean majorCompactionOnly) throws IOException { 1887 long result = Long.MAX_VALUE; 1888 for (HStore store : stores.values()) { 1889 Collection<HStoreFile> storeFiles = store.getStorefiles(); 1890 if (storeFiles == null) { 1891 continue; 1892 } 1893 for (HStoreFile file : storeFiles) { 1894 StoreFileReader sfReader = file.getReader(); 1895 if (sfReader == null) { 1896 continue; 1897 } 1898 HFile.Reader reader = sfReader.getHFileReader(); 1899 if (reader == null) { 1900 continue; 1901 } 1902 if (majorCompactionOnly) { 1903 byte[] val = reader.loadFileInfo().get(MAJOR_COMPACTION_KEY); 1904 if (val == null || !Bytes.toBoolean(val)) { 1905 continue; 1906 } 1907 } 1908 result = Math.min(result, reader.getFileContext().getFileCreateTime()); 1909 } 1910 } 1911 return result == Long.MAX_VALUE ? 0 : result; 1912 } 1913 1914 RegionLoad.Builder setCompleteSequenceId(RegionLoad.Builder regionLoadBldr) { 1915 long lastFlushOpSeqIdLocal = this.lastFlushOpSeqId; 1916 byte[] encodedRegionName = this.getRegionInfo().getEncodedNameAsBytes(); 1917 regionLoadBldr.clearStoreCompleteSequenceId(); 1918 for (byte[] familyName : this.stores.keySet()) { 1919 long earliest = this.wal.getEarliestMemStoreSeqNum(encodedRegionName, familyName); 1920 // Subtract - 1 to go earlier than the current oldest, unflushed edit in memstore; this will 1921 // give us a sequence id that is for sure flushed. We want edit replay to start after this 1922 // sequence id in this region. If NO_SEQNUM, use the regions maximum flush id. 1923 long csid = (earliest == HConstants.NO_SEQNUM)? lastFlushOpSeqIdLocal: earliest - 1; 1924 regionLoadBldr.addStoreCompleteSequenceId(StoreSequenceId.newBuilder() 1925 .setFamilyName(UnsafeByteOperations.unsafeWrap(familyName)).setSequenceId(csid).build()); 1926 } 1927 return regionLoadBldr.setCompleteSequenceId(getMaxFlushedSeqId()); 1928 } 1929 1930 ////////////////////////////////////////////////////////////////////////////// 1931 // HRegion maintenance. 1932 // 1933 // These methods are meant to be called periodically by the HRegionServer for 1934 // upkeep. 1935 ////////////////////////////////////////////////////////////////////////////// 1936 /** 1937 * Do preparation for pending compaction. 1938 * @throws IOException 1939 */ 1940 protected void doRegionCompactionPrep() throws IOException { 1941 } 1942 1943 /** 1944 * Synchronously compact all stores in the region. 1945 * <p>This operation could block for a long time, so don't call it from a 1946 * time-sensitive thread. 1947 * <p>Note that no locks are taken to prevent possible conflicts between 1948 * compaction and splitting activities. The regionserver does not normally compact 1949 * and split in parallel. However by calling this method you may introduce 1950 * unexpected and unhandled concurrency. Don't do this unless you know what 1951 * you are doing. 1952 * 1953 * @param majorCompaction True to force a major compaction regardless of thresholds 1954 * @throws IOException 1955 */ 1956 public void compact(boolean majorCompaction) throws IOException { 1957 if (majorCompaction) { 1958 stores.values().forEach(HStore::triggerMajorCompaction); 1959 } 1960 for (HStore s : stores.values()) { 1961 Optional<CompactionContext> compaction = s.requestCompaction(); 1962 if (compaction.isPresent()) { 1963 ThroughputController controller = null; 1964 if (rsServices != null) { 1965 controller = CompactionThroughputControllerFactory.create(rsServices, conf); 1966 } 1967 if (controller == null) { 1968 controller = NoLimitThroughputController.INSTANCE; 1969 } 1970 compact(compaction.get(), s, controller, null); 1971 } 1972 } 1973 } 1974 1975 /** 1976 * This is a helper function that compact all the stores synchronously. 1977 * <p> 1978 * It is used by utilities and testing 1979 */ 1980 @VisibleForTesting 1981 public void compactStores() throws IOException { 1982 for (HStore s : stores.values()) { 1983 Optional<CompactionContext> compaction = s.requestCompaction(); 1984 if (compaction.isPresent()) { 1985 compact(compaction.get(), s, NoLimitThroughputController.INSTANCE, null); 1986 } 1987 } 1988 } 1989 1990 /** 1991 * This is a helper function that compact the given store. 1992 * <p> 1993 * It is used by utilities and testing 1994 */ 1995 @VisibleForTesting 1996 void compactStore(byte[] family, ThroughputController throughputController) throws IOException { 1997 HStore s = getStore(family); 1998 Optional<CompactionContext> compaction = s.requestCompaction(); 1999 if (compaction.isPresent()) { 2000 compact(compaction.get(), s, throughputController, null); 2001 } 2002 } 2003 2004 /** 2005 * Called by compaction thread and after region is opened to compact the 2006 * HStores if necessary. 2007 * 2008 * <p>This operation could block for a long time, so don't call it from a 2009 * time-sensitive thread. 2010 * 2011 * Note that no locking is necessary at this level because compaction only 2012 * conflicts with a region split, and that cannot happen because the region 2013 * server does them sequentially and not in parallel. 2014 * 2015 * @param compaction Compaction details, obtained by requestCompaction() 2016 * @param throughputController 2017 * @return whether the compaction completed 2018 */ 2019 public boolean compact(CompactionContext compaction, HStore store, 2020 ThroughputController throughputController) throws IOException { 2021 return compact(compaction, store, throughputController, null); 2022 } 2023 2024 public boolean compact(CompactionContext compaction, HStore store, 2025 ThroughputController throughputController, User user) throws IOException { 2026 assert compaction != null && compaction.hasSelection(); 2027 assert !compaction.getRequest().getFiles().isEmpty(); 2028 if (this.closing.get() || this.closed.get()) { 2029 LOG.debug("Skipping compaction on " + this + " because closing/closed"); 2030 store.cancelRequestedCompaction(compaction); 2031 return false; 2032 } 2033 MonitoredTask status = null; 2034 boolean requestNeedsCancellation = true; 2035 /* 2036 * We are trying to remove / relax the region read lock for compaction. 2037 * Let's see what are the potential race conditions among the operations (user scan, 2038 * region split, region close and region bulk load). 2039 * 2040 * user scan ---> region read lock 2041 * region split --> region close first --> region write lock 2042 * region close --> region write lock 2043 * region bulk load --> region write lock 2044 * 2045 * read lock is compatible with read lock. ---> no problem with user scan/read 2046 * region bulk load does not cause problem for compaction (no consistency problem, store lock 2047 * will help the store file accounting). 2048 * They can run almost concurrently at the region level. 2049 * 2050 * The only remaining race condition is between the region close and compaction. 2051 * So we will evaluate, below, how region close intervenes with compaction if compaction does 2052 * not acquire region read lock. 2053 * 2054 * Here are the steps for compaction: 2055 * 1. obtain list of StoreFile's 2056 * 2. create StoreFileScanner's based on list from #1 2057 * 3. perform compaction and save resulting files under tmp dir 2058 * 4. swap in compacted files 2059 * 2060 * #1 is guarded by store lock. This patch does not change this --> no worse or better 2061 * For #2, we obtain smallest read point (for region) across all the Scanners (for both default 2062 * compactor and stripe compactor). 2063 * The read points are for user scans. Region keeps the read points for all currently open 2064 * user scanners. 2065 * Compaction needs to know the smallest read point so that during re-write of the hfiles, 2066 * it can remove the mvcc points for the cells if their mvccs are older than the smallest 2067 * since they are not needed anymore. 2068 * This will not conflict with compaction. 2069 * For #3, it can be performed in parallel to other operations. 2070 * For #4 bulk load and compaction don't conflict with each other on the region level 2071 * (for multi-family atomicy). 2072 * Region close and compaction are guarded pretty well by the 'writestate'. 2073 * In HRegion#doClose(), we have : 2074 * synchronized (writestate) { 2075 * // Disable compacting and flushing by background threads for this 2076 * // region. 2077 * canFlush = !writestate.readOnly; 2078 * writestate.writesEnabled = false; 2079 * LOG.debug("Closing " + this + ": disabling compactions & flushes"); 2080 * waitForFlushesAndCompactions(); 2081 * } 2082 * waitForFlushesAndCompactions() would wait for writestate.compacting to come down to 0. 2083 * and in HRegion.compact() 2084 * try { 2085 * synchronized (writestate) { 2086 * if (writestate.writesEnabled) { 2087 * wasStateSet = true; 2088 * ++writestate.compacting; 2089 * } else { 2090 * String msg = "NOT compacting region " + this + ". Writes disabled."; 2091 * LOG.info(msg); 2092 * status.abort(msg); 2093 * return false; 2094 * } 2095 * } 2096 * Also in compactor.performCompaction(): 2097 * check periodically to see if a system stop is requested 2098 * if (closeCheckInterval > 0) { 2099 * bytesWritten += len; 2100 * if (bytesWritten > closeCheckInterval) { 2101 * bytesWritten = 0; 2102 * if (!store.areWritesEnabled()) { 2103 * progress.cancel(); 2104 * return false; 2105 * } 2106 * } 2107 * } 2108 */ 2109 try { 2110 byte[] cf = Bytes.toBytes(store.getColumnFamilyName()); 2111 if (stores.get(cf) != store) { 2112 LOG.warn("Store " + store.getColumnFamilyName() + " on region " + this 2113 + " has been re-instantiated, cancel this compaction request. " 2114 + " It may be caused by the roll back of split transaction"); 2115 return false; 2116 } 2117 2118 status = TaskMonitor.get().createStatus("Compacting " + store + " in " + this); 2119 status.enableStatusJournal(false); 2120 if (this.closed.get()) { 2121 String msg = "Skipping compaction on " + this + " because closed"; 2122 LOG.debug(msg); 2123 status.abort(msg); 2124 return false; 2125 } 2126 boolean wasStateSet = false; 2127 try { 2128 synchronized (writestate) { 2129 if (writestate.writesEnabled) { 2130 wasStateSet = true; 2131 writestate.compacting.incrementAndGet(); 2132 } else { 2133 String msg = "NOT compacting region " + this + ". Writes disabled."; 2134 LOG.info(msg); 2135 status.abort(msg); 2136 return false; 2137 } 2138 } 2139 LOG.info("Starting compaction of {} in {}{}", store, this, 2140 (compaction.getRequest().isOffPeak()?" as an off-peak compaction":"")); 2141 doRegionCompactionPrep(); 2142 try { 2143 status.setStatus("Compacting store " + store); 2144 // We no longer need to cancel the request on the way out of this 2145 // method because Store#compact will clean up unconditionally 2146 requestNeedsCancellation = false; 2147 store.compact(compaction, throughputController, user); 2148 } catch (InterruptedIOException iioe) { 2149 String msg = "compaction interrupted"; 2150 LOG.info(msg, iioe); 2151 status.abort(msg); 2152 return false; 2153 } 2154 } finally { 2155 if (wasStateSet) { 2156 synchronized (writestate) { 2157 writestate.compacting.decrementAndGet(); 2158 if (writestate.compacting.get() <= 0) { 2159 writestate.notifyAll(); 2160 } 2161 } 2162 } 2163 } 2164 status.markComplete("Compaction complete"); 2165 return true; 2166 } finally { 2167 if (requestNeedsCancellation) store.cancelRequestedCompaction(compaction); 2168 if (status != null) { 2169 LOG.debug("Compaction status journal:\n\t" + status.prettyPrintJournal()); 2170 status.cleanup(); 2171 } 2172 } 2173 } 2174 2175 /** 2176 * Flush the cache. 2177 * 2178 * <p>When this method is called the cache will be flushed unless: 2179 * <ol> 2180 * <li>the cache is empty</li> 2181 * <li>the region is closed.</li> 2182 * <li>a flush is already in progress</li> 2183 * <li>writes are disabled</li> 2184 * </ol> 2185 * 2186 * <p>This method may block for some time, so it should not be called from a 2187 * time-sensitive thread. 2188 * @param force whether we want to force a flush of all stores 2189 * @return FlushResult indicating whether the flush was successful or not and if 2190 * the region needs compacting 2191 * 2192 * @throws IOException general io exceptions 2193 * because a snapshot was not properly persisted. 2194 */ 2195 // TODO HBASE-18905. We might have to expose a requestFlush API for CPs 2196 public FlushResult flush(boolean force) throws IOException { 2197 return flushcache(force, false, FlushLifeCycleTracker.DUMMY); 2198 } 2199 2200 public interface FlushResult { 2201 enum Result { 2202 FLUSHED_NO_COMPACTION_NEEDED, 2203 FLUSHED_COMPACTION_NEEDED, 2204 // Special case where a flush didn't run because there's nothing in the memstores. Used when 2205 // bulk loading to know when we can still load even if a flush didn't happen. 2206 CANNOT_FLUSH_MEMSTORE_EMPTY, 2207 CANNOT_FLUSH 2208 } 2209 2210 /** @return the detailed result code */ 2211 Result getResult(); 2212 2213 /** @return true if the memstores were flushed, else false */ 2214 boolean isFlushSucceeded(); 2215 2216 /** @return True if the flush requested a compaction, else false */ 2217 boolean isCompactionNeeded(); 2218 } 2219 2220 /** 2221 * Flush the cache. 2222 * 2223 * When this method is called the cache will be flushed unless: 2224 * <ol> 2225 * <li>the cache is empty</li> 2226 * <li>the region is closed.</li> 2227 * <li>a flush is already in progress</li> 2228 * <li>writes are disabled</li> 2229 * </ol> 2230 * 2231 * <p>This method may block for some time, so it should not be called from a 2232 * time-sensitive thread. 2233 * @param forceFlushAllStores whether we want to flush all stores 2234 * @param writeFlushRequestWalMarker whether to write the flush request marker to WAL 2235 * @param tracker used to track the life cycle of this flush 2236 * @return whether the flush is success and whether the region needs compacting 2237 * 2238 * @throws IOException general io exceptions 2239 * @throws DroppedSnapshotException Thrown when replay of wal is required 2240 * because a Snapshot was not properly persisted. The region is put in closing mode, and the 2241 * caller MUST abort after this. 2242 */ 2243 public FlushResultImpl flushcache(boolean forceFlushAllStores, boolean writeFlushRequestWalMarker, 2244 FlushLifeCycleTracker tracker) throws IOException { 2245 // fail-fast instead of waiting on the lock 2246 if (this.closing.get()) { 2247 String msg = "Skipping flush on " + this + " because closing"; 2248 LOG.debug(msg); 2249 return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false); 2250 } 2251 MonitoredTask status = TaskMonitor.get().createStatus("Flushing " + this); 2252 status.enableStatusJournal(false); 2253 status.setStatus("Acquiring readlock on region"); 2254 // block waiting for the lock for flushing cache 2255 lock.readLock().lock(); 2256 try { 2257 if (this.closed.get()) { 2258 String msg = "Skipping flush on " + this + " because closed"; 2259 LOG.debug(msg); 2260 status.abort(msg); 2261 return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false); 2262 } 2263 if (coprocessorHost != null) { 2264 status.setStatus("Running coprocessor pre-flush hooks"); 2265 coprocessorHost.preFlush(tracker); 2266 } 2267 // TODO: this should be managed within memstore with the snapshot, updated only after flush 2268 // successful 2269 if (numMutationsWithoutWAL.sum() > 0) { 2270 numMutationsWithoutWAL.reset(); 2271 dataInMemoryWithoutWAL.reset(); 2272 } 2273 synchronized (writestate) { 2274 if (!writestate.flushing && writestate.writesEnabled) { 2275 this.writestate.flushing = true; 2276 } else { 2277 if (LOG.isDebugEnabled()) { 2278 LOG.debug("NOT flushing memstore for region " + this 2279 + ", flushing=" + writestate.flushing + ", writesEnabled=" 2280 + writestate.writesEnabled); 2281 } 2282 String msg = "Not flushing since " 2283 + (writestate.flushing ? "already flushing" 2284 : "writes not enabled"); 2285 status.abort(msg); 2286 return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false); 2287 } 2288 } 2289 2290 try { 2291 Collection<HStore> specificStoresToFlush = 2292 forceFlushAllStores ? stores.values() : flushPolicy.selectStoresToFlush(); 2293 FlushResultImpl fs = 2294 internalFlushcache(specificStoresToFlush, status, writeFlushRequestWalMarker, tracker); 2295 2296 if (coprocessorHost != null) { 2297 status.setStatus("Running post-flush coprocessor hooks"); 2298 coprocessorHost.postFlush(tracker); 2299 } 2300 2301 if(fs.isFlushSucceeded()) { 2302 flushesQueued.reset(); 2303 } 2304 2305 status.markComplete("Flush successful"); 2306 return fs; 2307 } finally { 2308 synchronized (writestate) { 2309 writestate.flushing = false; 2310 this.writestate.flushRequested = false; 2311 writestate.notifyAll(); 2312 } 2313 } 2314 } finally { 2315 lock.readLock().unlock(); 2316 LOG.debug("Flush status journal:\n\t" + status.prettyPrintJournal()); 2317 status.cleanup(); 2318 } 2319 } 2320 2321 /** 2322 * Should the store be flushed because it is old enough. 2323 * <p> 2324 * Every FlushPolicy should call this to determine whether a store is old enough to flush (except 2325 * that you always flush all stores). Otherwise the method will always 2326 * returns true which will make a lot of flush requests. 2327 */ 2328 boolean shouldFlushStore(HStore store) { 2329 long earliest = this.wal.getEarliestMemStoreSeqNum(getRegionInfo().getEncodedNameAsBytes(), 2330 store.getColumnFamilyDescriptor().getName()) - 1; 2331 if (earliest > 0 && earliest + flushPerChanges < mvcc.getReadPoint()) { 2332 if (LOG.isDebugEnabled()) { 2333 LOG.debug("Flush column family " + store.getColumnFamilyName() + " of " + 2334 getRegionInfo().getEncodedName() + " because unflushed sequenceid=" + earliest + 2335 " is > " + this.flushPerChanges + " from current=" + mvcc.getReadPoint()); 2336 } 2337 return true; 2338 } 2339 if (this.flushCheckInterval <= 0) { 2340 return false; 2341 } 2342 long now = EnvironmentEdgeManager.currentTime(); 2343 if (store.timeOfOldestEdit() < now - this.flushCheckInterval) { 2344 if (LOG.isDebugEnabled()) { 2345 LOG.debug("Flush column family: " + store.getColumnFamilyName() + " of " + 2346 getRegionInfo().getEncodedName() + " because time of oldest edit=" + 2347 store.timeOfOldestEdit() + " is > " + this.flushCheckInterval + " from now =" + now); 2348 } 2349 return true; 2350 } 2351 return false; 2352 } 2353 2354 /** 2355 * Should the memstore be flushed now 2356 */ 2357 boolean shouldFlush(final StringBuilder whyFlush) { 2358 whyFlush.setLength(0); 2359 // This is a rough measure. 2360 if (this.maxFlushedSeqId > 0 2361 && (this.maxFlushedSeqId + this.flushPerChanges < this.mvcc.getReadPoint())) { 2362 whyFlush.append("more than max edits, " + this.flushPerChanges + ", since last flush"); 2363 return true; 2364 } 2365 long modifiedFlushCheckInterval = flushCheckInterval; 2366 if (getRegionInfo().getTable().isSystemTable() && 2367 getRegionInfo().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) { 2368 modifiedFlushCheckInterval = SYSTEM_CACHE_FLUSH_INTERVAL; 2369 } 2370 if (modifiedFlushCheckInterval <= 0) { //disabled 2371 return false; 2372 } 2373 long now = EnvironmentEdgeManager.currentTime(); 2374 //if we flushed in the recent past, we don't need to do again now 2375 if ((now - getEarliestFlushTimeForAllStores() < modifiedFlushCheckInterval)) { 2376 return false; 2377 } 2378 //since we didn't flush in the recent past, flush now if certain conditions 2379 //are met. Return true on first such memstore hit. 2380 for (HStore s : stores.values()) { 2381 if (s.timeOfOldestEdit() < now - modifiedFlushCheckInterval) { 2382 // we have an old enough edit in the memstore, flush 2383 whyFlush.append(s.toString() + " has an old edit so flush to free WALs"); 2384 return true; 2385 } 2386 } 2387 return false; 2388 } 2389 2390 /** 2391 * Flushing all stores. 2392 * @see #internalFlushcache(Collection, MonitoredTask, boolean, FlushLifeCycleTracker) 2393 */ 2394 private FlushResult internalFlushcache(MonitoredTask status) throws IOException { 2395 return internalFlushcache(stores.values(), status, false, FlushLifeCycleTracker.DUMMY); 2396 } 2397 2398 /** 2399 * Flushing given stores. 2400 * @see #internalFlushcache(WAL, long, Collection, MonitoredTask, boolean, FlushLifeCycleTracker) 2401 */ 2402 private FlushResultImpl internalFlushcache(Collection<HStore> storesToFlush, MonitoredTask status, 2403 boolean writeFlushWalMarker, FlushLifeCycleTracker tracker) throws IOException { 2404 return internalFlushcache(this.wal, HConstants.NO_SEQNUM, storesToFlush, status, 2405 writeFlushWalMarker, tracker); 2406 } 2407 2408 /** 2409 * Flush the memstore. Flushing the memstore is a little tricky. We have a lot of updates in the 2410 * memstore, all of which have also been written to the wal. We need to write those updates in the 2411 * memstore out to disk, while being able to process reads/writes as much as possible during the 2412 * flush operation. 2413 * <p> 2414 * This method may block for some time. Every time you call it, we up the regions sequence id even 2415 * if we don't flush; i.e. the returned region id will be at least one larger than the last edit 2416 * applied to this region. The returned id does not refer to an actual edit. The returned id can 2417 * be used for say installing a bulk loaded file just ahead of the last hfile that was the result 2418 * of this flush, etc. 2419 * @param wal Null if we're NOT to go via wal. 2420 * @param myseqid The seqid to use if <code>wal</code> is null writing out flush file. 2421 * @param storesToFlush The list of stores to flush. 2422 * @return object describing the flush's state 2423 * @throws IOException general io exceptions 2424 * @throws DroppedSnapshotException Thrown when replay of WAL is required. 2425 */ 2426 protected FlushResultImpl internalFlushcache(WAL wal, long myseqid, 2427 Collection<HStore> storesToFlush, MonitoredTask status, boolean writeFlushWalMarker, 2428 FlushLifeCycleTracker tracker) throws IOException { 2429 PrepareFlushResult result = 2430 internalPrepareFlushCache(wal, myseqid, storesToFlush, status, writeFlushWalMarker, tracker); 2431 if (result.result == null) { 2432 return internalFlushCacheAndCommit(wal, status, result, storesToFlush); 2433 } else { 2434 return result.result; // early exit due to failure from prepare stage 2435 } 2436 } 2437 2438 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="DLS_DEAD_LOCAL_STORE", 2439 justification="FindBugs seems confused about trxId") 2440 protected PrepareFlushResult internalPrepareFlushCache(WAL wal, long myseqid, 2441 Collection<HStore> storesToFlush, MonitoredTask status, boolean writeFlushWalMarker, 2442 FlushLifeCycleTracker tracker) throws IOException { 2443 if (this.rsServices != null && this.rsServices.isAborted()) { 2444 // Don't flush when server aborting, it's unsafe 2445 throw new IOException("Aborting flush because server is aborted..."); 2446 } 2447 final long startTime = EnvironmentEdgeManager.currentTime(); 2448 // If nothing to flush, return, but return with a valid unused sequenceId. 2449 // Its needed by bulk upload IIRC. It flushes until no edits in memory so it can insert a 2450 // bulk loaded file between memory and existing hfiles. It wants a good seqeunceId that belongs 2451 // to no other that it can use to associate with the bulk load. Hence this little dance below 2452 // to go get one. 2453 if (this.memStoreSizing.getDataSize() <= 0) { 2454 // Take an update lock so no edits can come into memory just yet. 2455 this.updatesLock.writeLock().lock(); 2456 WriteEntry writeEntry = null; 2457 try { 2458 if (this.memStoreSizing.getDataSize() <= 0) { 2459 // Presume that if there are still no edits in the memstore, then there are no edits for 2460 // this region out in the WAL subsystem so no need to do any trickery clearing out 2461 // edits in the WAL sub-system. Up the sequence number so the resulting flush id is for 2462 // sure just beyond the last appended region edit and not associated with any edit 2463 // (useful as marker when bulk loading, etc.). 2464 if (wal != null) { 2465 writeEntry = mvcc.begin(); 2466 long flushOpSeqId = writeEntry.getWriteNumber(); 2467 FlushResultImpl flushResult = 2468 new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, flushOpSeqId, 2469 "Nothing to flush", writeFlushRequestMarkerToWAL(wal, writeFlushWalMarker)); 2470 mvcc.completeAndWait(writeEntry); 2471 // Set to null so we don't complete it again down in finally block. 2472 writeEntry = null; 2473 return new PrepareFlushResult(flushResult, myseqid); 2474 } else { 2475 return new PrepareFlushResult(new FlushResultImpl( 2476 FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, "Nothing to flush", false), myseqid); 2477 } 2478 } 2479 } finally { 2480 if (writeEntry != null) { 2481 // If writeEntry is non-null, this operation failed; the mvcc transaction failed... 2482 // but complete it anyways so it doesn't block the mvcc queue. 2483 mvcc.complete(writeEntry); 2484 } 2485 this.updatesLock.writeLock().unlock(); 2486 } 2487 } 2488 logFatLineOnFlush(storesToFlush, myseqid); 2489 // Stop updates while we snapshot the memstore of all of these regions' stores. We only have 2490 // to do this for a moment. It is quick. We also set the memstore size to zero here before we 2491 // allow updates again so its value will represent the size of the updates received 2492 // during flush 2493 2494 // We have to take an update lock during snapshot, or else a write could end up in both snapshot 2495 // and memstore (makes it difficult to do atomic rows then) 2496 status.setStatus("Obtaining lock to block concurrent updates"); 2497 // block waiting for the lock for internal flush 2498 this.updatesLock.writeLock().lock(); 2499 status.setStatus("Preparing flush snapshotting stores in " + getRegionInfo().getEncodedName()); 2500 MemStoreSizing totalSizeOfFlushableStores = new NonThreadSafeMemStoreSizing(); 2501 2502 Map<byte[], Long> flushedFamilyNamesToSeq = new HashMap<>(); 2503 for (HStore store : storesToFlush) { 2504 flushedFamilyNamesToSeq.put(store.getColumnFamilyDescriptor().getName(), 2505 store.preFlushSeqIDEstimation()); 2506 } 2507 2508 TreeMap<byte[], StoreFlushContext> storeFlushCtxs = new TreeMap<>(Bytes.BYTES_COMPARATOR); 2509 TreeMap<byte[], List<Path>> committedFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR); 2510 TreeMap<byte[], MemStoreSize> storeFlushableSize = new TreeMap<>(Bytes.BYTES_COMPARATOR); 2511 // The sequence id of this flush operation which is used to log FlushMarker and pass to 2512 // createFlushContext to use as the store file's sequence id. It can be in advance of edits 2513 // still in the memstore, edits that are in other column families yet to be flushed. 2514 long flushOpSeqId = HConstants.NO_SEQNUM; 2515 // The max flushed sequence id after this flush operation completes. All edits in memstore 2516 // will be in advance of this sequence id. 2517 long flushedSeqId = HConstants.NO_SEQNUM; 2518 byte[] encodedRegionName = getRegionInfo().getEncodedNameAsBytes(); 2519 try { 2520 if (wal != null) { 2521 Long earliestUnflushedSequenceIdForTheRegion = 2522 wal.startCacheFlush(encodedRegionName, flushedFamilyNamesToSeq); 2523 if (earliestUnflushedSequenceIdForTheRegion == null) { 2524 // This should never happen. This is how startCacheFlush signals flush cannot proceed. 2525 String msg = this.getRegionInfo().getEncodedName() + " flush aborted; WAL closing."; 2526 status.setStatus(msg); 2527 return new PrepareFlushResult( 2528 new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false), 2529 myseqid); 2530 } 2531 flushOpSeqId = getNextSequenceId(wal); 2532 // Back up 1, minus 1 from oldest sequence id in memstore to get last 'flushed' edit 2533 flushedSeqId = 2534 earliestUnflushedSequenceIdForTheRegion.longValue() == HConstants.NO_SEQNUM? 2535 flushOpSeqId: earliestUnflushedSequenceIdForTheRegion.longValue() - 1; 2536 } else { 2537 // use the provided sequence Id as WAL is not being used for this flush. 2538 flushedSeqId = flushOpSeqId = myseqid; 2539 } 2540 2541 for (HStore s : storesToFlush) { 2542 storeFlushCtxs.put(s.getColumnFamilyDescriptor().getName(), 2543 s.createFlushContext(flushOpSeqId, tracker)); 2544 // for writing stores to WAL 2545 committedFiles.put(s.getColumnFamilyDescriptor().getName(), null); 2546 } 2547 2548 // write the snapshot start to WAL 2549 if (wal != null && !writestate.readOnly) { 2550 FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.START_FLUSH, 2551 getRegionInfo(), flushOpSeqId, committedFiles); 2552 // No sync. Sync is below where no updates lock and we do FlushAction.COMMIT_FLUSH 2553 WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, false, 2554 mvcc); 2555 } 2556 2557 // Prepare flush (take a snapshot) 2558 storeFlushCtxs.forEach((name, flush) -> { 2559 MemStoreSize snapshotSize = flush.prepare(); 2560 totalSizeOfFlushableStores.incMemStoreSize(snapshotSize); 2561 storeFlushableSize.put(name, snapshotSize); 2562 }); 2563 } catch (IOException ex) { 2564 doAbortFlushToWAL(wal, flushOpSeqId, committedFiles); 2565 throw ex; 2566 } finally { 2567 this.updatesLock.writeLock().unlock(); 2568 } 2569 String s = "Finished memstore snapshotting " + this + ", syncing WAL and waiting on mvcc, " + 2570 "flushsize=" + totalSizeOfFlushableStores; 2571 status.setStatus(s); 2572 doSyncOfUnflushedWALChanges(wal, getRegionInfo()); 2573 return new PrepareFlushResult(storeFlushCtxs, committedFiles, storeFlushableSize, startTime, 2574 flushOpSeqId, flushedSeqId, totalSizeOfFlushableStores); 2575 } 2576 2577 /** 2578 * Utility method broken out of internalPrepareFlushCache so that method is smaller. 2579 */ 2580 private void logFatLineOnFlush(Collection<HStore> storesToFlush, long sequenceId) { 2581 if (!LOG.isInfoEnabled()) { 2582 return; 2583 } 2584 // Log a fat line detailing what is being flushed. 2585 StringBuilder perCfExtras = null; 2586 if (!isAllFamilies(storesToFlush)) { 2587 perCfExtras = new StringBuilder(); 2588 for (HStore store: storesToFlush) { 2589 MemStoreSize mss = store.getFlushableSize(); 2590 perCfExtras.append("; ").append(store.getColumnFamilyName()); 2591 perCfExtras.append("={dataSize=") 2592 .append(StringUtils.byteDesc(mss.getDataSize())); 2593 perCfExtras.append(", heapSize=") 2594 .append(StringUtils.byteDesc(mss.getHeapSize())); 2595 perCfExtras.append(", offHeapSize=") 2596 .append(StringUtils.byteDesc(mss.getOffHeapSize())); 2597 perCfExtras.append("}"); 2598 } 2599 } 2600 MemStoreSize mss = this.memStoreSizing.getMemStoreSize(); 2601 LOG.info("Flushing " + + storesToFlush.size() + "/" + stores.size() + " column families," + 2602 " dataSize=" + StringUtils.byteDesc(mss.getDataSize()) + 2603 " heapSize=" + StringUtils.byteDesc(mss.getHeapSize()) + 2604 ((perCfExtras != null && perCfExtras.length() > 0)? perCfExtras.toString(): "") + 2605 ((wal != null) ? "" : "; WAL is null, using passed sequenceid=" + sequenceId)); 2606 } 2607 2608 private void doAbortFlushToWAL(final WAL wal, final long flushOpSeqId, 2609 final Map<byte[], List<Path>> committedFiles) { 2610 if (wal == null) return; 2611 try { 2612 FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.ABORT_FLUSH, 2613 getRegionInfo(), flushOpSeqId, committedFiles); 2614 WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, false, 2615 mvcc); 2616 } catch (Throwable t) { 2617 LOG.warn("Received unexpected exception trying to write ABORT_FLUSH marker to WAL:" + 2618 StringUtils.stringifyException(t)); 2619 // ignore this since we will be aborting the RS with DSE. 2620 } 2621 // we have called wal.startCacheFlush(), now we have to abort it 2622 wal.abortCacheFlush(this.getRegionInfo().getEncodedNameAsBytes()); 2623 } 2624 2625 /** 2626 * Sync unflushed WAL changes. See HBASE-8208 for details 2627 */ 2628 private static void doSyncOfUnflushedWALChanges(final WAL wal, final RegionInfo hri) 2629 throws IOException { 2630 if (wal == null) { 2631 return; 2632 } 2633 try { 2634 wal.sync(); // ensure that flush marker is sync'ed 2635 } catch (IOException ioe) { 2636 wal.abortCacheFlush(hri.getEncodedNameAsBytes()); 2637 throw ioe; 2638 } 2639 } 2640 2641 /** 2642 * @return True if passed Set is all families in the region. 2643 */ 2644 private boolean isAllFamilies(Collection<HStore> families) { 2645 return families == null || this.stores.size() == families.size(); 2646 } 2647 2648 /** 2649 * Writes a marker to WAL indicating a flush is requested but cannot be complete due to various 2650 * reasons. Ignores exceptions from WAL. Returns whether the write succeeded. 2651 * @param wal 2652 * @return whether WAL write was successful 2653 */ 2654 private boolean writeFlushRequestMarkerToWAL(WAL wal, boolean writeFlushWalMarker) { 2655 if (writeFlushWalMarker && wal != null && !writestate.readOnly) { 2656 FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.CANNOT_FLUSH, 2657 getRegionInfo(), -1, new TreeMap<>(Bytes.BYTES_COMPARATOR)); 2658 try { 2659 WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, true, 2660 mvcc); 2661 return true; 2662 } catch (IOException e) { 2663 LOG.warn(getRegionInfo().getEncodedName() + " : " 2664 + "Received exception while trying to write the flush request to wal", e); 2665 } 2666 } 2667 return false; 2668 } 2669 2670 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY", 2671 justification="Intentional; notify is about completed flush") 2672 protected FlushResultImpl internalFlushCacheAndCommit(WAL wal, MonitoredTask status, 2673 PrepareFlushResult prepareResult, Collection<HStore> storesToFlush) throws IOException { 2674 // prepare flush context is carried via PrepareFlushResult 2675 TreeMap<byte[], StoreFlushContext> storeFlushCtxs = prepareResult.storeFlushCtxs; 2676 TreeMap<byte[], List<Path>> committedFiles = prepareResult.committedFiles; 2677 long startTime = prepareResult.startTime; 2678 long flushOpSeqId = prepareResult.flushOpSeqId; 2679 long flushedSeqId = prepareResult.flushedSeqId; 2680 2681 String s = "Flushing stores of " + this; 2682 status.setStatus(s); 2683 if (LOG.isTraceEnabled()) LOG.trace(s); 2684 2685 // Any failure from here on out will be catastrophic requiring server 2686 // restart so wal content can be replayed and put back into the memstore. 2687 // Otherwise, the snapshot content while backed up in the wal, it will not 2688 // be part of the current running servers state. 2689 boolean compactionRequested = false; 2690 long flushedOutputFileSize = 0; 2691 try { 2692 // A. Flush memstore to all the HStores. 2693 // Keep running vector of all store files that includes both old and the 2694 // just-made new flush store file. The new flushed file is still in the 2695 // tmp directory. 2696 2697 for (StoreFlushContext flush : storeFlushCtxs.values()) { 2698 flush.flushCache(status); 2699 } 2700 2701 // Switch snapshot (in memstore) -> new hfile (thus causing 2702 // all the store scanners to reset/reseek). 2703 Iterator<HStore> it = storesToFlush.iterator(); 2704 // stores.values() and storeFlushCtxs have same order 2705 for (StoreFlushContext flush : storeFlushCtxs.values()) { 2706 boolean needsCompaction = flush.commit(status); 2707 if (needsCompaction) { 2708 compactionRequested = true; 2709 } 2710 byte[] storeName = it.next().getColumnFamilyDescriptor().getName(); 2711 List<Path> storeCommittedFiles = flush.getCommittedFiles(); 2712 committedFiles.put(storeName, storeCommittedFiles); 2713 // Flush committed no files, indicating flush is empty or flush was canceled 2714 if (storeCommittedFiles == null || storeCommittedFiles.isEmpty()) { 2715 MemStoreSize storeFlushableSize = prepareResult.storeFlushableSize.get(storeName); 2716 prepareResult.totalFlushableSize.decMemStoreSize(storeFlushableSize); 2717 } 2718 flushedOutputFileSize += flush.getOutputFileSize(); 2719 } 2720 storeFlushCtxs.clear(); 2721 2722 // Set down the memstore size by amount of flush. 2723 MemStoreSize mss = prepareResult.totalFlushableSize.getMemStoreSize(); 2724 this.decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize()); 2725 2726 if (wal != null) { 2727 // write flush marker to WAL. If fail, we should throw DroppedSnapshotException 2728 FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.COMMIT_FLUSH, 2729 getRegionInfo(), flushOpSeqId, committedFiles); 2730 WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, true, 2731 mvcc); 2732 } 2733 } catch (Throwable t) { 2734 // An exception here means that the snapshot was not persisted. 2735 // The wal needs to be replayed so its content is restored to memstore. 2736 // Currently, only a server restart will do this. 2737 // We used to only catch IOEs but its possible that we'd get other 2738 // exceptions -- e.g. HBASE-659 was about an NPE -- so now we catch 2739 // all and sundry. 2740 if (wal != null) { 2741 try { 2742 FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.ABORT_FLUSH, 2743 getRegionInfo(), flushOpSeqId, committedFiles); 2744 WALUtil.writeFlushMarker(wal, this.replicationScope, getRegionInfo(), desc, false, mvcc); 2745 } catch (Throwable ex) { 2746 LOG.warn(getRegionInfo().getEncodedName() + " : " 2747 + "failed writing ABORT_FLUSH marker to WAL", ex); 2748 // ignore this since we will be aborting the RS with DSE. 2749 } 2750 wal.abortCacheFlush(this.getRegionInfo().getEncodedNameAsBytes()); 2751 } 2752 DroppedSnapshotException dse = new DroppedSnapshotException("region: " + 2753 Bytes.toStringBinary(getRegionInfo().getRegionName())); 2754 dse.initCause(t); 2755 status.abort("Flush failed: " + StringUtils.stringifyException(t)); 2756 2757 // Callers for flushcache() should catch DroppedSnapshotException and abort the region server. 2758 // However, since we may have the region read lock, we cannot call close(true) here since 2759 // we cannot promote to a write lock. Instead we are setting closing so that all other region 2760 // operations except for close will be rejected. 2761 this.closing.set(true); 2762 2763 if (rsServices != null) { 2764 // This is a safeguard against the case where the caller fails to explicitly handle aborting 2765 rsServices.abort("Replay of WAL required. Forcing server shutdown", dse); 2766 } 2767 2768 throw dse; 2769 } 2770 2771 // If we get to here, the HStores have been written. 2772 if (wal != null) { 2773 wal.completeCacheFlush(this.getRegionInfo().getEncodedNameAsBytes()); 2774 } 2775 2776 // Record latest flush time 2777 for (HStore store: storesToFlush) { 2778 this.lastStoreFlushTimeMap.put(store, startTime); 2779 } 2780 2781 this.maxFlushedSeqId = flushedSeqId; 2782 this.lastFlushOpSeqId = flushOpSeqId; 2783 2784 // C. Finally notify anyone waiting on memstore to clear: 2785 // e.g. checkResources(). 2786 synchronized (this) { 2787 notifyAll(); // FindBugs NN_NAKED_NOTIFY 2788 } 2789 2790 long time = EnvironmentEdgeManager.currentTime() - startTime; 2791 MemStoreSize mss = prepareResult.totalFlushableSize.getMemStoreSize(); 2792 long memstoresize = this.memStoreSizing.getMemStoreSize().getDataSize(); 2793 String msg = "Finished flush of" 2794 + " dataSize ~" + StringUtils.byteDesc(mss.getDataSize()) + "/" + mss.getDataSize() 2795 + ", heapSize ~" + StringUtils.byteDesc(mss.getHeapSize()) + "/" + mss.getHeapSize() 2796 + ", currentSize=" + StringUtils.byteDesc(memstoresize) + "/" + memstoresize 2797 + " for " + this.getRegionInfo().getEncodedName() + " in " + time + "ms, sequenceid=" 2798 + flushOpSeqId + ", compaction requested=" + compactionRequested 2799 + ((wal == null) ? "; wal=null" : ""); 2800 LOG.info(msg); 2801 status.setStatus(msg); 2802 2803 if (rsServices != null && rsServices.getMetrics() != null) { 2804 rsServices.getMetrics().updateFlush(time, 2805 mss.getDataSize(), flushedOutputFileSize); 2806 } 2807 2808 return new FlushResultImpl(compactionRequested ? 2809 FlushResult.Result.FLUSHED_COMPACTION_NEEDED : 2810 FlushResult.Result.FLUSHED_NO_COMPACTION_NEEDED, flushOpSeqId); 2811 } 2812 2813 /** 2814 * Method to safely get the next sequence number. 2815 * @return Next sequence number unassociated with any actual edit. 2816 * @throws IOException 2817 */ 2818 @VisibleForTesting 2819 protected long getNextSequenceId(final WAL wal) throws IOException { 2820 WriteEntry we = mvcc.begin(); 2821 mvcc.completeAndWait(we); 2822 return we.getWriteNumber(); 2823 } 2824 2825 ////////////////////////////////////////////////////////////////////////////// 2826 // get() methods for client use. 2827 ////////////////////////////////////////////////////////////////////////////// 2828 2829 @Override 2830 public RegionScannerImpl getScanner(Scan scan) throws IOException { 2831 return getScanner(scan, null); 2832 } 2833 2834 @Override 2835 public RegionScannerImpl getScanner(Scan scan, List<KeyValueScanner> additionalScanners) 2836 throws IOException { 2837 return getScanner(scan, additionalScanners, HConstants.NO_NONCE, HConstants.NO_NONCE); 2838 } 2839 2840 private RegionScannerImpl getScanner(Scan scan, List<KeyValueScanner> additionalScanners, 2841 long nonceGroup, long nonce) throws IOException { 2842 startRegionOperation(Operation.SCAN); 2843 try { 2844 // Verify families are all valid 2845 if (!scan.hasFamilies()) { 2846 // Adding all families to scanner 2847 for (byte[] family : this.htableDescriptor.getColumnFamilyNames()) { 2848 scan.addFamily(family); 2849 } 2850 } else { 2851 for (byte[] family : scan.getFamilyMap().keySet()) { 2852 checkFamily(family); 2853 } 2854 } 2855 return instantiateRegionScanner(scan, additionalScanners, nonceGroup, nonce); 2856 } finally { 2857 closeRegionOperation(Operation.SCAN); 2858 } 2859 } 2860 2861 protected RegionScanner instantiateRegionScanner(Scan scan, 2862 List<KeyValueScanner> additionalScanners) throws IOException { 2863 return instantiateRegionScanner(scan, additionalScanners, HConstants.NO_NONCE, 2864 HConstants.NO_NONCE); 2865 } 2866 2867 protected RegionScannerImpl instantiateRegionScanner(Scan scan, 2868 List<KeyValueScanner> additionalScanners, long nonceGroup, long nonce) throws IOException { 2869 if (scan.isReversed()) { 2870 if (scan.getFilter() != null) { 2871 scan.getFilter().setReversed(true); 2872 } 2873 return new ReversedRegionScannerImpl(scan, additionalScanners, this); 2874 } 2875 return new RegionScannerImpl(scan, additionalScanners, this, nonceGroup, nonce); 2876 } 2877 2878 /** 2879 * Prepare a delete for a row mutation processor 2880 * @param delete The passed delete is modified by this method. WARNING! 2881 * @throws IOException 2882 */ 2883 public void prepareDelete(Delete delete) throws IOException { 2884 // Check to see if this is a deleteRow insert 2885 if(delete.getFamilyCellMap().isEmpty()){ 2886 for(byte [] family : this.htableDescriptor.getColumnFamilyNames()){ 2887 // Don't eat the timestamp 2888 delete.addFamily(family, delete.getTimestamp()); 2889 } 2890 } else { 2891 for(byte [] family : delete.getFamilyCellMap().keySet()) { 2892 if(family == null) { 2893 throw new NoSuchColumnFamilyException("Empty family is invalid"); 2894 } 2895 checkFamily(family); 2896 } 2897 } 2898 } 2899 2900 @Override 2901 public void delete(Delete delete) throws IOException { 2902 checkReadOnly(); 2903 checkResources(); 2904 startRegionOperation(Operation.DELETE); 2905 try { 2906 // All edits for the given row (across all column families) must happen atomically. 2907 doBatchMutate(delete); 2908 } finally { 2909 closeRegionOperation(Operation.DELETE); 2910 } 2911 } 2912 2913 /** 2914 * Row needed by below method. 2915 */ 2916 private static final byte [] FOR_UNIT_TESTS_ONLY = Bytes.toBytes("ForUnitTestsOnly"); 2917 2918 /** 2919 * This is used only by unit tests. Not required to be a public API. 2920 * @param familyMap map of family to edits for the given family. 2921 * @throws IOException 2922 */ 2923 void delete(NavigableMap<byte[], List<Cell>> familyMap, 2924 Durability durability) throws IOException { 2925 Delete delete = new Delete(FOR_UNIT_TESTS_ONLY); 2926 delete.setFamilyCellMap(familyMap); 2927 delete.setDurability(durability); 2928 doBatchMutate(delete); 2929 } 2930 2931 /** 2932 * Set up correct timestamps in the KVs in Delete object. 2933 * <p>Caller should have the row and region locks. 2934 * @param mutation 2935 * @param familyMap 2936 * @param byteNow 2937 * @throws IOException 2938 */ 2939 public void prepareDeleteTimestamps(Mutation mutation, Map<byte[], List<Cell>> familyMap, 2940 byte[] byteNow) throws IOException { 2941 for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) { 2942 2943 byte[] family = e.getKey(); 2944 List<Cell> cells = e.getValue(); 2945 assert cells instanceof RandomAccess; 2946 2947 Map<byte[], Integer> kvCount = new TreeMap<>(Bytes.BYTES_COMPARATOR); 2948 int listSize = cells.size(); 2949 for (int i=0; i < listSize; i++) { 2950 Cell cell = cells.get(i); 2951 // Check if time is LATEST, change to time of most recent addition if so 2952 // This is expensive. 2953 if (cell.getTimestamp() == HConstants.LATEST_TIMESTAMP 2954 && PrivateCellUtil.isDeleteType(cell)) { 2955 byte[] qual = CellUtil.cloneQualifier(cell); 2956 2957 Integer count = kvCount.get(qual); 2958 if (count == null) { 2959 kvCount.put(qual, 1); 2960 } else { 2961 kvCount.put(qual, count + 1); 2962 } 2963 count = kvCount.get(qual); 2964 2965 Get get = new Get(CellUtil.cloneRow(cell)); 2966 get.setMaxVersions(count); 2967 get.addColumn(family, qual); 2968 if (coprocessorHost != null) { 2969 if (!coprocessorHost.prePrepareTimeStampForDeleteVersion(mutation, cell, 2970 byteNow, get)) { 2971 updateDeleteLatestVersionTimestamp(cell, get, count, byteNow); 2972 } 2973 } else { 2974 updateDeleteLatestVersionTimestamp(cell, get, count, byteNow); 2975 } 2976 } else { 2977 PrivateCellUtil.updateLatestStamp(cell, byteNow); 2978 } 2979 } 2980 } 2981 } 2982 2983 void updateDeleteLatestVersionTimestamp(Cell cell, Get get, int count, byte[] byteNow) 2984 throws IOException { 2985 List<Cell> result = get(get, false); 2986 2987 if (result.size() < count) { 2988 // Nothing to delete 2989 PrivateCellUtil.updateLatestStamp(cell, byteNow); 2990 return; 2991 } 2992 if (result.size() > count) { 2993 throw new RuntimeException("Unexpected size: " + result.size()); 2994 } 2995 Cell getCell = result.get(count - 1); 2996 PrivateCellUtil.setTimestamp(cell, getCell.getTimestamp()); 2997 } 2998 2999 @Override 3000 public void put(Put put) throws IOException { 3001 checkReadOnly(); 3002 3003 // Do a rough check that we have resources to accept a write. The check is 3004 // 'rough' in that between the resource check and the call to obtain a 3005 // read lock, resources may run out. For now, the thought is that this 3006 // will be extremely rare; we'll deal with it when it happens. 3007 checkResources(); 3008 startRegionOperation(Operation.PUT); 3009 try { 3010 // All edits for the given row (across all column families) must happen atomically. 3011 doBatchMutate(put); 3012 } finally { 3013 closeRegionOperation(Operation.PUT); 3014 } 3015 } 3016 3017 /** 3018 * Class that tracks the progress of a batch operations, accumulating status codes and tracking 3019 * the index at which processing is proceeding. These batch operations may get split into 3020 * mini-batches for processing. 3021 */ 3022 private abstract static class BatchOperation<T> { 3023 protected final T[] operations; 3024 protected final OperationStatus[] retCodeDetails; 3025 protected final WALEdit[] walEditsFromCoprocessors; 3026 // reference family cell maps directly so coprocessors can mutate them if desired 3027 protected final Map<byte[], List<Cell>>[] familyCellMaps; 3028 3029 protected final HRegion region; 3030 protected int nextIndexToProcess = 0; 3031 protected final ObservedExceptionsInBatch observedExceptions; 3032 //Durability of the batch (highest durability of all operations) 3033 protected Durability durability; 3034 protected boolean atomic = false; 3035 3036 public BatchOperation(final HRegion region, T[] operations) { 3037 this.operations = operations; 3038 this.retCodeDetails = new OperationStatus[operations.length]; 3039 Arrays.fill(this.retCodeDetails, OperationStatus.NOT_RUN); 3040 this.walEditsFromCoprocessors = new WALEdit[operations.length]; 3041 familyCellMaps = new Map[operations.length]; 3042 3043 this.region = region; 3044 observedExceptions = new ObservedExceptionsInBatch(); 3045 durability = Durability.USE_DEFAULT; 3046 } 3047 3048 /** 3049 * Visitor interface for batch operations 3050 */ 3051 @FunctionalInterface 3052 public interface Visitor { 3053 /** 3054 * @param index operation index 3055 * @return If true continue visiting remaining entries, break otherwise 3056 */ 3057 boolean visit(int index) throws IOException; 3058 } 3059 3060 /** 3061 * Helper method for visiting pending/ all batch operations 3062 */ 3063 public void visitBatchOperations(boolean pendingOnly, int lastIndexExclusive, Visitor visitor) 3064 throws IOException { 3065 assert lastIndexExclusive <= this.size(); 3066 for (int i = nextIndexToProcess; i < lastIndexExclusive; i++) { 3067 if (!pendingOnly || isOperationPending(i)) { 3068 if (!visitor.visit(i)) { 3069 break; 3070 } 3071 } 3072 } 3073 } 3074 3075 public abstract Mutation getMutation(int index); 3076 3077 public abstract long getNonceGroup(int index); 3078 3079 public abstract long getNonce(int index); 3080 3081 /** 3082 * This method is potentially expensive and useful mostly for non-replay CP path. 3083 */ 3084 public abstract Mutation[] getMutationsForCoprocs(); 3085 3086 public abstract boolean isInReplay(); 3087 3088 public abstract long getOrigLogSeqNum(); 3089 3090 public abstract void startRegionOperation() throws IOException; 3091 3092 public abstract void closeRegionOperation() throws IOException; 3093 3094 /** 3095 * Validates each mutation and prepares a batch for write. If necessary (non-replay case), runs 3096 * CP prePut()/ preDelete() hooks for all mutations in a batch. This is intended to operate on 3097 * entire batch and will be called from outside of class to check and prepare batch. This can 3098 * be implemented by calling helper method {@link #checkAndPrepareMutation(int, long)} in a 3099 * 'for' loop over mutations. 3100 */ 3101 public abstract void checkAndPrepare() throws IOException; 3102 3103 /** 3104 * Implement any Put request specific check and prepare logic here. Please refer to 3105 * {@link #checkAndPrepareMutation(Mutation, long)} for how its used. 3106 */ 3107 protected abstract void checkAndPreparePut(final Put p) throws IOException; 3108 3109 /** 3110 * If necessary, calls preBatchMutate() CP hook for a mini-batch and updates metrics, cell 3111 * count, tags and timestamp for all cells of all operations in a mini-batch. 3112 */ 3113 public abstract void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation> 3114 miniBatchOp, long timestamp, final List<RowLock> acquiredRowLocks) throws IOException; 3115 3116 /** 3117 * Write mini-batch operations to MemStore 3118 */ 3119 public abstract WriteEntry writeMiniBatchOperationsToMemStore( 3120 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry) 3121 throws IOException; 3122 3123 protected void writeMiniBatchOperationsToMemStore( 3124 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final long writeNumber) 3125 throws IOException { 3126 MemStoreSizing memStoreAccounting = new NonThreadSafeMemStoreSizing(); 3127 visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> { 3128 // We need to update the sequence id for following reasons. 3129 // 1) If the op is in replay mode, FSWALEntry#stampRegionSequenceId won't stamp sequence id. 3130 // 2) If no WAL, FSWALEntry won't be used 3131 // we use durability of the original mutation for the mutation passed by CP. 3132 if (isInReplay() || getMutation(index).getDurability() == Durability.SKIP_WAL) { 3133 region.updateSequenceId(familyCellMaps[index].values(), writeNumber); 3134 } 3135 applyFamilyMapToMemStore(familyCellMaps[index], memStoreAccounting); 3136 return true; 3137 }); 3138 // update memStore size 3139 region.incMemStoreSize(memStoreAccounting.getDataSize(), memStoreAccounting.getHeapSize(), 3140 memStoreAccounting.getOffHeapSize()); 3141 } 3142 3143 public boolean isDone() { 3144 return nextIndexToProcess == operations.length; 3145 } 3146 3147 public int size() { 3148 return operations.length; 3149 } 3150 3151 public boolean isOperationPending(int index) { 3152 return retCodeDetails[index].getOperationStatusCode() == OperationStatusCode.NOT_RUN; 3153 } 3154 3155 public List<UUID> getClusterIds() { 3156 assert size() != 0; 3157 return getMutation(0).getClusterIds(); 3158 } 3159 3160 boolean isAtomic() { 3161 return atomic; 3162 } 3163 3164 /** 3165 * Helper method that checks and prepares only one mutation. This can be used to implement 3166 * {@link #checkAndPrepare()} for entire Batch. 3167 * NOTE: As CP prePut()/ preDelete() hooks may modify mutations, this method should be called 3168 * after prePut()/ preDelete() CP hooks are run for the mutation 3169 */ 3170 protected void checkAndPrepareMutation(Mutation mutation, final long timestamp) 3171 throws IOException { 3172 region.checkRow(mutation.getRow(), "batchMutate"); 3173 if (mutation instanceof Put) { 3174 // Check the families in the put. If bad, skip this one. 3175 checkAndPreparePut((Put) mutation); 3176 region.checkTimestamps(mutation.getFamilyCellMap(), timestamp); 3177 } else { 3178 region.prepareDelete((Delete) mutation); 3179 } 3180 } 3181 3182 protected void checkAndPrepareMutation(int index, long timestamp) throws IOException { 3183 Mutation mutation = getMutation(index); 3184 try { 3185 this.checkAndPrepareMutation(mutation, timestamp); 3186 3187 // store the family map reference to allow for mutations 3188 familyCellMaps[index] = mutation.getFamilyCellMap(); 3189 // store durability for the batch (highest durability of all operations in the batch) 3190 Durability tmpDur = region.getEffectiveDurability(mutation.getDurability()); 3191 if (tmpDur.ordinal() > durability.ordinal()) { 3192 durability = tmpDur; 3193 } 3194 } catch (NoSuchColumnFamilyException nscfe) { 3195 final String msg = "No such column family in batch mutation. "; 3196 if (observedExceptions.hasSeenNoSuchFamily()) { 3197 LOG.warn(msg + nscfe.getMessage()); 3198 } else { 3199 LOG.warn(msg, nscfe); 3200 observedExceptions.sawNoSuchFamily(); 3201 } 3202 retCodeDetails[index] = new OperationStatus( 3203 OperationStatusCode.BAD_FAMILY, nscfe.getMessage()); 3204 if (isAtomic()) { // fail, atomic means all or none 3205 throw nscfe; 3206 } 3207 } catch (FailedSanityCheckException fsce) { 3208 final String msg = "Batch Mutation did not pass sanity check. "; 3209 if (observedExceptions.hasSeenFailedSanityCheck()) { 3210 LOG.warn(msg + fsce.getMessage()); 3211 } else { 3212 LOG.warn(msg, fsce); 3213 observedExceptions.sawFailedSanityCheck(); 3214 } 3215 retCodeDetails[index] = new OperationStatus( 3216 OperationStatusCode.SANITY_CHECK_FAILURE, fsce.getMessage()); 3217 if (isAtomic()) { 3218 throw fsce; 3219 } 3220 } catch (WrongRegionException we) { 3221 final String msg = "Batch mutation had a row that does not belong to this region. "; 3222 if (observedExceptions.hasSeenWrongRegion()) { 3223 LOG.warn(msg + we.getMessage()); 3224 } else { 3225 LOG.warn(msg, we); 3226 observedExceptions.sawWrongRegion(); 3227 } 3228 retCodeDetails[index] = new OperationStatus( 3229 OperationStatusCode.SANITY_CHECK_FAILURE, we.getMessage()); 3230 if (isAtomic()) { 3231 throw we; 3232 } 3233 } 3234 } 3235 3236 /** 3237 * Creates Mini-batch of all operations [nextIndexToProcess, lastIndexExclusive) for which 3238 * a row lock can be acquired. All mutations with locked rows are considered to be 3239 * In-progress operations and hence the name {@link MiniBatchOperationInProgress}. Mini batch 3240 * is window over {@link BatchOperation} and contains contiguous pending operations. 3241 * 3242 * @param acquiredRowLocks keeps track of rowLocks acquired. 3243 */ 3244 public MiniBatchOperationInProgress<Mutation> lockRowsAndBuildMiniBatch( 3245 List<RowLock> acquiredRowLocks) throws IOException { 3246 int readyToWriteCount = 0; 3247 int lastIndexExclusive = 0; 3248 RowLock prevRowLock = null; 3249 for (; lastIndexExclusive < size(); lastIndexExclusive++) { 3250 // It reaches the miniBatchSize, stop here and process the miniBatch 3251 // This only applies to non-atomic batch operations. 3252 if (!isAtomic() && (readyToWriteCount == region.miniBatchSize)) { 3253 break; 3254 } 3255 3256 if (!isOperationPending(lastIndexExclusive)) { 3257 continue; 3258 } 3259 Mutation mutation = getMutation(lastIndexExclusive); 3260 // If we haven't got any rows in our batch, we should block to get the next one. 3261 RowLock rowLock = null; 3262 try { 3263 // if atomic then get exclusive lock, else shared lock 3264 rowLock = region.getRowLockInternal(mutation.getRow(), !isAtomic(), prevRowLock); 3265 } catch (TimeoutIOException | InterruptedIOException e) { 3266 // NOTE: We will retry when other exceptions, but we should stop if we receive 3267 // TimeoutIOException or InterruptedIOException as operation has timed out or 3268 // interrupted respectively. 3269 throw e; 3270 } catch (IOException ioe) { 3271 LOG.warn("Failed getting lock, row=" + Bytes.toStringBinary(mutation.getRow()), ioe); 3272 if (isAtomic()) { // fail, atomic means all or none 3273 throw ioe; 3274 } 3275 } 3276 if (rowLock == null) { 3277 // We failed to grab another lock 3278 if (isAtomic()) { 3279 throw new IOException("Can't apply all operations atomically!"); 3280 } 3281 break; // Stop acquiring more rows for this batch 3282 } else { 3283 if (rowLock != prevRowLock) { 3284 // It is a different row now, add this to the acquiredRowLocks and 3285 // set prevRowLock to the new returned rowLock 3286 acquiredRowLocks.add(rowLock); 3287 prevRowLock = rowLock; 3288 } 3289 } 3290 3291 readyToWriteCount++; 3292 } 3293 return createMiniBatch(lastIndexExclusive, readyToWriteCount); 3294 } 3295 3296 protected MiniBatchOperationInProgress<Mutation> createMiniBatch(final int lastIndexExclusive, 3297 final int readyToWriteCount) { 3298 return new MiniBatchOperationInProgress<>(getMutationsForCoprocs(), retCodeDetails, 3299 walEditsFromCoprocessors, nextIndexToProcess, lastIndexExclusive, readyToWriteCount); 3300 } 3301 3302 /** 3303 * Builds separate WALEdit per nonce by applying input mutations. If WALEdits from CP are 3304 * present, they are merged to result WALEdit. 3305 */ 3306 public List<Pair<NonceKey, WALEdit>> buildWALEdits( 3307 final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException { 3308 List<Pair<NonceKey, WALEdit>> walEdits = new ArrayList<>(); 3309 3310 visitBatchOperations(true, nextIndexToProcess + miniBatchOp.size(), new Visitor() { 3311 private Pair<NonceKey, WALEdit> curWALEditForNonce; 3312 3313 @Override 3314 public boolean visit(int index) throws IOException { 3315 Mutation m = getMutation(index); 3316 // we use durability of the original mutation for the mutation passed by CP. 3317 if (region.getEffectiveDurability(m.getDurability()) == Durability.SKIP_WAL) { 3318 region.recordMutationWithoutWal(m.getFamilyCellMap()); 3319 return true; 3320 } 3321 3322 // the batch may contain multiple nonce keys (replay case). If so, write WALEdit for each. 3323 // Given how nonce keys are originally written, these should be contiguous. 3324 // They don't have to be, it will still work, just write more WALEdits than needed. 3325 long nonceGroup = getNonceGroup(index); 3326 long nonce = getNonce(index); 3327 if (curWALEditForNonce == null || 3328 curWALEditForNonce.getFirst().getNonceGroup() != nonceGroup || 3329 curWALEditForNonce.getFirst().getNonce() != nonce) { 3330 curWALEditForNonce = new Pair<>(new NonceKey(nonceGroup, nonce), 3331 new WALEdit(miniBatchOp.getCellCount(), isInReplay())); 3332 walEdits.add(curWALEditForNonce); 3333 } 3334 WALEdit walEdit = curWALEditForNonce.getSecond(); 3335 3336 // Add WAL edits from CPs. 3337 WALEdit fromCP = walEditsFromCoprocessors[index]; 3338 if (fromCP != null) { 3339 for (Cell cell : fromCP.getCells()) { 3340 walEdit.add(cell); 3341 } 3342 } 3343 walEdit.add(familyCellMaps[index]); 3344 3345 return true; 3346 } 3347 }); 3348 return walEdits; 3349 } 3350 3351 /** 3352 * This method completes mini-batch operations by calling postBatchMutate() CP hook (if 3353 * required) and completing mvcc. 3354 */ 3355 public void completeMiniBatchOperations( 3356 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry) 3357 throws IOException { 3358 if (writeEntry != null) { 3359 region.mvcc.completeAndWait(writeEntry); 3360 } 3361 } 3362 3363 public void doPostOpCleanupForMiniBatch( 3364 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WALEdit walEdit, 3365 boolean success) throws IOException {} 3366 3367 /** 3368 * Atomically apply the given map of family->edits to the memstore. 3369 * This handles the consistency control on its own, but the caller 3370 * should already have locked updatesLock.readLock(). This also does 3371 * <b>not</b> check the families for validity. 3372 * 3373 * @param familyMap Map of Cells by family 3374 */ 3375 protected void applyFamilyMapToMemStore(Map<byte[], List<Cell>> familyMap, 3376 MemStoreSizing memstoreAccounting) throws IOException { 3377 for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) { 3378 byte[] family = e.getKey(); 3379 List<Cell> cells = e.getValue(); 3380 assert cells instanceof RandomAccess; 3381 region.applyToMemStore(region.getStore(family), cells, false, memstoreAccounting); 3382 } 3383 } 3384 } 3385 3386 3387 /** 3388 * Batch of mutation operations. Base class is shared with {@link ReplayBatchOperation} as most 3389 * of the logic is same. 3390 */ 3391 static class MutationBatchOperation extends BatchOperation<Mutation> { 3392 private long nonceGroup; 3393 private long nonce; 3394 public MutationBatchOperation(final HRegion region, Mutation[] operations, boolean atomic, 3395 long nonceGroup, long nonce) { 3396 super(region, operations); 3397 this.atomic = atomic; 3398 this.nonceGroup = nonceGroup; 3399 this.nonce = nonce; 3400 } 3401 3402 @Override 3403 public Mutation getMutation(int index) { 3404 return this.operations[index]; 3405 } 3406 3407 @Override 3408 public long getNonceGroup(int index) { 3409 return nonceGroup; 3410 } 3411 3412 @Override 3413 public long getNonce(int index) { 3414 return nonce; 3415 } 3416 3417 @Override 3418 public Mutation[] getMutationsForCoprocs() { 3419 return this.operations; 3420 } 3421 3422 @Override 3423 public boolean isInReplay() { 3424 return false; 3425 } 3426 3427 @Override 3428 public long getOrigLogSeqNum() { 3429 return SequenceId.NO_SEQUENCE_ID; 3430 } 3431 3432 @Override 3433 public void startRegionOperation() throws IOException { 3434 region.startRegionOperation(Operation.BATCH_MUTATE); 3435 } 3436 3437 @Override 3438 public void closeRegionOperation() throws IOException { 3439 region.closeRegionOperation(Operation.BATCH_MUTATE); 3440 } 3441 3442 @Override 3443 public void checkAndPreparePut(Put p) throws IOException { 3444 region.checkFamilies(p.getFamilyCellMap().keySet()); 3445 } 3446 3447 @Override 3448 public void checkAndPrepare() throws IOException { 3449 final int[] metrics = {0, 0}; // index 0: puts, index 1: deletes 3450 visitBatchOperations(true, this.size(), new Visitor() { 3451 private long now = EnvironmentEdgeManager.currentTime(); 3452 private WALEdit walEdit; 3453 @Override 3454 public boolean visit(int index) throws IOException { 3455 // Run coprocessor pre hook outside of locks to avoid deadlock 3456 if (region.coprocessorHost != null) { 3457 if (walEdit == null) { 3458 walEdit = new WALEdit(); 3459 } 3460 callPreMutateCPHook(index, walEdit, metrics); 3461 if (!walEdit.isEmpty()) { 3462 walEditsFromCoprocessors[index] = walEdit; 3463 walEdit = null; 3464 } 3465 } 3466 if (isOperationPending(index)) { 3467 // TODO: Currently validation is done with current time before acquiring locks and 3468 // updates are done with different timestamps after acquiring locks. This behavior is 3469 // inherited from the code prior to this change. Can this be changed? 3470 checkAndPrepareMutation(index, now); 3471 } 3472 return true; 3473 } 3474 }); 3475 3476 // FIXME: we may update metrics twice! here for all operations bypassed by CP and later in 3477 // normal processing. 3478 // Update metrics in same way as it is done when we go the normal processing route (we now 3479 // update general metrics though a Coprocessor did the work). 3480 if (region.metricsRegion != null) { 3481 if (metrics[0] > 0) { 3482 // There were some Puts in the batch. 3483 region.metricsRegion.updatePut(); 3484 } 3485 if (metrics[1] > 0) { 3486 // There were some Deletes in the batch. 3487 region.metricsRegion.updateDelete(); 3488 } 3489 } 3490 } 3491 3492 @Override 3493 public void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation> miniBatchOp, 3494 long timestamp, final List<RowLock> acquiredRowLocks) throws IOException { 3495 byte[] byteTS = Bytes.toBytes(timestamp); 3496 visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> { 3497 Mutation mutation = getMutation(index); 3498 if (mutation instanceof Put) { 3499 region.updateCellTimestamps(familyCellMaps[index].values(), byteTS); 3500 miniBatchOp.incrementNumOfPuts(); 3501 } else { 3502 region.prepareDeleteTimestamps(mutation, familyCellMaps[index], byteTS); 3503 miniBatchOp.incrementNumOfDeletes(); 3504 } 3505 region.rewriteCellTags(familyCellMaps[index], mutation); 3506 3507 // update cell count 3508 if (region.getEffectiveDurability(mutation.getDurability()) != Durability.SKIP_WAL) { 3509 for (List<Cell> cells : mutation.getFamilyCellMap().values()) { 3510 miniBatchOp.addCellCount(cells.size()); 3511 } 3512 } 3513 3514 WALEdit fromCP = walEditsFromCoprocessors[index]; 3515 if (fromCP != null) { 3516 miniBatchOp.addCellCount(fromCP.size()); 3517 } 3518 return true; 3519 }); 3520 3521 if (region.coprocessorHost != null) { 3522 // calling the pre CP hook for batch mutation 3523 region.coprocessorHost.preBatchMutate(miniBatchOp); 3524 checkAndMergeCPMutations(miniBatchOp, acquiredRowLocks, timestamp); 3525 } 3526 } 3527 3528 @Override 3529 public List<Pair<NonceKey, WALEdit>> buildWALEdits(final MiniBatchOperationInProgress<Mutation> 3530 miniBatchOp) throws IOException { 3531 List<Pair<NonceKey, WALEdit>> walEdits = super.buildWALEdits(miniBatchOp); 3532 // for MutationBatchOperation, more than one nonce is not allowed 3533 if (walEdits.size() > 1) { 3534 throw new IOException("Found multiple nonce keys per batch!"); 3535 } 3536 return walEdits; 3537 } 3538 3539 @Override 3540 public WriteEntry writeMiniBatchOperationsToMemStore( 3541 final MiniBatchOperationInProgress<Mutation> miniBatchOp, @Nullable WriteEntry writeEntry) 3542 throws IOException { 3543 if (writeEntry == null) { 3544 writeEntry = region.mvcc.begin(); 3545 } 3546 super.writeMiniBatchOperationsToMemStore(miniBatchOp, writeEntry.getWriteNumber()); 3547 return writeEntry; 3548 } 3549 3550 @Override 3551 public void completeMiniBatchOperations( 3552 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry) 3553 throws IOException { 3554 // TODO: can it be done after completing mvcc? 3555 // calling the post CP hook for batch mutation 3556 if (region.coprocessorHost != null) { 3557 region.coprocessorHost.postBatchMutate(miniBatchOp); 3558 } 3559 super.completeMiniBatchOperations(miniBatchOp, writeEntry); 3560 } 3561 3562 @Override 3563 public void doPostOpCleanupForMiniBatch(MiniBatchOperationInProgress<Mutation> miniBatchOp, 3564 final WALEdit walEdit, boolean success) throws IOException { 3565 if (miniBatchOp != null) { 3566 // synced so that the coprocessor contract is adhered to. 3567 if (region.coprocessorHost != null) { 3568 visitBatchOperations(false, miniBatchOp.getLastIndexExclusive(), (int i) -> { 3569 // only for successful puts 3570 if (retCodeDetails[i].getOperationStatusCode() == OperationStatusCode.SUCCESS) { 3571 Mutation m = getMutation(i); 3572 if (m instanceof Put) { 3573 region.coprocessorHost.postPut((Put) m, walEdit, m.getDurability()); 3574 } else { 3575 region.coprocessorHost.postDelete((Delete) m, walEdit, m.getDurability()); 3576 } 3577 } 3578 return true; 3579 }); 3580 } 3581 3582 // See if the column families were consistent through the whole thing. 3583 // if they were then keep them. If they were not then pass a null. 3584 // null will be treated as unknown. 3585 // Total time taken might be involving Puts and Deletes. 3586 // Split the time for puts and deletes based on the total number of Puts and Deletes. 3587 if (region.metricsRegion != null) { 3588 if (miniBatchOp.getNumOfPuts() > 0) { 3589 // There were some Puts in the batch. 3590 region.metricsRegion.updatePut(); 3591 } 3592 if (miniBatchOp.getNumOfDeletes() > 0) { 3593 // There were some Deletes in the batch. 3594 region.metricsRegion.updateDelete(); 3595 } 3596 } 3597 } 3598 3599 if (region.coprocessorHost != null) { 3600 // call the coprocessor hook to do any finalization steps after the put is done 3601 region.coprocessorHost.postBatchMutateIndispensably( 3602 miniBatchOp != null ? miniBatchOp : createMiniBatch(size(), 0), success); 3603 } 3604 } 3605 3606 /** 3607 * Runs prePut/ preDelete coprocessor hook for input mutation in a batch 3608 * @param metrics Array of 2 ints. index 0: count of puts and index 1: count of deletes 3609 */ 3610 private void callPreMutateCPHook(int index, final WALEdit walEdit, final int[] metrics) 3611 throws IOException { 3612 Mutation m = getMutation(index); 3613 if (m instanceof Put) { 3614 if (region.coprocessorHost.prePut((Put) m, walEdit, m.getDurability())) { 3615 // pre hook says skip this Put 3616 // mark as success and skip in doMiniBatchMutation 3617 metrics[0]++; 3618 retCodeDetails[index] = OperationStatus.SUCCESS; 3619 } 3620 } else if (m instanceof Delete) { 3621 Delete curDel = (Delete) m; 3622 if (curDel.getFamilyCellMap().isEmpty()) { 3623 // handle deleting a row case 3624 // TODO: prepareDelete() has been called twice, before and after preDelete() CP hook. 3625 // Can this be avoided? 3626 region.prepareDelete(curDel); 3627 } 3628 if (region.coprocessorHost.preDelete(curDel, walEdit, m.getDurability())) { 3629 // pre hook says skip this Delete 3630 // mark as success and skip in doMiniBatchMutation 3631 metrics[1]++; 3632 retCodeDetails[index] = OperationStatus.SUCCESS; 3633 } 3634 } else { 3635 String msg = "Put/Delete mutations only supported in a batch"; 3636 // In case of passing Append mutations along with the Puts and Deletes in batchMutate 3637 // mark the operation return code as failure so that it will not be considered in 3638 // the doMiniBatchMutation 3639 retCodeDetails[index] = new OperationStatus(OperationStatusCode.FAILURE, msg); 3640 3641 if (isAtomic()) { // fail, atomic means all or none 3642 throw new IOException(msg); 3643 } 3644 } 3645 } 3646 3647 private void checkAndMergeCPMutations(final MiniBatchOperationInProgress<Mutation> miniBatchOp, 3648 final List<RowLock> acquiredRowLocks, final long timestamp) throws IOException { 3649 visitBatchOperations(true, nextIndexToProcess + miniBatchOp.size(), (int i) -> { 3650 // we pass (i - firstIndex) below since the call expects a relative index 3651 Mutation[] cpMutations = miniBatchOp.getOperationsFromCoprocessors(i - nextIndexToProcess); 3652 if (cpMutations == null) { 3653 return true; 3654 } 3655 // Else Coprocessor added more Mutations corresponding to the Mutation at this index. 3656 Mutation mutation = getMutation(i); 3657 for (Mutation cpMutation : cpMutations) { 3658 this.checkAndPrepareMutation(cpMutation, timestamp); 3659 3660 // Acquire row locks. If not, the whole batch will fail. 3661 acquiredRowLocks.add(region.getRowLockInternal(cpMutation.getRow(), true, null)); 3662 3663 // Returned mutations from coprocessor correspond to the Mutation at index i. We can 3664 // directly add the cells from those mutations to the familyMaps of this mutation. 3665 Map<byte[], List<Cell>> cpFamilyMap = cpMutation.getFamilyCellMap(); 3666 // will get added to the memStore later 3667 mergeFamilyMaps(familyCellMaps[i], cpFamilyMap); 3668 3669 // The durability of returned mutation is replaced by the corresponding mutation. 3670 // If the corresponding mutation contains the SKIP_WAL, we shouldn't count the 3671 // cells of returned mutation. 3672 if (region.getEffectiveDurability(mutation.getDurability()) != Durability.SKIP_WAL) { 3673 for (List<Cell> cells : cpFamilyMap.values()) { 3674 miniBatchOp.addCellCount(cells.size()); 3675 } 3676 } 3677 } 3678 return true; 3679 }); 3680 } 3681 3682 private void mergeFamilyMaps(Map<byte[], List<Cell>> familyMap, 3683 Map<byte[], List<Cell>> toBeMerged) { 3684 for (Map.Entry<byte[], List<Cell>> entry : toBeMerged.entrySet()) { 3685 List<Cell> cells = familyMap.get(entry.getKey()); 3686 if (cells == null) { 3687 familyMap.put(entry.getKey(), entry.getValue()); 3688 } else { 3689 cells.addAll(entry.getValue()); 3690 } 3691 } 3692 } 3693 } 3694 3695 /** 3696 * Batch of mutations for replay. Base class is shared with {@link MutationBatchOperation} as most 3697 * of the logic is same. 3698 */ 3699 static class ReplayBatchOperation extends BatchOperation<MutationReplay> { 3700 private long origLogSeqNum = 0; 3701 public ReplayBatchOperation(final HRegion region, MutationReplay[] operations, 3702 long origLogSeqNum) { 3703 super(region, operations); 3704 this.origLogSeqNum = origLogSeqNum; 3705 } 3706 3707 @Override 3708 public Mutation getMutation(int index) { 3709 return this.operations[index].mutation; 3710 } 3711 3712 @Override 3713 public long getNonceGroup(int index) { 3714 return this.operations[index].nonceGroup; 3715 } 3716 3717 @Override 3718 public long getNonce(int index) { 3719 return this.operations[index].nonce; 3720 } 3721 3722 @Override 3723 public Mutation[] getMutationsForCoprocs() { 3724 return null; 3725 } 3726 3727 @Override 3728 public boolean isInReplay() { 3729 return true; 3730 } 3731 3732 @Override 3733 public long getOrigLogSeqNum() { 3734 return this.origLogSeqNum; 3735 } 3736 3737 @Override 3738 public void startRegionOperation() throws IOException { 3739 region.startRegionOperation(Operation.REPLAY_BATCH_MUTATE); 3740 } 3741 3742 @Override 3743 public void closeRegionOperation() throws IOException { 3744 region.closeRegionOperation(Operation.REPLAY_BATCH_MUTATE); 3745 } 3746 3747 /** 3748 * During replay, there could exist column families which are removed between region server 3749 * failure and replay 3750 */ 3751 @Override 3752 protected void checkAndPreparePut(Put p) throws IOException { 3753 Map<byte[], List<Cell>> familyCellMap = p.getFamilyCellMap(); 3754 List<byte[]> nonExistentList = null; 3755 for (byte[] family : familyCellMap.keySet()) { 3756 if (!region.htableDescriptor.hasColumnFamily(family)) { 3757 if (nonExistentList == null) { 3758 nonExistentList = new ArrayList<>(); 3759 } 3760 nonExistentList.add(family); 3761 } 3762 } 3763 if (nonExistentList != null) { 3764 for (byte[] family : nonExistentList) { 3765 // Perhaps schema was changed between crash and replay 3766 LOG.info("No family for " + Bytes.toString(family) + " omit from reply."); 3767 familyCellMap.remove(family); 3768 } 3769 } 3770 } 3771 3772 @Override 3773 public void checkAndPrepare() throws IOException { 3774 long now = EnvironmentEdgeManager.currentTime(); 3775 visitBatchOperations(true, this.size(), (int index) -> { 3776 checkAndPrepareMutation(index, now); 3777 return true; 3778 }); 3779 } 3780 3781 @Override 3782 public void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation> miniBatchOp, 3783 long timestamp, final List<RowLock> acquiredRowLocks) throws IOException { 3784 visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> { 3785 // update cell count 3786 for (List<Cell> cells : getMutation(index).getFamilyCellMap().values()) { 3787 miniBatchOp.addCellCount(cells.size()); 3788 } 3789 return true; 3790 }); 3791 } 3792 3793 @Override 3794 public WriteEntry writeMiniBatchOperationsToMemStore( 3795 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry) 3796 throws IOException { 3797 super.writeMiniBatchOperationsToMemStore(miniBatchOp, getOrigLogSeqNum()); 3798 return writeEntry; 3799 } 3800 3801 @Override 3802 public void completeMiniBatchOperations( 3803 final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry) 3804 throws IOException { 3805 super.completeMiniBatchOperations(miniBatchOp, writeEntry); 3806 region.mvcc.advanceTo(getOrigLogSeqNum()); 3807 } 3808 } 3809 3810 public OperationStatus[] batchMutate(Mutation[] mutations, long nonceGroup, long nonce) 3811 throws IOException { 3812 return batchMutate(mutations, false, nonceGroup, nonce); 3813 } 3814 3815 public OperationStatus[] batchMutate(Mutation[] mutations, boolean atomic, long nonceGroup, 3816 long nonce) throws IOException { 3817 // As it stands, this is used for 3 things 3818 // * batchMutate with single mutation - put/delete, separate or from checkAndMutate. 3819 // * coprocessor calls (see ex. BulkDeleteEndpoint). 3820 // So nonces are not really ever used by HBase. They could be by coprocs, and checkAnd... 3821 return batchMutate(new MutationBatchOperation(this, mutations, atomic, nonceGroup, nonce)); 3822 } 3823 3824 @Override 3825 public OperationStatus[] batchMutate(Mutation[] mutations) throws IOException { 3826 return batchMutate(mutations, HConstants.NO_NONCE, HConstants.NO_NONCE); 3827 } 3828 3829 public OperationStatus[] batchReplay(MutationReplay[] mutations, long replaySeqId) 3830 throws IOException { 3831 if (!RegionReplicaUtil.isDefaultReplica(getRegionInfo()) 3832 && replaySeqId < lastReplayedOpenRegionSeqId) { 3833 // if it is a secondary replica we should ignore these entries silently 3834 // since they are coming out of order 3835 if (LOG.isTraceEnabled()) { 3836 LOG.trace(getRegionInfo().getEncodedName() + " : " 3837 + "Skipping " + mutations.length + " mutations with replaySeqId=" + replaySeqId 3838 + " which is < than lastReplayedOpenRegionSeqId=" + lastReplayedOpenRegionSeqId); 3839 for (MutationReplay mut : mutations) { 3840 LOG.trace(getRegionInfo().getEncodedName() + " : Skipping : " + mut.mutation); 3841 } 3842 } 3843 3844 OperationStatus[] statuses = new OperationStatus[mutations.length]; 3845 for (int i = 0; i < statuses.length; i++) { 3846 statuses[i] = OperationStatus.SUCCESS; 3847 } 3848 return statuses; 3849 } 3850 return batchMutate(new ReplayBatchOperation(this, mutations, replaySeqId)); 3851 } 3852 3853 /** 3854 * Perform a batch of mutations. 3855 * 3856 * It supports only Put and Delete mutations and will ignore other types passed. Operations in 3857 * a batch are stored with highest durability specified of for all operations in a batch, 3858 * except for {@link Durability#SKIP_WAL}. 3859 * 3860 * <p>This function is called from {@link #batchReplay(MutationReplay[], long)} with 3861 * {@link ReplayBatchOperation} instance and {@link #batchMutate(Mutation[], long, long)} with 3862 * {@link MutationBatchOperation} instance as an argument. As the processing of replay batch 3863 * and mutation batch is very similar, lot of code is shared by providing generic methods in 3864 * base class {@link BatchOperation}. The logic for this method and 3865 * {@link #doMiniBatchMutate(BatchOperation)} is implemented using methods in base class which 3866 * are overridden by derived classes to implement special behavior. 3867 * 3868 * @param batchOp contains the list of mutations 3869 * @return an array of OperationStatus which internally contains the 3870 * OperationStatusCode and the exceptionMessage if any. 3871 * @throws IOException 3872 */ 3873 OperationStatus[] batchMutate(BatchOperation<?> batchOp) throws IOException { 3874 boolean initialized = false; 3875 batchOp.startRegionOperation(); 3876 try { 3877 while (!batchOp.isDone()) { 3878 if (!batchOp.isInReplay()) { 3879 checkReadOnly(); 3880 } 3881 checkResources(); 3882 3883 if (!initialized) { 3884 this.writeRequestsCount.add(batchOp.size()); 3885 // validate and prepare batch for write, for MutationBatchOperation it also calls CP 3886 // prePut()/ preDelete() hooks 3887 batchOp.checkAndPrepare(); 3888 initialized = true; 3889 } 3890 doMiniBatchMutate(batchOp); 3891 requestFlushIfNeeded(); 3892 } 3893 } finally { 3894 batchOp.closeRegionOperation(); 3895 } 3896 return batchOp.retCodeDetails; 3897 } 3898 3899 /** 3900 * Called to do a piece of the batch that came in to {@link #batchMutate(Mutation[], long, long)} 3901 * In here we also handle replay of edits on region recover. 3902 * @return Change in size brought about by applying <code>batchOp</code> 3903 */ 3904 private void doMiniBatchMutate(BatchOperation<?> batchOp) throws IOException { 3905 boolean success = false; 3906 WALEdit walEdit = null; 3907 WriteEntry writeEntry = null; 3908 boolean locked = false; 3909 // We try to set up a batch in the range [batchOp.nextIndexToProcess,lastIndexExclusive) 3910 MiniBatchOperationInProgress<Mutation> miniBatchOp = null; 3911 /** Keep track of the locks we hold so we can release them in finally clause */ 3912 List<RowLock> acquiredRowLocks = Lists.newArrayListWithCapacity(batchOp.size()); 3913 try { 3914 // STEP 1. Try to acquire as many locks as we can and build mini-batch of operations with 3915 // locked rows 3916 miniBatchOp = batchOp.lockRowsAndBuildMiniBatch(acquiredRowLocks); 3917 3918 // We've now grabbed as many mutations off the list as we can 3919 // Ensure we acquire at least one. 3920 if (miniBatchOp.getReadyToWriteCount() <= 0) { 3921 // Nothing to put/delete -- an exception in the above such as NoSuchColumnFamily? 3922 return; 3923 } 3924 3925 lock(this.updatesLock.readLock(), miniBatchOp.getReadyToWriteCount()); 3926 locked = true; 3927 3928 // STEP 2. Update mini batch of all operations in progress with LATEST_TIMESTAMP timestamp 3929 // We should record the timestamp only after we have acquired the rowLock, 3930 // otherwise, newer puts/deletes are not guaranteed to have a newer timestamp 3931 long now = EnvironmentEdgeManager.currentTime(); 3932 batchOp.prepareMiniBatchOperations(miniBatchOp, now, acquiredRowLocks); 3933 3934 // STEP 3. Build WAL edit 3935 List<Pair<NonceKey, WALEdit>> walEdits = batchOp.buildWALEdits(miniBatchOp); 3936 3937 // STEP 4. Append the WALEdits to WAL and sync. 3938 for(Iterator<Pair<NonceKey, WALEdit>> it = walEdits.iterator(); it.hasNext();) { 3939 Pair<NonceKey, WALEdit> nonceKeyWALEditPair = it.next(); 3940 walEdit = nonceKeyWALEditPair.getSecond(); 3941 NonceKey nonceKey = nonceKeyWALEditPair.getFirst(); 3942 3943 if (walEdit != null && !walEdit.isEmpty()) { 3944 writeEntry = doWALAppend(walEdit, batchOp.durability, batchOp.getClusterIds(), now, 3945 nonceKey.getNonceGroup(), nonceKey.getNonce(), batchOp.getOrigLogSeqNum()); 3946 } 3947 3948 // Complete mvcc for all but last writeEntry (for replay case) 3949 if (it.hasNext() && writeEntry != null) { 3950 mvcc.complete(writeEntry); 3951 writeEntry = null; 3952 } 3953 } 3954 3955 // STEP 5. Write back to memStore 3956 // NOTE: writeEntry can be null here 3957 writeEntry = batchOp.writeMiniBatchOperationsToMemStore(miniBatchOp, writeEntry); 3958 3959 // STEP 6. Complete MiniBatchOperations: If required calls postBatchMutate() CP hook and 3960 // complete mvcc for last writeEntry 3961 batchOp.completeMiniBatchOperations(miniBatchOp, writeEntry); 3962 writeEntry = null; 3963 success = true; 3964 } finally { 3965 // Call complete rather than completeAndWait because we probably had error if walKey != null 3966 if (writeEntry != null) mvcc.complete(writeEntry); 3967 3968 if (locked) { 3969 this.updatesLock.readLock().unlock(); 3970 } 3971 releaseRowLocks(acquiredRowLocks); 3972 3973 final int finalLastIndexExclusive = 3974 miniBatchOp != null ? miniBatchOp.getLastIndexExclusive() : batchOp.size(); 3975 final boolean finalSuccess = success; 3976 batchOp.visitBatchOperations(true, finalLastIndexExclusive, (int i) -> { 3977 batchOp.retCodeDetails[i] = 3978 finalSuccess ? OperationStatus.SUCCESS : OperationStatus.FAILURE; 3979 return true; 3980 }); 3981 3982 batchOp.doPostOpCleanupForMiniBatch(miniBatchOp, walEdit, finalSuccess); 3983 3984 batchOp.nextIndexToProcess = finalLastIndexExclusive; 3985 } 3986 } 3987 3988 /** 3989 * Returns effective durability from the passed durability and 3990 * the table descriptor. 3991 */ 3992 protected Durability getEffectiveDurability(Durability d) { 3993 return d == Durability.USE_DEFAULT ? this.regionDurability : d; 3994 } 3995 3996 @Override 3997 public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOperator op, 3998 ByteArrayComparable comparator, TimeRange timeRange, Mutation mutation) throws IOException { 3999 checkMutationType(mutation, row); 4000 return doCheckAndRowMutate(row, family, qualifier, op, comparator, timeRange, null, mutation); 4001 } 4002 4003 @Override 4004 public boolean checkAndRowMutate(byte[] row, byte[] family, byte[] qualifier, CompareOperator op, 4005 ByteArrayComparable comparator, TimeRange timeRange, RowMutations rm) throws IOException { 4006 return doCheckAndRowMutate(row, family, qualifier, op, comparator, timeRange, rm, null); 4007 } 4008 4009 /** 4010 * checkAndMutate and checkAndRowMutate are 90% the same. Rather than copy/paste, below has 4011 * switches in the few places where there is deviation. 4012 */ 4013 private boolean doCheckAndRowMutate(byte[] row, byte[] family, byte[] qualifier, 4014 CompareOperator op, ByteArrayComparable comparator, TimeRange timeRange, 4015 RowMutations rowMutations, Mutation mutation) 4016 throws IOException { 4017 // Could do the below checks but seems wacky with two callers only. Just comment out for now. 4018 // One caller passes a Mutation, the other passes RowMutation. Presume all good so we don't 4019 // need these commented out checks. 4020 // if (rowMutations == null && mutation == null) throw new DoNotRetryIOException("Both null"); 4021 // if (rowMutations != null && mutation != null) throw new DoNotRetryIOException("Both set"); 4022 checkReadOnly(); 4023 // TODO, add check for value length also move this check to the client 4024 checkResources(); 4025 startRegionOperation(); 4026 try { 4027 Get get = new Get(row); 4028 checkFamily(family); 4029 get.addColumn(family, qualifier); 4030 if (timeRange != null) { 4031 get.setTimeRange(timeRange.getMin(), timeRange.getMax()); 4032 } 4033 // Lock row - note that doBatchMutate will relock this row if called 4034 checkRow(row, "doCheckAndRowMutate"); 4035 RowLock rowLock = getRowLockInternal(get.getRow(), false, null); 4036 try { 4037 if (mutation != null && this.getCoprocessorHost() != null) { 4038 // Call coprocessor. 4039 Boolean processed = null; 4040 if (mutation instanceof Put) { 4041 processed = this.getCoprocessorHost().preCheckAndPutAfterRowLock(row, family, 4042 qualifier, op, comparator, (Put)mutation); 4043 } else if (mutation instanceof Delete) { 4044 processed = this.getCoprocessorHost().preCheckAndDeleteAfterRowLock(row, family, 4045 qualifier, op, comparator, (Delete)mutation); 4046 } 4047 if (processed != null) { 4048 return processed; 4049 } 4050 } 4051 // NOTE: We used to wait here until mvcc caught up: mvcc.await(); 4052 // Supposition is that now all changes are done under row locks, then when we go to read, 4053 // we'll get the latest on this row. 4054 List<Cell> result = get(get, false); 4055 boolean valueIsNull = comparator.getValue() == null || comparator.getValue().length == 0; 4056 boolean matches = false; 4057 long cellTs = 0; 4058 if (result.isEmpty() && valueIsNull) { 4059 matches = true; 4060 } else if (result.size() > 0 && result.get(0).getValueLength() == 0 && valueIsNull) { 4061 matches = true; 4062 cellTs = result.get(0).getTimestamp(); 4063 } else if (result.size() == 1 && !valueIsNull) { 4064 Cell kv = result.get(0); 4065 cellTs = kv.getTimestamp(); 4066 int compareResult = PrivateCellUtil.compareValue(kv, comparator); 4067 matches = matches(op, compareResult); 4068 } 4069 // If matches put the new put or delete the new delete 4070 if (matches) { 4071 // We have acquired the row lock already. If the system clock is NOT monotonically 4072 // non-decreasing (see HBASE-14070) we should make sure that the mutation has a 4073 // larger timestamp than what was observed via Get. doBatchMutate already does this, but 4074 // there is no way to pass the cellTs. See HBASE-14054. 4075 long now = EnvironmentEdgeManager.currentTime(); 4076 long ts = Math.max(now, cellTs); // ensure write is not eclipsed 4077 byte[] byteTs = Bytes.toBytes(ts); 4078 if (mutation != null) { 4079 if (mutation instanceof Put) { 4080 updateCellTimestamps(mutation.getFamilyCellMap().values(), byteTs); 4081 } 4082 // And else 'delete' is not needed since it already does a second get, and sets the 4083 // timestamp from get (see prepareDeleteTimestamps). 4084 } else { 4085 for (Mutation m: rowMutations.getMutations()) { 4086 if (m instanceof Put) { 4087 updateCellTimestamps(m.getFamilyCellMap().values(), byteTs); 4088 } 4089 } 4090 // And else 'delete' is not needed since it already does a second get, and sets the 4091 // timestamp from get (see prepareDeleteTimestamps). 4092 } 4093 // All edits for the given row (across all column families) must happen atomically. 4094 if (mutation != null) { 4095 doBatchMutate(mutation); 4096 } else { 4097 mutateRow(rowMutations); 4098 } 4099 this.checkAndMutateChecksPassed.increment(); 4100 return true; 4101 } 4102 this.checkAndMutateChecksFailed.increment(); 4103 return false; 4104 } finally { 4105 rowLock.release(); 4106 } 4107 } finally { 4108 closeRegionOperation(); 4109 } 4110 } 4111 4112 private void checkMutationType(final Mutation mutation, final byte [] row) 4113 throws DoNotRetryIOException { 4114 boolean isPut = mutation instanceof Put; 4115 if (!isPut && !(mutation instanceof Delete)) { 4116 throw new org.apache.hadoop.hbase.DoNotRetryIOException("Action must be Put or Delete"); 4117 } 4118 if (!Bytes.equals(row, mutation.getRow())) { 4119 throw new org.apache.hadoop.hbase.DoNotRetryIOException("Action's getRow must match"); 4120 } 4121 } 4122 4123 private boolean matches(final CompareOperator op, final int compareResult) { 4124 boolean matches = false; 4125 switch (op) { 4126 case LESS: 4127 matches = compareResult < 0; 4128 break; 4129 case LESS_OR_EQUAL: 4130 matches = compareResult <= 0; 4131 break; 4132 case EQUAL: 4133 matches = compareResult == 0; 4134 break; 4135 case NOT_EQUAL: 4136 matches = compareResult != 0; 4137 break; 4138 case GREATER_OR_EQUAL: 4139 matches = compareResult >= 0; 4140 break; 4141 case GREATER: 4142 matches = compareResult > 0; 4143 break; 4144 default: 4145 throw new RuntimeException("Unknown Compare op " + op.name()); 4146 } 4147 return matches; 4148 } 4149 4150 4151 private void doBatchMutate(Mutation mutation) throws IOException { 4152 // Currently this is only called for puts and deletes, so no nonces. 4153 OperationStatus[] batchMutate = this.batchMutate(new Mutation[]{mutation}); 4154 if (batchMutate[0].getOperationStatusCode().equals(OperationStatusCode.SANITY_CHECK_FAILURE)) { 4155 throw new FailedSanityCheckException(batchMutate[0].getExceptionMsg()); 4156 } else if (batchMutate[0].getOperationStatusCode().equals(OperationStatusCode.BAD_FAMILY)) { 4157 throw new NoSuchColumnFamilyException(batchMutate[0].getExceptionMsg()); 4158 } 4159 } 4160 4161 /** 4162 * Complete taking the snapshot on the region. Writes the region info and adds references to the 4163 * working snapshot directory. 4164 * 4165 * TODO for api consistency, consider adding another version with no {@link ForeignExceptionSnare} 4166 * arg. (In the future other cancellable HRegion methods could eventually add a 4167 * {@link ForeignExceptionSnare}, or we could do something fancier). 4168 * 4169 * @param desc snapshot description object 4170 * @param exnSnare ForeignExceptionSnare that captures external exceptions in case we need to 4171 * bail out. This is allowed to be null and will just be ignored in that case. 4172 * @throws IOException if there is an external or internal error causing the snapshot to fail 4173 */ 4174 public void addRegionToSnapshot(SnapshotDescription desc, 4175 ForeignExceptionSnare exnSnare) throws IOException { 4176 Path rootDir = FSUtils.getRootDir(conf); 4177 Path snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir); 4178 4179 SnapshotManifest manifest = SnapshotManifest.create(conf, getFilesystem(), 4180 snapshotDir, desc, exnSnare); 4181 manifest.addRegion(this); 4182 } 4183 4184 private void updateSequenceId(final Iterable<List<Cell>> cellItr, final long sequenceId) 4185 throws IOException { 4186 for (List<Cell> cells: cellItr) { 4187 if (cells == null) return; 4188 for (Cell cell : cells) { 4189 PrivateCellUtil.setSequenceId(cell, sequenceId); 4190 } 4191 } 4192 } 4193 4194 /** 4195 * Replace any cell timestamps set to {@link org.apache.hadoop.hbase.HConstants#LATEST_TIMESTAMP} 4196 * provided current timestamp. 4197 * @param cellItr 4198 * @param now 4199 */ 4200 private static void updateCellTimestamps(final Iterable<List<Cell>> cellItr, final byte[] now) 4201 throws IOException { 4202 for (List<Cell> cells: cellItr) { 4203 if (cells == null) continue; 4204 // Optimization: 'foreach' loop is not used. See: 4205 // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects 4206 assert cells instanceof RandomAccess; 4207 int listSize = cells.size(); 4208 for (int i = 0; i < listSize; i++) { 4209 PrivateCellUtil.updateLatestStamp(cells.get(i), now); 4210 } 4211 } 4212 } 4213 4214 /** 4215 * Possibly rewrite incoming cell tags. 4216 */ 4217 void rewriteCellTags(Map<byte[], List<Cell>> familyMap, final Mutation m) { 4218 // Check if we have any work to do and early out otherwise 4219 // Update these checks as more logic is added here 4220 if (m.getTTL() == Long.MAX_VALUE) { 4221 return; 4222 } 4223 4224 // From this point we know we have some work to do 4225 for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) { 4226 List<Cell> cells = e.getValue(); 4227 assert cells instanceof RandomAccess; 4228 int listSize = cells.size(); 4229 for (int i = 0; i < listSize; i++) { 4230 Cell cell = cells.get(i); 4231 List<Tag> newTags = TagUtil.carryForwardTags(null, cell); 4232 newTags = TagUtil.carryForwardTTLTag(newTags, m.getTTL()); 4233 // Rewrite the cell with the updated set of tags 4234 cells.set(i, PrivateCellUtil.createCell(cell, newTags)); 4235 } 4236 } 4237 } 4238 4239 /* 4240 * Check if resources to support an update. 4241 * 4242 * We throw RegionTooBusyException if above memstore limit 4243 * and expect client to retry using some kind of backoff 4244 */ 4245 void checkResources() throws RegionTooBusyException { 4246 // If catalog region, do not impose resource constraints or block updates. 4247 if (this.getRegionInfo().isMetaRegion()) return; 4248 4249 MemStoreSize mss = this.memStoreSizing.getMemStoreSize(); 4250 if (mss.getHeapSize() + mss.getOffHeapSize() > this.blockingMemStoreSize) { 4251 blockedRequestsCount.increment(); 4252 requestFlush(); 4253 // Don't print current limit because it will vary too much. The message is used as a key 4254 // over in RetriesExhaustedWithDetailsException processing. 4255 throw new RegionTooBusyException("Over memstore limit=" + 4256 org.apache.hadoop.hbase.procedure2.util.StringUtils.humanSize(this.blockingMemStoreSize) + 4257 ", regionName=" + 4258 (this.getRegionInfo() == null? "unknown": this.getRegionInfo().getEncodedName()) + 4259 ", server=" + (this.getRegionServerServices() == null? "unknown": 4260 this.getRegionServerServices().getServerName())); 4261 } 4262 } 4263 4264 /** 4265 * @throws IOException Throws exception if region is in read-only mode. 4266 */ 4267 protected void checkReadOnly() throws IOException { 4268 if (isReadOnly()) { 4269 throw new DoNotRetryIOException("region is read only"); 4270 } 4271 } 4272 4273 protected void checkReadsEnabled() throws IOException { 4274 if (!this.writestate.readsEnabled) { 4275 throw new IOException(getRegionInfo().getEncodedName() 4276 + ": The region's reads are disabled. Cannot serve the request"); 4277 } 4278 } 4279 4280 public void setReadsEnabled(boolean readsEnabled) { 4281 if (readsEnabled && !this.writestate.readsEnabled) { 4282 LOG.info(getRegionInfo().getEncodedName() + " : Enabling reads for region."); 4283 } 4284 this.writestate.setReadsEnabled(readsEnabled); 4285 } 4286 4287 /** 4288 * Add updates first to the wal and then add values to memstore. 4289 * Warning: Assumption is caller has lock on passed in row. 4290 * @param edits Cell updates by column 4291 * @throws IOException 4292 */ 4293 void put(final byte [] row, byte [] family, List<Cell> edits) 4294 throws IOException { 4295 NavigableMap<byte[], List<Cell>> familyMap; 4296 familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 4297 4298 familyMap.put(family, edits); 4299 Put p = new Put(row); 4300 p.setFamilyCellMap(familyMap); 4301 doBatchMutate(p); 4302 } 4303 4304 /** 4305 * @param delta If we are doing delta changes -- e.g. increment/append -- then this flag will be 4306 * set; when set we will run operations that make sense in the increment/append scenario 4307 * but that do not make sense otherwise. 4308 * @see #applyToMemStore(HStore, Cell, MemStoreSizing) 4309 */ 4310 private void applyToMemStore(HStore store, List<Cell> cells, boolean delta, 4311 MemStoreSizing memstoreAccounting) throws IOException { 4312 // Any change in how we update Store/MemStore needs to also be done in other applyToMemStore!!!! 4313 boolean upsert = delta && store.getColumnFamilyDescriptor().getMaxVersions() == 1; 4314 if (upsert) { 4315 store.upsert(cells, getSmallestReadPoint(), memstoreAccounting); 4316 } else { 4317 store.add(cells, memstoreAccounting); 4318 } 4319 } 4320 4321 /** 4322 * @see #applyToMemStore(HStore, List, boolean, MemStoreSizing) 4323 */ 4324 private void applyToMemStore(HStore store, Cell cell, MemStoreSizing memstoreAccounting) 4325 throws IOException { 4326 // Any change in how we update Store/MemStore needs to also be done in other applyToMemStore!!!! 4327 if (store == null) { 4328 checkFamily(CellUtil.cloneFamily(cell)); 4329 // Unreachable because checkFamily will throw exception 4330 } 4331 store.add(cell, memstoreAccounting); 4332 } 4333 4334 /** 4335 * Check the collection of families for validity. 4336 * @param families 4337 * @throws NoSuchColumnFamilyException 4338 */ 4339 public void checkFamilies(Collection<byte[]> families) throws NoSuchColumnFamilyException { 4340 for (byte[] family : families) { 4341 checkFamily(family); 4342 } 4343 } 4344 4345 /** 4346 * Check the collection of families for valid timestamps 4347 * @param familyMap 4348 * @param now current timestamp 4349 * @throws FailedSanityCheckException 4350 */ 4351 public void checkTimestamps(final Map<byte[], List<Cell>> familyMap, long now) 4352 throws FailedSanityCheckException { 4353 if (timestampSlop == HConstants.LATEST_TIMESTAMP) { 4354 return; 4355 } 4356 long maxTs = now + timestampSlop; 4357 for (List<Cell> kvs : familyMap.values()) { 4358 // Optimization: 'foreach' loop is not used. See: 4359 // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects 4360 assert kvs instanceof RandomAccess; 4361 int listSize = kvs.size(); 4362 for (int i=0; i < listSize; i++) { 4363 Cell cell = kvs.get(i); 4364 // see if the user-side TS is out of range. latest = server-side 4365 long ts = cell.getTimestamp(); 4366 if (ts != HConstants.LATEST_TIMESTAMP && ts > maxTs) { 4367 throw new FailedSanityCheckException("Timestamp for KV out of range " 4368 + cell + " (too.new=" + timestampSlop + ")"); 4369 } 4370 } 4371 } 4372 } 4373 4374 /* 4375 * @param size 4376 * @return True if size is over the flush threshold 4377 */ 4378 private boolean isFlushSize(MemStoreSize size) { 4379 return size.getHeapSize() + size.getOffHeapSize() > getMemStoreFlushSize(); 4380 } 4381 4382 /** 4383 * Read the edits put under this region by wal splitting process. Put 4384 * the recovered edits back up into this region. 4385 * 4386 * <p>We can ignore any wal message that has a sequence ID that's equal to or 4387 * lower than minSeqId. (Because we know such messages are already 4388 * reflected in the HFiles.) 4389 * 4390 * <p>While this is running we are putting pressure on memory yet we are 4391 * outside of our usual accounting because we are not yet an onlined region 4392 * (this stuff is being run as part of Region initialization). This means 4393 * that if we're up against global memory limits, we'll not be flagged to flush 4394 * because we are not online. We can't be flushed by usual mechanisms anyways; 4395 * we're not yet online so our relative sequenceids are not yet aligned with 4396 * WAL sequenceids -- not till we come up online, post processing of split 4397 * edits. 4398 * 4399 * <p>But to help relieve memory pressure, at least manage our own heap size 4400 * flushing if are in excess of per-region limits. Flushing, though, we have 4401 * to be careful and avoid using the regionserver/wal sequenceid. Its running 4402 * on a different line to whats going on in here in this region context so if we 4403 * crashed replaying these edits, but in the midst had a flush that used the 4404 * regionserver wal with a sequenceid in excess of whats going on in here 4405 * in this region and with its split editlogs, then we could miss edits the 4406 * next time we go to recover. So, we have to flush inline, using seqids that 4407 * make sense in a this single region context only -- until we online. 4408 * 4409 * @param maxSeqIdInStores Any edit found in split editlogs needs to be in excess of 4410 * the maxSeqId for the store to be applied, else its skipped. 4411 * @return the sequence id of the last edit added to this region out of the 4412 * recovered edits log or <code>minSeqId</code> if nothing added from editlogs. 4413 * @throws IOException 4414 */ 4415 protected long replayRecoveredEditsIfAny(Map<byte[], Long> maxSeqIdInStores, 4416 final CancelableProgressable reporter, final MonitoredTask status) 4417 throws IOException { 4418 long minSeqIdForTheRegion = -1; 4419 for (Long maxSeqIdInStore : maxSeqIdInStores.values()) { 4420 if (maxSeqIdInStore < minSeqIdForTheRegion || minSeqIdForTheRegion == -1) { 4421 minSeqIdForTheRegion = maxSeqIdInStore; 4422 } 4423 } 4424 long seqid = minSeqIdForTheRegion; 4425 4426 FileSystem walFS = getWalFileSystem(); 4427 FileSystem rootFS = getFilesystem(); 4428 Path regionDir = getWALRegionDir(); 4429 Path defaultRegionDir = getRegionDir(FSUtils.getRootDir(conf), getRegionInfo()); 4430 4431 // This is to ensure backwards compatability with HBASE-20723 where recovered edits can appear 4432 // under the root dir even if walDir is set. 4433 NavigableSet<Path> filesUnderRootDir = null; 4434 if (!regionDir.equals(defaultRegionDir)) { 4435 filesUnderRootDir = 4436 WALSplitter.getSplitEditFilesSorted(rootFS, defaultRegionDir); 4437 seqid = Math.max(seqid, 4438 replayRecoveredEditsForPaths(minSeqIdForTheRegion, rootFS, filesUnderRootDir, reporter, 4439 defaultRegionDir)); 4440 } 4441 4442 NavigableSet<Path> files = WALSplitter.getSplitEditFilesSorted(walFS, regionDir); 4443 seqid = Math.max(seqid, replayRecoveredEditsForPaths(minSeqIdForTheRegion, walFS, 4444 files, reporter, regionDir)); 4445 4446 if (seqid > minSeqIdForTheRegion) { 4447 // Then we added some edits to memory. Flush and cleanup split edit files. 4448 internalFlushcache(null, seqid, stores.values(), status, false, FlushLifeCycleTracker.DUMMY); 4449 } 4450 // Now delete the content of recovered edits. We're done w/ them. 4451 if (files.size() > 0 && this.conf.getBoolean("hbase.region.archive.recovered.edits", false)) { 4452 // For debugging data loss issues! 4453 // If this flag is set, make use of the hfile archiving by making recovered.edits a fake 4454 // column family. Have to fake out file type too by casting our recovered.edits as storefiles 4455 String fakeFamilyName = WALSplitter.getRegionDirRecoveredEditsDir(regionDir).getName(); 4456 Set<HStoreFile> fakeStoreFiles = new HashSet<>(files.size()); 4457 for (Path file: files) { 4458 fakeStoreFiles.add( 4459 new HStoreFile(walFS, file, this.conf, null, null, true)); 4460 } 4461 getRegionWALFileSystem().removeStoreFiles(fakeFamilyName, fakeStoreFiles); 4462 } else { 4463 if (filesUnderRootDir != null) { 4464 for (Path file : filesUnderRootDir) { 4465 if (!rootFS.delete(file, false)) { 4466 LOG.error("Failed delete of {} from under the root directory.", file); 4467 } else { 4468 LOG.debug("Deleted recovered.edits under root directory. file=" + file); 4469 } 4470 } 4471 } 4472 for (Path file: files) { 4473 if (!walFS.delete(file, false)) { 4474 LOG.error("Failed delete of " + file); 4475 } else { 4476 LOG.debug("Deleted recovered.edits file=" + file); 4477 } 4478 } 4479 } 4480 return seqid; 4481 } 4482 4483 private long replayRecoveredEditsForPaths(long minSeqIdForTheRegion, FileSystem fs, 4484 final NavigableSet<Path> files, final CancelableProgressable reporter, final Path regionDir) 4485 throws IOException { 4486 long seqid = minSeqIdForTheRegion; 4487 if (LOG.isDebugEnabled()) { 4488 LOG.debug("Found " + (files == null ? 0 : files.size()) 4489 + " recovered edits file(s) under " + regionDir); 4490 } 4491 4492 if (files == null || files.isEmpty()) { 4493 return minSeqIdForTheRegion; 4494 } 4495 4496 for (Path edits: files) { 4497 if (edits == null || !fs.exists(edits)) { 4498 LOG.warn("Null or non-existent edits file: " + edits); 4499 continue; 4500 } 4501 if (isZeroLengthThenDelete(fs, edits)) continue; 4502 4503 long maxSeqId; 4504 String fileName = edits.getName(); 4505 maxSeqId = Math.abs(Long.parseLong(fileName)); 4506 if (maxSeqId <= minSeqIdForTheRegion) { 4507 if (LOG.isDebugEnabled()) { 4508 String msg = "Maximum sequenceid for this wal is " + maxSeqId 4509 + " and minimum sequenceid for the region is " + minSeqIdForTheRegion 4510 + ", skipped the whole file, path=" + edits; 4511 LOG.debug(msg); 4512 } 4513 continue; 4514 } 4515 4516 try { 4517 // replay the edits. Replay can return -1 if everything is skipped, only update 4518 // if seqId is greater 4519 seqid = Math.max(seqid, replayRecoveredEdits(edits, maxSeqIdInStores, reporter, fs)); 4520 } catch (IOException e) { 4521 boolean skipErrors = conf.getBoolean( 4522 HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS, 4523 conf.getBoolean( 4524 "hbase.skip.errors", 4525 HConstants.DEFAULT_HREGION_EDITS_REPLAY_SKIP_ERRORS)); 4526 if (conf.get("hbase.skip.errors") != null) { 4527 LOG.warn( 4528 "The property 'hbase.skip.errors' has been deprecated. Please use " + 4529 HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS + " instead."); 4530 } 4531 if (skipErrors) { 4532 Path p = WALSplitter.moveAsideBadEditsFile(walFS, edits); 4533 LOG.error(HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS 4534 + "=true so continuing. Renamed " + edits + 4535 " as " + p, e); 4536 } else { 4537 throw e; 4538 } 4539 } 4540 } 4541 return seqid; 4542 } 4543 4544 /* 4545 * @param edits File of recovered edits. 4546 * @param maxSeqIdInStores Maximum sequenceid found in each store. Edits in wal 4547 * must be larger than this to be replayed for each store. 4548 * @param reporter 4549 * @return the sequence id of the last edit added to this region out of the 4550 * recovered edits log or <code>minSeqId</code> if nothing added from editlogs. 4551 * @throws IOException 4552 */ 4553 private long replayRecoveredEdits(final Path edits, 4554 Map<byte[], Long> maxSeqIdInStores, final CancelableProgressable reporter, FileSystem fs) 4555 throws IOException { 4556 String msg = "Replaying edits from " + edits; 4557 LOG.info(msg); 4558 MonitoredTask status = TaskMonitor.get().createStatus(msg); 4559 4560 status.setStatus("Opening recovered edits"); 4561 WAL.Reader reader = null; 4562 try { 4563 reader = WALFactory.createReader(fs, edits, conf); 4564 long currentEditSeqId = -1; 4565 long currentReplaySeqId = -1; 4566 long firstSeqIdInLog = -1; 4567 long skippedEdits = 0; 4568 long editsCount = 0; 4569 long intervalEdits = 0; 4570 WAL.Entry entry; 4571 HStore store = null; 4572 boolean reported_once = false; 4573 ServerNonceManager ng = this.rsServices == null ? null : this.rsServices.getNonceManager(); 4574 4575 try { 4576 // How many edits seen before we check elapsed time 4577 int interval = this.conf.getInt("hbase.hstore.report.interval.edits", 2000); 4578 // How often to send a progress report (default 1/2 master timeout) 4579 int period = this.conf.getInt("hbase.hstore.report.period", 300000); 4580 long lastReport = EnvironmentEdgeManager.currentTime(); 4581 4582 if (coprocessorHost != null) { 4583 coprocessorHost.preReplayWALs(this.getRegionInfo(), edits); 4584 } 4585 4586 while ((entry = reader.next()) != null) { 4587 WALKey key = entry.getKey(); 4588 WALEdit val = entry.getEdit(); 4589 4590 if (ng != null) { // some test, or nonces disabled 4591 ng.reportOperationFromWal(key.getNonceGroup(), key.getNonce(), key.getWriteTime()); 4592 } 4593 4594 if (reporter != null) { 4595 intervalEdits += val.size(); 4596 if (intervalEdits >= interval) { 4597 // Number of edits interval reached 4598 intervalEdits = 0; 4599 long cur = EnvironmentEdgeManager.currentTime(); 4600 if (lastReport + period <= cur) { 4601 status.setStatus("Replaying edits..." + 4602 " skipped=" + skippedEdits + 4603 " edits=" + editsCount); 4604 // Timeout reached 4605 if(!reporter.progress()) { 4606 msg = "Progressable reporter failed, stopping replay"; 4607 LOG.warn(msg); 4608 status.abort(msg); 4609 throw new IOException(msg); 4610 } 4611 reported_once = true; 4612 lastReport = cur; 4613 } 4614 } 4615 } 4616 4617 if (firstSeqIdInLog == -1) { 4618 firstSeqIdInLog = key.getSequenceId(); 4619 } 4620 if (currentEditSeqId > key.getSequenceId()) { 4621 // when this condition is true, it means we have a serious defect because we need to 4622 // maintain increasing SeqId for WAL edits per region 4623 LOG.error(getRegionInfo().getEncodedName() + " : " 4624 + "Found decreasing SeqId. PreId=" + currentEditSeqId + " key=" + key 4625 + "; edit=" + val); 4626 } else { 4627 currentEditSeqId = key.getSequenceId(); 4628 } 4629 currentReplaySeqId = (key.getOrigLogSeqNum() > 0) ? 4630 key.getOrigLogSeqNum() : currentEditSeqId; 4631 4632 // Start coprocessor replay here. The coprocessor is for each WALEdit 4633 // instead of a KeyValue. 4634 if (coprocessorHost != null) { 4635 status.setStatus("Running pre-WAL-restore hook in coprocessors"); 4636 if (coprocessorHost.preWALRestore(this.getRegionInfo(), key, val)) { 4637 // if bypass this wal entry, ignore it ... 4638 continue; 4639 } 4640 } 4641 boolean checkRowWithinBoundary = false; 4642 // Check this edit is for this region. 4643 if (!Bytes.equals(key.getEncodedRegionName(), 4644 this.getRegionInfo().getEncodedNameAsBytes())) { 4645 checkRowWithinBoundary = true; 4646 } 4647 4648 boolean flush = false; 4649 MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing(); 4650 for (Cell cell: val.getCells()) { 4651 // Check this edit is for me. Also, guard against writing the special 4652 // METACOLUMN info such as HBASE::CACHEFLUSH entries 4653 if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) { 4654 // if region names don't match, skipp replaying compaction marker 4655 if (!checkRowWithinBoundary) { 4656 //this is a special edit, we should handle it 4657 CompactionDescriptor compaction = WALEdit.getCompaction(cell); 4658 if (compaction != null) { 4659 //replay the compaction 4660 replayWALCompactionMarker(compaction, false, true, Long.MAX_VALUE); 4661 } 4662 } 4663 skippedEdits++; 4664 continue; 4665 } 4666 // Figure which store the edit is meant for. 4667 if (store == null || !CellUtil.matchingFamily(cell, 4668 store.getColumnFamilyDescriptor().getName())) { 4669 store = getStore(cell); 4670 } 4671 if (store == null) { 4672 // This should never happen. Perhaps schema was changed between 4673 // crash and redeploy? 4674 LOG.warn("No family for " + cell); 4675 skippedEdits++; 4676 continue; 4677 } 4678 if (checkRowWithinBoundary && !rowIsInRange(this.getRegionInfo(), 4679 cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())) { 4680 LOG.warn("Row of " + cell + " is not within region boundary"); 4681 skippedEdits++; 4682 continue; 4683 } 4684 // Now, figure if we should skip this edit. 4685 if (key.getSequenceId() <= maxSeqIdInStores.get(store.getColumnFamilyDescriptor() 4686 .getName())) { 4687 skippedEdits++; 4688 continue; 4689 } 4690 PrivateCellUtil.setSequenceId(cell, currentReplaySeqId); 4691 4692 restoreEdit(store, cell, memStoreSizing); 4693 editsCount++; 4694 } 4695 MemStoreSize mss = memStoreSizing.getMemStoreSize(); 4696 incMemStoreSize(mss); 4697 flush = isFlushSize(this.memStoreSizing.getMemStoreSize()); 4698 if (flush) { 4699 internalFlushcache(null, currentEditSeqId, stores.values(), status, false, 4700 FlushLifeCycleTracker.DUMMY); 4701 } 4702 4703 if (coprocessorHost != null) { 4704 coprocessorHost.postWALRestore(this.getRegionInfo(), key, val); 4705 } 4706 } 4707 4708 if (coprocessorHost != null) { 4709 coprocessorHost.postReplayWALs(this.getRegionInfo(), edits); 4710 } 4711 } catch (EOFException eof) { 4712 Path p = WALSplitter.moveAsideBadEditsFile(walFS, edits); 4713 msg = "EnLongAddered EOF. Most likely due to Master failure during " + 4714 "wal splitting, so we have this data in another edit. " + 4715 "Continuing, but renaming " + edits + " as " + p; 4716 LOG.warn(msg, eof); 4717 status.abort(msg); 4718 } catch (IOException ioe) { 4719 // If the IOE resulted from bad file format, 4720 // then this problem is idempotent and retrying won't help 4721 if (ioe.getCause() instanceof ParseException) { 4722 Path p = WALSplitter.moveAsideBadEditsFile(walFS, edits); 4723 msg = "File corruption enLongAddered! " + 4724 "Continuing, but renaming " + edits + " as " + p; 4725 LOG.warn(msg, ioe); 4726 status.setStatus(msg); 4727 } else { 4728 status.abort(StringUtils.stringifyException(ioe)); 4729 // other IO errors may be transient (bad network connection, 4730 // checksum exception on one datanode, etc). throw & retry 4731 throw ioe; 4732 } 4733 } 4734 if (reporter != null && !reported_once) { 4735 reporter.progress(); 4736 } 4737 msg = "Applied " + editsCount + ", skipped " + skippedEdits + 4738 ", firstSequenceIdInLog=" + firstSeqIdInLog + 4739 ", maxSequenceIdInLog=" + currentEditSeqId + ", path=" + edits; 4740 status.markComplete(msg); 4741 LOG.debug(msg); 4742 return currentEditSeqId; 4743 } finally { 4744 status.cleanup(); 4745 if (reader != null) { 4746 reader.close(); 4747 } 4748 } 4749 } 4750 4751 /** 4752 * Call to complete a compaction. Its for the case where we find in the WAL a compaction 4753 * that was not finished. We could find one recovering a WAL after a regionserver crash. 4754 * See HBASE-2331. 4755 */ 4756 void replayWALCompactionMarker(CompactionDescriptor compaction, boolean pickCompactionFiles, 4757 boolean removeFiles, long replaySeqId) 4758 throws IOException { 4759 try { 4760 checkTargetRegion(compaction.getEncodedRegionName().toByteArray(), 4761 "Compaction marker from WAL ", compaction); 4762 } catch (WrongRegionException wre) { 4763 if (RegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { 4764 // skip the compaction marker since it is not for this region 4765 return; 4766 } 4767 throw wre; 4768 } 4769 4770 synchronized (writestate) { 4771 if (replaySeqId < lastReplayedOpenRegionSeqId) { 4772 LOG.warn(getRegionInfo().getEncodedName() + " : " 4773 + "Skipping replaying compaction event :" + TextFormat.shortDebugString(compaction) 4774 + " because its sequence id " + replaySeqId + " is smaller than this regions " 4775 + "lastReplayedOpenRegionSeqId of " + lastReplayedOpenRegionSeqId); 4776 return; 4777 } 4778 if (replaySeqId < lastReplayedCompactionSeqId) { 4779 LOG.warn(getRegionInfo().getEncodedName() + " : " 4780 + "Skipping replaying compaction event :" + TextFormat.shortDebugString(compaction) 4781 + " because its sequence id " + replaySeqId + " is smaller than this regions " 4782 + "lastReplayedCompactionSeqId of " + lastReplayedCompactionSeqId); 4783 return; 4784 } else { 4785 lastReplayedCompactionSeqId = replaySeqId; 4786 } 4787 4788 if (LOG.isDebugEnabled()) { 4789 LOG.debug(getRegionInfo().getEncodedName() + " : " 4790 + "Replaying compaction marker " + TextFormat.shortDebugString(compaction) 4791 + " with seqId=" + replaySeqId + " and lastReplayedOpenRegionSeqId=" 4792 + lastReplayedOpenRegionSeqId); 4793 } 4794 4795 startRegionOperation(Operation.REPLAY_EVENT); 4796 try { 4797 HStore store = this.getStore(compaction.getFamilyName().toByteArray()); 4798 if (store == null) { 4799 LOG.warn(getRegionInfo().getEncodedName() + " : " 4800 + "Found Compaction WAL edit for deleted family:" 4801 + Bytes.toString(compaction.getFamilyName().toByteArray())); 4802 return; 4803 } 4804 store.replayCompactionMarker(compaction, pickCompactionFiles, removeFiles); 4805 logRegionFiles(); 4806 } catch (FileNotFoundException ex) { 4807 LOG.warn(getRegionInfo().getEncodedName() + " : " 4808 + "At least one of the store files in compaction: " 4809 + TextFormat.shortDebugString(compaction) 4810 + " doesn't exist any more. Skip loading the file(s)", ex); 4811 } finally { 4812 closeRegionOperation(Operation.REPLAY_EVENT); 4813 } 4814 } 4815 } 4816 4817 void replayWALFlushMarker(FlushDescriptor flush, long replaySeqId) throws IOException { 4818 checkTargetRegion(flush.getEncodedRegionName().toByteArray(), 4819 "Flush marker from WAL ", flush); 4820 4821 if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { 4822 return; // if primary nothing to do 4823 } 4824 4825 if (LOG.isDebugEnabled()) { 4826 LOG.debug(getRegionInfo().getEncodedName() + " : " 4827 + "Replaying flush marker " + TextFormat.shortDebugString(flush)); 4828 } 4829 4830 startRegionOperation(Operation.REPLAY_EVENT); // use region close lock to guard against close 4831 try { 4832 FlushAction action = flush.getAction(); 4833 switch (action) { 4834 case START_FLUSH: 4835 replayWALFlushStartMarker(flush); 4836 break; 4837 case COMMIT_FLUSH: 4838 replayWALFlushCommitMarker(flush); 4839 break; 4840 case ABORT_FLUSH: 4841 replayWALFlushAbortMarker(flush); 4842 break; 4843 case CANNOT_FLUSH: 4844 replayWALFlushCannotFlushMarker(flush, replaySeqId); 4845 break; 4846 default: 4847 LOG.warn(getRegionInfo().getEncodedName() + " : " + 4848 "Received a flush event with unknown action, ignoring. " + 4849 TextFormat.shortDebugString(flush)); 4850 break; 4851 } 4852 4853 logRegionFiles(); 4854 } finally { 4855 closeRegionOperation(Operation.REPLAY_EVENT); 4856 } 4857 } 4858 4859 /** Replay the flush marker from primary region by creating a corresponding snapshot of 4860 * the store memstores, only if the memstores do not have a higher seqId from an earlier wal 4861 * edit (because the events may be coming out of order). 4862 */ 4863 @VisibleForTesting 4864 PrepareFlushResult replayWALFlushStartMarker(FlushDescriptor flush) throws IOException { 4865 long flushSeqId = flush.getFlushSequenceNumber(); 4866 4867 HashSet<HStore> storesToFlush = new HashSet<>(); 4868 for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) { 4869 byte[] family = storeFlush.getFamilyName().toByteArray(); 4870 HStore store = getStore(family); 4871 if (store == null) { 4872 LOG.warn(getRegionInfo().getEncodedName() + " : " 4873 + "Received a flush start marker from primary, but the family is not found. Ignoring" 4874 + " StoreFlushDescriptor:" + TextFormat.shortDebugString(storeFlush)); 4875 continue; 4876 } 4877 storesToFlush.add(store); 4878 } 4879 4880 MonitoredTask status = TaskMonitor.get().createStatus("Preparing flush " + this); 4881 4882 // we will use writestate as a coarse-grain lock for all the replay events 4883 // (flush, compaction, region open etc) 4884 synchronized (writestate) { 4885 try { 4886 if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) { 4887 LOG.warn(getRegionInfo().getEncodedName() + " : " 4888 + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush) 4889 + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " 4890 + " of " + lastReplayedOpenRegionSeqId); 4891 return null; 4892 } 4893 if (numMutationsWithoutWAL.sum() > 0) { 4894 numMutationsWithoutWAL.reset(); 4895 dataInMemoryWithoutWAL.reset(); 4896 } 4897 4898 if (!writestate.flushing) { 4899 // we do not have an active snapshot and corresponding this.prepareResult. This means 4900 // we can just snapshot our memstores and continue as normal. 4901 4902 // invoke prepareFlushCache. Send null as wal since we do not want the flush events in wal 4903 PrepareFlushResult prepareResult = internalPrepareFlushCache(null, flushSeqId, 4904 storesToFlush, status, false, FlushLifeCycleTracker.DUMMY); 4905 if (prepareResult.result == null) { 4906 // save the PrepareFlushResult so that we can use it later from commit flush 4907 this.writestate.flushing = true; 4908 this.prepareFlushResult = prepareResult; 4909 status.markComplete("Flush prepare successful"); 4910 if (LOG.isDebugEnabled()) { 4911 LOG.debug(getRegionInfo().getEncodedName() + " : " 4912 + " Prepared flush with seqId:" + flush.getFlushSequenceNumber()); 4913 } 4914 } else { 4915 // special case empty memstore. We will still save the flush result in this case, since 4916 // our memstore ie empty, but the primary is still flushing 4917 if (prepareResult.getResult().getResult() == 4918 FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY) { 4919 this.writestate.flushing = true; 4920 this.prepareFlushResult = prepareResult; 4921 if (LOG.isDebugEnabled()) { 4922 LOG.debug(getRegionInfo().getEncodedName() + " : " 4923 + " Prepared empty flush with seqId:" + flush.getFlushSequenceNumber()); 4924 } 4925 } 4926 status.abort("Flush prepare failed with " + prepareResult.result); 4927 // nothing much to do. prepare flush failed because of some reason. 4928 } 4929 return prepareResult; 4930 } else { 4931 // we already have an active snapshot. 4932 if (flush.getFlushSequenceNumber() == this.prepareFlushResult.flushOpSeqId) { 4933 // They define the same flush. Log and continue. 4934 LOG.warn(getRegionInfo().getEncodedName() + " : " 4935 + "Received a flush prepare marker with the same seqId: " + 4936 + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " 4937 + prepareFlushResult.flushOpSeqId + ". Ignoring"); 4938 // ignore 4939 } else if (flush.getFlushSequenceNumber() < this.prepareFlushResult.flushOpSeqId) { 4940 // We received a flush with a smaller seqNum than what we have prepared. We can only 4941 // ignore this prepare flush request. 4942 LOG.warn(getRegionInfo().getEncodedName() + " : " 4943 + "Received a flush prepare marker with a smaller seqId: " + 4944 + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " 4945 + prepareFlushResult.flushOpSeqId + ". Ignoring"); 4946 // ignore 4947 } else { 4948 // We received a flush with a larger seqNum than what we have prepared 4949 LOG.warn(getRegionInfo().getEncodedName() + " : " 4950 + "Received a flush prepare marker with a larger seqId: " + 4951 + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " 4952 + prepareFlushResult.flushOpSeqId + ". Ignoring"); 4953 // We do not have multiple active snapshots in the memstore or a way to merge current 4954 // memstore snapshot with the contents and resnapshot for now. We cannot take 4955 // another snapshot and drop the previous one because that will cause temporary 4956 // data loss in the secondary. So we ignore this for now, deferring the resolution 4957 // to happen when we see the corresponding flush commit marker. If we have a memstore 4958 // snapshot with x, and later received another prepare snapshot with y (where x < y), 4959 // when we see flush commit for y, we will drop snapshot for x, and can also drop all 4960 // the memstore edits if everything in memstore is < y. This is the usual case for 4961 // RS crash + recovery where we might see consequtive prepare flush wal markers. 4962 // Otherwise, this will cause more memory to be used in secondary replica until a 4963 // further prapare + commit flush is seen and replayed. 4964 } 4965 } 4966 } finally { 4967 status.cleanup(); 4968 writestate.notifyAll(); 4969 } 4970 } 4971 return null; 4972 } 4973 4974 @VisibleForTesting 4975 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY", 4976 justification="Intentional; post memstore flush") 4977 void replayWALFlushCommitMarker(FlushDescriptor flush) throws IOException { 4978 MonitoredTask status = TaskMonitor.get().createStatus("Committing flush " + this); 4979 4980 // check whether we have the memstore snapshot with the corresponding seqId. Replay to 4981 // secondary region replicas are in order, except for when the region moves or then the 4982 // region server crashes. In those cases, we may receive replay requests out of order from 4983 // the original seqIds. 4984 synchronized (writestate) { 4985 try { 4986 if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) { 4987 LOG.warn(getRegionInfo().getEncodedName() + " : " 4988 + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush) 4989 + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " 4990 + " of " + lastReplayedOpenRegionSeqId); 4991 return; 4992 } 4993 4994 if (writestate.flushing) { 4995 PrepareFlushResult prepareFlushResult = this.prepareFlushResult; 4996 if (flush.getFlushSequenceNumber() == prepareFlushResult.flushOpSeqId) { 4997 if (LOG.isDebugEnabled()) { 4998 LOG.debug(getRegionInfo().getEncodedName() + " : " 4999 + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber() 5000 + " and a previous prepared snapshot was found"); 5001 } 5002 // This is the regular case where we received commit flush after prepare flush 5003 // corresponding to the same seqId. 5004 replayFlushInStores(flush, prepareFlushResult, true); 5005 5006 // Set down the memstore size by amount of flush. 5007 this.decrMemStoreSize(prepareFlushResult.totalFlushableSize.getMemStoreSize()); 5008 this.prepareFlushResult = null; 5009 writestate.flushing = false; 5010 } else if (flush.getFlushSequenceNumber() < prepareFlushResult.flushOpSeqId) { 5011 // This should not happen normally. However, lets be safe and guard against these cases 5012 // we received a flush commit with a smaller seqId than what we have prepared 5013 // we will pick the flush file up from this commit (if we have not seen it), but we 5014 // will not drop the memstore 5015 LOG.warn(getRegionInfo().getEncodedName() + " : " 5016 + "Received a flush commit marker with smaller seqId: " 5017 + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: " 5018 + prepareFlushResult.flushOpSeqId + ". Picking up new file, but not dropping" 5019 +" prepared memstore snapshot"); 5020 replayFlushInStores(flush, prepareFlushResult, false); 5021 5022 // snapshot is not dropped, so memstore sizes should not be decremented 5023 // we still have the prepared snapshot, flushing should still be true 5024 } else { 5025 // This should not happen normally. However, lets be safe and guard against these cases 5026 // we received a flush commit with a larger seqId than what we have prepared 5027 // we will pick the flush file for this. We will also obtain the updates lock and 5028 // look for contents of the memstore to see whether we have edits after this seqId. 5029 // If not, we will drop all the memstore edits and the snapshot as well. 5030 LOG.warn(getRegionInfo().getEncodedName() + " : " 5031 + "Received a flush commit marker with larger seqId: " 5032 + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: " + 5033 prepareFlushResult.flushOpSeqId + ". Picking up new file and dropping prepared" 5034 +" memstore snapshot"); 5035 5036 replayFlushInStores(flush, prepareFlushResult, true); 5037 5038 // Set down the memstore size by amount of flush. 5039 this.decrMemStoreSize(prepareFlushResult.totalFlushableSize.getMemStoreSize()); 5040 5041 // Inspect the memstore contents to see whether the memstore contains only edits 5042 // with seqId smaller than the flush seqId. If so, we can discard those edits. 5043 dropMemStoreContentsForSeqId(flush.getFlushSequenceNumber(), null); 5044 5045 this.prepareFlushResult = null; 5046 writestate.flushing = false; 5047 } 5048 // If we were waiting for observing a flush or region opening event for not showing 5049 // partial data after a secondary region crash, we can allow reads now. We can only make 5050 // sure that we are not showing partial data (for example skipping some previous edits) 5051 // until we observe a full flush start and flush commit. So if we were not able to find 5052 // a previous flush we will not enable reads now. 5053 this.setReadsEnabled(true); 5054 } else { 5055 LOG.warn(getRegionInfo().getEncodedName() + " : " 5056 + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber() 5057 + ", but no previous prepared snapshot was found"); 5058 // There is no corresponding prepare snapshot from before. 5059 // We will pick up the new flushed file 5060 replayFlushInStores(flush, null, false); 5061 5062 // Inspect the memstore contents to see whether the memstore contains only edits 5063 // with seqId smaller than the flush seqId. If so, we can discard those edits. 5064 dropMemStoreContentsForSeqId(flush.getFlushSequenceNumber(), null); 5065 } 5066 5067 status.markComplete("Flush commit successful"); 5068 5069 // Update the last flushed sequence id for region. 5070 this.maxFlushedSeqId = flush.getFlushSequenceNumber(); 5071 5072 // advance the mvcc read point so that the new flushed file is visible. 5073 mvcc.advanceTo(flush.getFlushSequenceNumber()); 5074 5075 } catch (FileNotFoundException ex) { 5076 LOG.warn(getRegionInfo().getEncodedName() + " : " 5077 + "At least one of the store files in flush: " + TextFormat.shortDebugString(flush) 5078 + " doesn't exist any more. Skip loading the file(s)", ex); 5079 } 5080 finally { 5081 status.cleanup(); 5082 writestate.notifyAll(); 5083 } 5084 } 5085 5086 // C. Finally notify anyone waiting on memstore to clear: 5087 // e.g. checkResources(). 5088 synchronized (this) { 5089 notifyAll(); // FindBugs NN_NAKED_NOTIFY 5090 } 5091 } 5092 5093 /** 5094 * Replays the given flush descriptor by opening the flush files in stores and dropping the 5095 * memstore snapshots if requested. 5096 * @param flush 5097 * @param prepareFlushResult 5098 * @param dropMemstoreSnapshot 5099 * @throws IOException 5100 */ 5101 private void replayFlushInStores(FlushDescriptor flush, PrepareFlushResult prepareFlushResult, 5102 boolean dropMemstoreSnapshot) 5103 throws IOException { 5104 for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) { 5105 byte[] family = storeFlush.getFamilyName().toByteArray(); 5106 HStore store = getStore(family); 5107 if (store == null) { 5108 LOG.warn(getRegionInfo().getEncodedName() + " : " 5109 + "Received a flush commit marker from primary, but the family is not found." 5110 + "Ignoring StoreFlushDescriptor:" + storeFlush); 5111 continue; 5112 } 5113 List<String> flushFiles = storeFlush.getFlushOutputList(); 5114 StoreFlushContext ctx = null; 5115 long startTime = EnvironmentEdgeManager.currentTime(); 5116 if (prepareFlushResult == null || prepareFlushResult.storeFlushCtxs == null) { 5117 ctx = store.createFlushContext(flush.getFlushSequenceNumber(), FlushLifeCycleTracker.DUMMY); 5118 } else { 5119 ctx = prepareFlushResult.storeFlushCtxs.get(family); 5120 startTime = prepareFlushResult.startTime; 5121 } 5122 5123 if (ctx == null) { 5124 LOG.warn(getRegionInfo().getEncodedName() + " : " 5125 + "Unexpected: flush commit marker received from store " 5126 + Bytes.toString(family) + " but no associated flush context. Ignoring"); 5127 continue; 5128 } 5129 5130 ctx.replayFlush(flushFiles, dropMemstoreSnapshot); // replay the flush 5131 5132 // Record latest flush time 5133 this.lastStoreFlushTimeMap.put(store, startTime); 5134 } 5135 } 5136 5137 /** 5138 * Be careful, this method will drop all data in the memstore of this region. 5139 * Currently, this method is used to drop memstore to prevent memory leak 5140 * when replaying recovered.edits while opening region. 5141 */ 5142 public MemStoreSize dropMemStoreContents() throws IOException { 5143 MemStoreSizing totalFreedSize = new NonThreadSafeMemStoreSizing(); 5144 this.updatesLock.writeLock().lock(); 5145 try { 5146 for (HStore s : stores.values()) { 5147 MemStoreSize memStoreSize = doDropStoreMemStoreContentsForSeqId(s, HConstants.NO_SEQNUM); 5148 LOG.info("Drop memstore for Store " + s.getColumnFamilyName() + " in region " 5149 + this.getRegionInfo().getRegionNameAsString() 5150 + " , dropped memstoresize: [" + memStoreSize + " }"); 5151 totalFreedSize.incMemStoreSize(memStoreSize); 5152 } 5153 return totalFreedSize.getMemStoreSize(); 5154 } finally { 5155 this.updatesLock.writeLock().unlock(); 5156 } 5157 } 5158 5159 /** 5160 * Drops the memstore contents after replaying a flush descriptor or region open event replay 5161 * if the memstore edits have seqNums smaller than the given seq id 5162 * @throws IOException 5163 */ 5164 private MemStoreSize dropMemStoreContentsForSeqId(long seqId, HStore store) throws IOException { 5165 MemStoreSizing totalFreedSize = new NonThreadSafeMemStoreSizing(); 5166 this.updatesLock.writeLock().lock(); 5167 try { 5168 5169 long currentSeqId = mvcc.getReadPoint(); 5170 if (seqId >= currentSeqId) { 5171 // then we can drop the memstore contents since everything is below this seqId 5172 LOG.info(getRegionInfo().getEncodedName() + " : " 5173 + "Dropping memstore contents as well since replayed flush seqId: " 5174 + seqId + " is greater than current seqId:" + currentSeqId); 5175 5176 // Prepare flush (take a snapshot) and then abort (drop the snapshot) 5177 if (store == null) { 5178 for (HStore s : stores.values()) { 5179 totalFreedSize.incMemStoreSize(doDropStoreMemStoreContentsForSeqId(s, currentSeqId)); 5180 } 5181 } else { 5182 totalFreedSize.incMemStoreSize(doDropStoreMemStoreContentsForSeqId(store, currentSeqId)); 5183 } 5184 } else { 5185 LOG.info(getRegionInfo().getEncodedName() + " : " 5186 + "Not dropping memstore contents since replayed flush seqId: " 5187 + seqId + " is smaller than current seqId:" + currentSeqId); 5188 } 5189 } finally { 5190 this.updatesLock.writeLock().unlock(); 5191 } 5192 return totalFreedSize.getMemStoreSize(); 5193 } 5194 5195 private MemStoreSize doDropStoreMemStoreContentsForSeqId(HStore s, long currentSeqId) 5196 throws IOException { 5197 MemStoreSize flushableSize = s.getFlushableSize(); 5198 this.decrMemStoreSize(flushableSize); 5199 StoreFlushContext ctx = s.createFlushContext(currentSeqId, FlushLifeCycleTracker.DUMMY); 5200 ctx.prepare(); 5201 ctx.abort(); 5202 return flushableSize; 5203 } 5204 5205 private void replayWALFlushAbortMarker(FlushDescriptor flush) { 5206 // nothing to do for now. A flush abort will cause a RS abort which means that the region 5207 // will be opened somewhere else later. We will see the region open event soon, and replaying 5208 // that will drop the snapshot 5209 } 5210 5211 private void replayWALFlushCannotFlushMarker(FlushDescriptor flush, long replaySeqId) { 5212 synchronized (writestate) { 5213 if (this.lastReplayedOpenRegionSeqId > replaySeqId) { 5214 LOG.warn(getRegionInfo().getEncodedName() + " : " 5215 + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush) 5216 + " because its sequence id " + replaySeqId + " is smaller than this regions " 5217 + "lastReplayedOpenRegionSeqId of " + lastReplayedOpenRegionSeqId); 5218 return; 5219 } 5220 5221 // If we were waiting for observing a flush or region opening event for not showing partial 5222 // data after a secondary region crash, we can allow reads now. This event means that the 5223 // primary was not able to flush because memstore is empty when we requested flush. By the 5224 // time we observe this, we are guaranteed to have up to date seqId with our previous 5225 // assignment. 5226 this.setReadsEnabled(true); 5227 } 5228 } 5229 5230 @VisibleForTesting 5231 PrepareFlushResult getPrepareFlushResult() { 5232 return prepareFlushResult; 5233 } 5234 5235 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY", 5236 justification="Intentional; cleared the memstore") 5237 void replayWALRegionEventMarker(RegionEventDescriptor regionEvent) throws IOException { 5238 checkTargetRegion(regionEvent.getEncodedRegionName().toByteArray(), 5239 "RegionEvent marker from WAL ", regionEvent); 5240 5241 startRegionOperation(Operation.REPLAY_EVENT); 5242 try { 5243 if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { 5244 return; // if primary nothing to do 5245 } 5246 5247 if (regionEvent.getEventType() == EventType.REGION_CLOSE) { 5248 // nothing to do on REGION_CLOSE for now. 5249 return; 5250 } 5251 if (regionEvent.getEventType() != EventType.REGION_OPEN) { 5252 LOG.warn(getRegionInfo().getEncodedName() + " : " 5253 + "Unknown region event received, ignoring :" 5254 + TextFormat.shortDebugString(regionEvent)); 5255 return; 5256 } 5257 5258 if (LOG.isDebugEnabled()) { 5259 LOG.debug(getRegionInfo().getEncodedName() + " : " 5260 + "Replaying region open event marker " + TextFormat.shortDebugString(regionEvent)); 5261 } 5262 5263 // we will use writestate as a coarse-grain lock for all the replay events 5264 synchronized (writestate) { 5265 // Replication can deliver events out of order when primary region moves or the region 5266 // server crashes, since there is no coordination between replication of different wal files 5267 // belonging to different region servers. We have to safe guard against this case by using 5268 // region open event's seqid. Since this is the first event that the region puts (after 5269 // possibly flushing recovered.edits), after seeing this event, we can ignore every edit 5270 // smaller than this seqId 5271 if (this.lastReplayedOpenRegionSeqId <= regionEvent.getLogSequenceNumber()) { 5272 this.lastReplayedOpenRegionSeqId = regionEvent.getLogSequenceNumber(); 5273 } else { 5274 LOG.warn(getRegionInfo().getEncodedName() + " : " 5275 + "Skipping replaying region event :" + TextFormat.shortDebugString(regionEvent) 5276 + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " 5277 + " of " + lastReplayedOpenRegionSeqId); 5278 return; 5279 } 5280 5281 // region open lists all the files that the region has at the time of the opening. Just pick 5282 // all the files and drop prepared flushes and empty memstores 5283 for (StoreDescriptor storeDescriptor : regionEvent.getStoresList()) { 5284 // stores of primary may be different now 5285 byte[] family = storeDescriptor.getFamilyName().toByteArray(); 5286 HStore store = getStore(family); 5287 if (store == null) { 5288 LOG.warn(getRegionInfo().getEncodedName() + " : " 5289 + "Received a region open marker from primary, but the family is not found. " 5290 + "Ignoring. StoreDescriptor:" + storeDescriptor); 5291 continue; 5292 } 5293 5294 long storeSeqId = store.getMaxSequenceId().orElse(0L); 5295 List<String> storeFiles = storeDescriptor.getStoreFileList(); 5296 try { 5297 store.refreshStoreFiles(storeFiles); // replace the files with the new ones 5298 } catch (FileNotFoundException ex) { 5299 LOG.warn(getRegionInfo().getEncodedName() + " : " 5300 + "At least one of the store files: " + storeFiles 5301 + " doesn't exist any more. Skip loading the file(s)", ex); 5302 continue; 5303 } 5304 if (store.getMaxSequenceId().orElse(0L) != storeSeqId) { 5305 // Record latest flush time if we picked up new files 5306 lastStoreFlushTimeMap.put(store, EnvironmentEdgeManager.currentTime()); 5307 } 5308 5309 if (writestate.flushing) { 5310 // only drop memstore snapshots if they are smaller than last flush for the store 5311 if (this.prepareFlushResult.flushOpSeqId <= regionEvent.getLogSequenceNumber()) { 5312 StoreFlushContext ctx = this.prepareFlushResult.storeFlushCtxs == null ? 5313 null : this.prepareFlushResult.storeFlushCtxs.get(family); 5314 if (ctx != null) { 5315 MemStoreSize mss = store.getFlushableSize(); 5316 ctx.abort(); 5317 this.decrMemStoreSize(mss); 5318 this.prepareFlushResult.storeFlushCtxs.remove(family); 5319 } 5320 } 5321 } 5322 5323 // Drop the memstore contents if they are now smaller than the latest seen flushed file 5324 dropMemStoreContentsForSeqId(regionEvent.getLogSequenceNumber(), store); 5325 if (storeSeqId > this.maxFlushedSeqId) { 5326 this.maxFlushedSeqId = storeSeqId; 5327 } 5328 } 5329 5330 // if all stores ended up dropping their snapshots, we can safely drop the 5331 // prepareFlushResult 5332 dropPrepareFlushIfPossible(); 5333 5334 // advance the mvcc read point so that the new flushed file is visible. 5335 mvcc.await(); 5336 5337 // If we were waiting for observing a flush or region opening event for not showing partial 5338 // data after a secondary region crash, we can allow reads now. 5339 this.setReadsEnabled(true); 5340 5341 // C. Finally notify anyone waiting on memstore to clear: 5342 // e.g. checkResources(). 5343 synchronized (this) { 5344 notifyAll(); // FindBugs NN_NAKED_NOTIFY 5345 } 5346 } 5347 logRegionFiles(); 5348 } finally { 5349 closeRegionOperation(Operation.REPLAY_EVENT); 5350 } 5351 } 5352 5353 void replayWALBulkLoadEventMarker(WALProtos.BulkLoadDescriptor bulkLoadEvent) throws IOException { 5354 checkTargetRegion(bulkLoadEvent.getEncodedRegionName().toByteArray(), 5355 "BulkLoad marker from WAL ", bulkLoadEvent); 5356 5357 if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { 5358 return; // if primary nothing to do 5359 } 5360 5361 if (LOG.isDebugEnabled()) { 5362 LOG.debug(getRegionInfo().getEncodedName() + " : " 5363 + "Replaying bulkload event marker " + TextFormat.shortDebugString(bulkLoadEvent)); 5364 } 5365 // check if multiple families involved 5366 boolean multipleFamilies = false; 5367 byte[] family = null; 5368 for (StoreDescriptor storeDescriptor : bulkLoadEvent.getStoresList()) { 5369 byte[] fam = storeDescriptor.getFamilyName().toByteArray(); 5370 if (family == null) { 5371 family = fam; 5372 } else if (!Bytes.equals(family, fam)) { 5373 multipleFamilies = true; 5374 break; 5375 } 5376 } 5377 5378 startBulkRegionOperation(multipleFamilies); 5379 try { 5380 // we will use writestate as a coarse-grain lock for all the replay events 5381 synchronized (writestate) { 5382 // Replication can deliver events out of order when primary region moves or the region 5383 // server crashes, since there is no coordination between replication of different wal files 5384 // belonging to different region servers. We have to safe guard against this case by using 5385 // region open event's seqid. Since this is the first event that the region puts (after 5386 // possibly flushing recovered.edits), after seeing this event, we can ignore every edit 5387 // smaller than this seqId 5388 if (bulkLoadEvent.getBulkloadSeqNum() >= 0 5389 && this.lastReplayedOpenRegionSeqId >= bulkLoadEvent.getBulkloadSeqNum()) { 5390 LOG.warn(getRegionInfo().getEncodedName() + " : " 5391 + "Skipping replaying bulkload event :" 5392 + TextFormat.shortDebugString(bulkLoadEvent) 5393 + " because its sequence id is smaller than this region's lastReplayedOpenRegionSeqId" 5394 + " =" + lastReplayedOpenRegionSeqId); 5395 5396 return; 5397 } 5398 5399 for (StoreDescriptor storeDescriptor : bulkLoadEvent.getStoresList()) { 5400 // stores of primary may be different now 5401 family = storeDescriptor.getFamilyName().toByteArray(); 5402 HStore store = getStore(family); 5403 if (store == null) { 5404 LOG.warn(getRegionInfo().getEncodedName() + " : " 5405 + "Received a bulk load marker from primary, but the family is not found. " 5406 + "Ignoring. StoreDescriptor:" + storeDescriptor); 5407 continue; 5408 } 5409 5410 List<String> storeFiles = storeDescriptor.getStoreFileList(); 5411 for (String storeFile : storeFiles) { 5412 StoreFileInfo storeFileInfo = null; 5413 try { 5414 storeFileInfo = fs.getStoreFileInfo(Bytes.toString(family), storeFile); 5415 store.bulkLoadHFile(storeFileInfo); 5416 } catch(FileNotFoundException ex) { 5417 LOG.warn(getRegionInfo().getEncodedName() + " : " 5418 + ((storeFileInfo != null) ? storeFileInfo.toString() : 5419 (new Path(Bytes.toString(family), storeFile)).toString()) 5420 + " doesn't exist any more. Skip loading the file"); 5421 } 5422 } 5423 } 5424 } 5425 if (bulkLoadEvent.getBulkloadSeqNum() > 0) { 5426 mvcc.advanceTo(bulkLoadEvent.getBulkloadSeqNum()); 5427 } 5428 } finally { 5429 closeBulkRegionOperation(); 5430 } 5431 } 5432 5433 /** 5434 * If all stores ended up dropping their snapshots, we can safely drop the prepareFlushResult 5435 */ 5436 private void dropPrepareFlushIfPossible() { 5437 if (writestate.flushing) { 5438 boolean canDrop = true; 5439 if (prepareFlushResult.storeFlushCtxs != null) { 5440 for (Entry<byte[], StoreFlushContext> entry : prepareFlushResult.storeFlushCtxs 5441 .entrySet()) { 5442 HStore store = getStore(entry.getKey()); 5443 if (store == null) { 5444 continue; 5445 } 5446 if (store.getSnapshotSize().getDataSize() > 0) { 5447 canDrop = false; 5448 break; 5449 } 5450 } 5451 } 5452 5453 // this means that all the stores in the region has finished flushing, but the WAL marker 5454 // may not have been written or we did not receive it yet. 5455 if (canDrop) { 5456 writestate.flushing = false; 5457 this.prepareFlushResult = null; 5458 } 5459 } 5460 } 5461 5462 @Override 5463 public boolean refreshStoreFiles() throws IOException { 5464 return refreshStoreFiles(false); 5465 } 5466 5467 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NN_NAKED_NOTIFY", 5468 justification = "Notify is about post replay. Intentional") 5469 protected boolean refreshStoreFiles(boolean force) throws IOException { 5470 if (!force && ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { 5471 return false; // if primary nothing to do 5472 } 5473 5474 if (LOG.isDebugEnabled()) { 5475 LOG.debug(getRegionInfo().getEncodedName() + " : " 5476 + "Refreshing store files to see whether we can free up memstore"); 5477 } 5478 5479 long totalFreedDataSize = 0; 5480 5481 long smallestSeqIdInStores = Long.MAX_VALUE; 5482 5483 startRegionOperation(); // obtain region close lock 5484 try { 5485 Map<HStore, Long> map = new HashMap<>(); 5486 synchronized (writestate) { 5487 for (HStore store : stores.values()) { 5488 // TODO: some stores might see new data from flush, while others do not which 5489 // MIGHT break atomic edits across column families. 5490 long maxSeqIdBefore = store.getMaxSequenceId().orElse(0L); 5491 5492 // refresh the store files. This is similar to observing a region open wal marker. 5493 store.refreshStoreFiles(); 5494 5495 long storeSeqId = store.getMaxSequenceId().orElse(0L); 5496 if (storeSeqId < smallestSeqIdInStores) { 5497 smallestSeqIdInStores = storeSeqId; 5498 } 5499 5500 // see whether we can drop the memstore or the snapshot 5501 if (storeSeqId > maxSeqIdBefore) { 5502 if (writestate.flushing) { 5503 // only drop memstore snapshots if they are smaller than last flush for the store 5504 if (this.prepareFlushResult.flushOpSeqId <= storeSeqId) { 5505 StoreFlushContext ctx = this.prepareFlushResult.storeFlushCtxs == null ? 5506 null : this.prepareFlushResult.storeFlushCtxs.get( 5507 store.getColumnFamilyDescriptor().getName()); 5508 if (ctx != null) { 5509 MemStoreSize mss = store.getFlushableSize(); 5510 ctx.abort(); 5511 this.decrMemStoreSize(mss); 5512 this.prepareFlushResult.storeFlushCtxs. 5513 remove(store.getColumnFamilyDescriptor().getName()); 5514 totalFreedDataSize += mss.getDataSize(); 5515 } 5516 } 5517 } 5518 5519 map.put(store, storeSeqId); 5520 } 5521 } 5522 5523 // if all stores ended up dropping their snapshots, we can safely drop the 5524 // prepareFlushResult 5525 dropPrepareFlushIfPossible(); 5526 5527 // advance the mvcc read point so that the new flushed files are visible. 5528 // either greater than flush seq number or they were already picked up via flush. 5529 for (HStore s : stores.values()) { 5530 mvcc.advanceTo(s.getMaxMemStoreTS().orElse(0L)); 5531 } 5532 5533 5534 // smallestSeqIdInStores is the seqId that we have a corresponding hfile for. We can safely 5535 // skip all edits that are to be replayed in the future with that has a smaller seqId 5536 // than this. We are updating lastReplayedOpenRegionSeqId so that we can skip all edits 5537 // that we have picked the flush files for 5538 if (this.lastReplayedOpenRegionSeqId < smallestSeqIdInStores) { 5539 this.lastReplayedOpenRegionSeqId = smallestSeqIdInStores; 5540 } 5541 } 5542 if (!map.isEmpty()) { 5543 for (Map.Entry<HStore, Long> entry : map.entrySet()) { 5544 // Drop the memstore contents if they are now smaller than the latest seen flushed file 5545 totalFreedDataSize += dropMemStoreContentsForSeqId(entry.getValue(), entry.getKey()) 5546 .getDataSize(); 5547 } 5548 } 5549 // C. Finally notify anyone waiting on memstore to clear: 5550 // e.g. checkResources(). 5551 synchronized (this) { 5552 notifyAll(); // FindBugs NN_NAKED_NOTIFY 5553 } 5554 return totalFreedDataSize > 0; 5555 } finally { 5556 closeRegionOperation(); 5557 } 5558 } 5559 5560 private void logRegionFiles() { 5561 if (LOG.isTraceEnabled()) { 5562 LOG.trace(getRegionInfo().getEncodedName() + " : Store files for region: "); 5563 stores.values().stream().filter(s -> s.getStorefiles() != null) 5564 .flatMap(s -> s.getStorefiles().stream()) 5565 .forEachOrdered(sf -> LOG.trace(getRegionInfo().getEncodedName() + " : " + sf)); 5566 } 5567 } 5568 5569 /** Checks whether the given regionName is either equal to our region, or that 5570 * the regionName is the primary region to our corresponding range for the secondary replica. 5571 */ 5572 private void checkTargetRegion(byte[] encodedRegionName, String exceptionMsg, Object payload) 5573 throws WrongRegionException { 5574 if (Bytes.equals(this.getRegionInfo().getEncodedNameAsBytes(), encodedRegionName)) { 5575 return; 5576 } 5577 5578 if (!RegionReplicaUtil.isDefaultReplica(this.getRegionInfo()) && 5579 Bytes.equals(encodedRegionName, 5580 this.fs.getRegionInfoForFS().getEncodedNameAsBytes())) { 5581 return; 5582 } 5583 5584 throw new WrongRegionException(exceptionMsg + payload 5585 + " targetted for region " + Bytes.toStringBinary(encodedRegionName) 5586 + " does not match this region: " + this.getRegionInfo()); 5587 } 5588 5589 /** 5590 * Used by tests 5591 * @param s Store to add edit too. 5592 * @param cell Cell to add. 5593 */ 5594 @VisibleForTesting 5595 protected void restoreEdit(HStore s, Cell cell, MemStoreSizing memstoreAccounting) { 5596 s.add(cell, memstoreAccounting); 5597 } 5598 5599 /** 5600 * @param p File to check. 5601 * @return True if file was zero-length (and if so, we'll delete it in here). 5602 * @throws IOException 5603 */ 5604 private static boolean isZeroLengthThenDelete(final FileSystem fs, final Path p) 5605 throws IOException { 5606 FileStatus stat = fs.getFileStatus(p); 5607 if (stat.getLen() > 0) { 5608 return false; 5609 } 5610 LOG.warn("File " + p + " is zero-length, deleting."); 5611 fs.delete(p, false); 5612 return true; 5613 } 5614 5615 protected HStore instantiateHStore(final ColumnFamilyDescriptor family) throws IOException { 5616 if (family.isMobEnabled()) { 5617 if (HFile.getFormatVersion(this.conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) { 5618 throw new IOException("A minimum HFile version of " 5619 + HFile.MIN_FORMAT_VERSION_WITH_TAGS 5620 + " is required for MOB feature. Consider setting " + HFile.FORMAT_VERSION_KEY 5621 + " accordingly."); 5622 } 5623 return new HMobStore(this, family, this.conf); 5624 } 5625 return new HStore(this, family, this.conf); 5626 } 5627 5628 @Override 5629 public HStore getStore(byte[] column) { 5630 return this.stores.get(column); 5631 } 5632 5633 /** 5634 * Return HStore instance. Does not do any copy: as the number of store is limited, we iterate on 5635 * the list. 5636 */ 5637 private HStore getStore(Cell cell) { 5638 return stores.entrySet().stream().filter(e -> CellUtil.matchingFamily(cell, e.getKey())) 5639 .map(e -> e.getValue()).findFirst().orElse(null); 5640 } 5641 5642 @Override 5643 public List<HStore> getStores() { 5644 return new ArrayList<>(stores.values()); 5645 } 5646 5647 @Override 5648 public List<String> getStoreFileList(byte[][] columns) throws IllegalArgumentException { 5649 List<String> storeFileNames = new ArrayList<>(); 5650 synchronized (closeLock) { 5651 for (byte[] column : columns) { 5652 HStore store = this.stores.get(column); 5653 if (store == null) { 5654 throw new IllegalArgumentException( 5655 "No column family : " + new String(column, StandardCharsets.UTF_8) + " available"); 5656 } 5657 Collection<HStoreFile> storeFiles = store.getStorefiles(); 5658 if (storeFiles == null) { 5659 continue; 5660 } 5661 for (HStoreFile storeFile : storeFiles) { 5662 storeFileNames.add(storeFile.getPath().toString()); 5663 } 5664 5665 logRegionFiles(); 5666 } 5667 } 5668 return storeFileNames; 5669 } 5670 5671 ////////////////////////////////////////////////////////////////////////////// 5672 // Support code 5673 ////////////////////////////////////////////////////////////////////////////// 5674 5675 /** Make sure this is a valid row for the HRegion */ 5676 void checkRow(byte[] row, String op) throws IOException { 5677 if (!rowIsInRange(getRegionInfo(), row)) { 5678 throw new WrongRegionException("Requested row out of range for " + 5679 op + " on HRegion " + this + ", startKey='" + 5680 Bytes.toStringBinary(getRegionInfo().getStartKey()) + "', getEndKey()='" + 5681 Bytes.toStringBinary(getRegionInfo().getEndKey()) + "', row='" + 5682 Bytes.toStringBinary(row) + "'"); 5683 } 5684 } 5685 5686 5687 /** 5688 * Get an exclusive ( write lock ) lock on a given row. 5689 * @param row Which row to lock. 5690 * @return A locked RowLock. The lock is exclusive and already aqquired. 5691 * @throws IOException 5692 */ 5693 public RowLock getRowLock(byte[] row) throws IOException { 5694 return getRowLock(row, false); 5695 } 5696 5697 @Override 5698 public RowLock getRowLock(byte[] row, boolean readLock) throws IOException { 5699 checkRow(row, "row lock"); 5700 return getRowLockInternal(row, readLock, null); 5701 } 5702 5703 protected RowLock getRowLockInternal(byte[] row, boolean readLock, final RowLock prevRowLock) 5704 throws IOException { 5705 // create an object to use a a key in the row lock map 5706 HashedBytes rowKey = new HashedBytes(row); 5707 5708 RowLockContext rowLockContext = null; 5709 RowLockImpl result = null; 5710 5711 boolean success = false; 5712 try (TraceScope scope = TraceUtil.createTrace("HRegion.getRowLock")) { 5713 TraceUtil.addTimelineAnnotation("Getting a " + (readLock?"readLock":"writeLock")); 5714 // Keep trying until we have a lock or error out. 5715 // TODO: do we need to add a time component here? 5716 while (result == null) { 5717 rowLockContext = computeIfAbsent(lockedRows, rowKey, () -> new RowLockContext(rowKey)); 5718 // Now try an get the lock. 5719 // This can fail as 5720 if (readLock) { 5721 // For read lock, if the caller has locked the same row previously, it will not try 5722 // to acquire the same read lock. It simply returns the previous row lock. 5723 RowLockImpl prevRowLockImpl = (RowLockImpl)prevRowLock; 5724 if ((prevRowLockImpl != null) && (prevRowLockImpl.getLock() == 5725 rowLockContext.readWriteLock.readLock())) { 5726 success = true; 5727 return prevRowLock; 5728 } 5729 result = rowLockContext.newReadLock(); 5730 } else { 5731 result = rowLockContext.newWriteLock(); 5732 } 5733 } 5734 5735 int timeout = rowLockWaitDuration; 5736 boolean reachDeadlineFirst = false; 5737 Optional<RpcCall> call = RpcServer.getCurrentCall(); 5738 if (call.isPresent()) { 5739 long deadline = call.get().getDeadline(); 5740 if (deadline < Long.MAX_VALUE) { 5741 int timeToDeadline = (int) (deadline - System.currentTimeMillis()); 5742 if (timeToDeadline <= this.rowLockWaitDuration) { 5743 reachDeadlineFirst = true; 5744 timeout = timeToDeadline; 5745 } 5746 } 5747 } 5748 5749 if (timeout <= 0 || !result.getLock().tryLock(timeout, TimeUnit.MILLISECONDS)) { 5750 TraceUtil.addTimelineAnnotation("Failed to get row lock"); 5751 String message = "Timed out waiting for lock for row: " + rowKey + " in region " 5752 + getRegionInfo().getEncodedName(); 5753 if (reachDeadlineFirst) { 5754 throw new TimeoutIOException(message); 5755 } else { 5756 // If timeToDeadline is larger than rowLockWaitDuration, we can not drop the request. 5757 throw new IOException(message); 5758 } 5759 } 5760 rowLockContext.setThreadName(Thread.currentThread().getName()); 5761 success = true; 5762 return result; 5763 } catch (InterruptedException ie) { 5764 LOG.warn("Thread interrupted waiting for lock on row: " + rowKey); 5765 InterruptedIOException iie = new InterruptedIOException(); 5766 iie.initCause(ie); 5767 TraceUtil.addTimelineAnnotation("Interrupted exception getting row lock"); 5768 Thread.currentThread().interrupt(); 5769 throw iie; 5770 } catch (Error error) { 5771 // The maximum lock count for read lock is 64K (hardcoded), when this maximum count 5772 // is reached, it will throw out an Error. This Error needs to be caught so it can 5773 // go ahead to process the minibatch with lock acquired. 5774 LOG.warn("Error to get row lock for " + Bytes.toStringBinary(row) + ", cause: " + error); 5775 IOException ioe = new IOException(); 5776 ioe.initCause(error); 5777 TraceUtil.addTimelineAnnotation("Error getting row lock"); 5778 throw ioe; 5779 } finally { 5780 // Clean up the counts just in case this was the thing keeping the context alive. 5781 if (!success && rowLockContext != null) { 5782 rowLockContext.cleanUp(); 5783 } 5784 } 5785 } 5786 5787 private void releaseRowLocks(List<RowLock> rowLocks) { 5788 if (rowLocks != null) { 5789 for (RowLock rowLock : rowLocks) { 5790 rowLock.release(); 5791 } 5792 rowLocks.clear(); 5793 } 5794 } 5795 5796 @VisibleForTesting 5797 public int getReadLockCount() { 5798 return lock.getReadLockCount(); 5799 } 5800 5801 public ConcurrentHashMap<HashedBytes, RowLockContext> getLockedRows() { 5802 return lockedRows; 5803 } 5804 5805 @VisibleForTesting 5806 class RowLockContext { 5807 private final HashedBytes row; 5808 final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); 5809 final AtomicBoolean usable = new AtomicBoolean(true); 5810 final AtomicInteger count = new AtomicInteger(0); 5811 final Object lock = new Object(); 5812 private String threadName; 5813 5814 RowLockContext(HashedBytes row) { 5815 this.row = row; 5816 } 5817 5818 RowLockImpl newWriteLock() { 5819 Lock l = readWriteLock.writeLock(); 5820 return getRowLock(l); 5821 } 5822 RowLockImpl newReadLock() { 5823 Lock l = readWriteLock.readLock(); 5824 return getRowLock(l); 5825 } 5826 5827 private RowLockImpl getRowLock(Lock l) { 5828 count.incrementAndGet(); 5829 synchronized (lock) { 5830 if (usable.get()) { 5831 return new RowLockImpl(this, l); 5832 } else { 5833 return null; 5834 } 5835 } 5836 } 5837 5838 void cleanUp() { 5839 long c = count.decrementAndGet(); 5840 if (c <= 0) { 5841 synchronized (lock) { 5842 if (count.get() <= 0 && usable.get()){ // Don't attempt to remove row if already removed 5843 usable.set(false); 5844 RowLockContext removed = lockedRows.remove(row); 5845 assert removed == this: "we should never remove a different context"; 5846 } 5847 } 5848 } 5849 } 5850 5851 public void setThreadName(String threadName) { 5852 this.threadName = threadName; 5853 } 5854 5855 @Override 5856 public String toString() { 5857 return "RowLockContext{" + 5858 "row=" + row + 5859 ", readWriteLock=" + readWriteLock + 5860 ", count=" + count + 5861 ", threadName=" + threadName + 5862 '}'; 5863 } 5864 } 5865 5866 /** 5867 * Class used to represent a lock on a row. 5868 */ 5869 public static class RowLockImpl implements RowLock { 5870 private final RowLockContext context; 5871 private final Lock lock; 5872 5873 public RowLockImpl(RowLockContext context, Lock lock) { 5874 this.context = context; 5875 this.lock = lock; 5876 } 5877 5878 public Lock getLock() { 5879 return lock; 5880 } 5881 5882 @VisibleForTesting 5883 public RowLockContext getContext() { 5884 return context; 5885 } 5886 5887 @Override 5888 public void release() { 5889 lock.unlock(); 5890 context.cleanUp(); 5891 } 5892 5893 @Override 5894 public String toString() { 5895 return "RowLockImpl{" + 5896 "context=" + context + 5897 ", lock=" + lock + 5898 '}'; 5899 } 5900 } 5901 5902 /** 5903 * Determines whether multiple column families are present 5904 * Precondition: familyPaths is not null 5905 * 5906 * @param familyPaths List of (column family, hfilePath) 5907 */ 5908 private static boolean hasMultipleColumnFamilies(Collection<Pair<byte[], String>> familyPaths) { 5909 boolean multipleFamilies = false; 5910 byte[] family = null; 5911 for (Pair<byte[], String> pair : familyPaths) { 5912 byte[] fam = pair.getFirst(); 5913 if (family == null) { 5914 family = fam; 5915 } else if (!Bytes.equals(family, fam)) { 5916 multipleFamilies = true; 5917 break; 5918 } 5919 } 5920 return multipleFamilies; 5921 } 5922 5923 /** 5924 * Attempts to atomically load a group of hfiles. This is critical for loading 5925 * rows with multiple column families atomically. 5926 * 5927 * @param familyPaths List of Pair<byte[] column family, String hfilePath> 5928 * @param bulkLoadListener Internal hooks enabling massaging/preparation of a 5929 * file about to be bulk loaded 5930 * @param assignSeqId 5931 * @return Map from family to List of store file paths if successful, null if failed recoverably 5932 * @throws IOException if failed unrecoverably. 5933 */ 5934 public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> familyPaths, boolean assignSeqId, 5935 BulkLoadListener bulkLoadListener) throws IOException { 5936 return bulkLoadHFiles(familyPaths, assignSeqId, bulkLoadListener, false); 5937 } 5938 5939 /** 5940 * Listener class to enable callers of 5941 * bulkLoadHFile() to perform any necessary 5942 * pre/post processing of a given bulkload call 5943 */ 5944 public interface BulkLoadListener { 5945 /** 5946 * Called before an HFile is actually loaded 5947 * @param family family being loaded to 5948 * @param srcPath path of HFile 5949 * @return final path to be used for actual loading 5950 * @throws IOException 5951 */ 5952 String prepareBulkLoad(byte[] family, String srcPath, boolean copyFile) 5953 throws IOException; 5954 5955 /** 5956 * Called after a successful HFile load 5957 * @param family family being loaded to 5958 * @param srcPath path of HFile 5959 * @throws IOException 5960 */ 5961 void doneBulkLoad(byte[] family, String srcPath) throws IOException; 5962 5963 /** 5964 * Called after a failed HFile load 5965 * @param family family being loaded to 5966 * @param srcPath path of HFile 5967 * @throws IOException 5968 */ 5969 void failedBulkLoad(byte[] family, String srcPath) throws IOException; 5970 } 5971 5972 /** 5973 * Attempts to atomically load a group of hfiles. This is critical for loading 5974 * rows with multiple column families atomically. 5975 * 5976 * @param familyPaths List of Pair<byte[] column family, String hfilePath> 5977 * @param assignSeqId 5978 * @param bulkLoadListener Internal hooks enabling massaging/preparation of a 5979 * file about to be bulk loaded 5980 * @param copyFile always copy hfiles if true 5981 * @return Map from family to List of store file paths if successful, null if failed recoverably 5982 * @throws IOException if failed unrecoverably. 5983 */ 5984 public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> familyPaths, 5985 boolean assignSeqId, BulkLoadListener bulkLoadListener, boolean copyFile) throws IOException { 5986 long seqId = -1; 5987 Map<byte[], List<Path>> storeFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR); 5988 Map<String, Long> storeFilesSizes = new HashMap<>(); 5989 Preconditions.checkNotNull(familyPaths); 5990 // we need writeLock for multi-family bulk load 5991 startBulkRegionOperation(hasMultipleColumnFamilies(familyPaths)); 5992 boolean isSuccessful = false; 5993 try { 5994 this.writeRequestsCount.increment(); 5995 5996 // There possibly was a split that happened between when the split keys 5997 // were gathered and before the HRegion's write lock was taken. We need 5998 // to validate the HFile region before attempting to bulk load all of them 5999 List<IOException> ioes = new ArrayList<>(); 6000 List<Pair<byte[], String>> failures = new ArrayList<>(); 6001 for (Pair<byte[], String> p : familyPaths) { 6002 byte[] familyName = p.getFirst(); 6003 String path = p.getSecond(); 6004 6005 HStore store = getStore(familyName); 6006 if (store == null) { 6007 IOException ioe = new org.apache.hadoop.hbase.DoNotRetryIOException( 6008 "No such column family " + Bytes.toStringBinary(familyName)); 6009 ioes.add(ioe); 6010 } else { 6011 try { 6012 store.assertBulkLoadHFileOk(new Path(path)); 6013 } catch (WrongRegionException wre) { 6014 // recoverable (file doesn't fit in region) 6015 failures.add(p); 6016 } catch (IOException ioe) { 6017 // unrecoverable (hdfs problem) 6018 ioes.add(ioe); 6019 } 6020 } 6021 } 6022 6023 // validation failed because of some sort of IO problem. 6024 if (ioes.size() != 0) { 6025 IOException e = MultipleIOException.createIOException(ioes); 6026 LOG.error("There were one or more IO errors when checking if the bulk load is ok.", e); 6027 throw e; 6028 } 6029 6030 // validation failed, bail out before doing anything permanent. 6031 if (failures.size() != 0) { 6032 StringBuilder list = new StringBuilder(); 6033 for (Pair<byte[], String> p : failures) { 6034 list.append("\n").append(Bytes.toString(p.getFirst())).append(" : ") 6035 .append(p.getSecond()); 6036 } 6037 // problem when validating 6038 LOG.warn("There was a recoverable bulk load failure likely due to a" + 6039 " split. These (family, HFile) pairs were not loaded: " + list); 6040 return null; 6041 } 6042 6043 // We need to assign a sequential ID that's in between two memstores in order to preserve 6044 // the guarantee that all the edits lower than the highest sequential ID from all the 6045 // HFiles are flushed on disk. See HBASE-10958. The sequence id returned when we flush is 6046 // guaranteed to be one beyond the file made when we flushed (or if nothing to flush, it is 6047 // a sequence id that we can be sure is beyond the last hfile written). 6048 if (assignSeqId) { 6049 FlushResult fs = flushcache(true, false, FlushLifeCycleTracker.DUMMY); 6050 if (fs.isFlushSucceeded()) { 6051 seqId = ((FlushResultImpl)fs).flushSequenceId; 6052 } else if (fs.getResult() == FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY) { 6053 seqId = ((FlushResultImpl)fs).flushSequenceId; 6054 } else if (fs.getResult() == FlushResult.Result.CANNOT_FLUSH) { 6055 // CANNOT_FLUSH may mean that a flush is already on-going 6056 // we need to wait for that flush to complete 6057 waitForFlushes(); 6058 } else { 6059 throw new IOException("Could not bulk load with an assigned sequential ID because the "+ 6060 "flush didn't run. Reason for not flushing: " + ((FlushResultImpl)fs).failureReason); 6061 } 6062 } 6063 6064 Map<byte[], List<Pair<Path, Path>>> familyWithFinalPath = 6065 new TreeMap<>(Bytes.BYTES_COMPARATOR); 6066 for (Pair<byte[], String> p : familyPaths) { 6067 byte[] familyName = p.getFirst(); 6068 String path = p.getSecond(); 6069 HStore store = getStore(familyName); 6070 if (!familyWithFinalPath.containsKey(familyName)) { 6071 familyWithFinalPath.put(familyName, new ArrayList<>()); 6072 } 6073 List<Pair<Path, Path>> lst = familyWithFinalPath.get(familyName); 6074 try { 6075 String finalPath = path; 6076 if (bulkLoadListener != null) { 6077 finalPath = bulkLoadListener.prepareBulkLoad(familyName, path, copyFile); 6078 } 6079 Pair<Path, Path> pair = store.preBulkLoadHFile(finalPath, seqId); 6080 lst.add(pair); 6081 } catch (IOException ioe) { 6082 // A failure here can cause an atomicity violation that we currently 6083 // cannot recover from since it is likely a failed HDFS operation. 6084 6085 LOG.error("There was a partial failure due to IO when attempting to" + 6086 " load " + Bytes.toString(p.getFirst()) + " : " + p.getSecond(), ioe); 6087 if (bulkLoadListener != null) { 6088 try { 6089 bulkLoadListener.failedBulkLoad(familyName, path); 6090 } catch (Exception ex) { 6091 LOG.error("Error while calling failedBulkLoad for family " + 6092 Bytes.toString(familyName) + " with path " + path, ex); 6093 } 6094 } 6095 throw ioe; 6096 } 6097 } 6098 6099 if (this.getCoprocessorHost() != null) { 6100 for (Map.Entry<byte[], List<Pair<Path, Path>>> entry : familyWithFinalPath.entrySet()) { 6101 this.getCoprocessorHost().preCommitStoreFile(entry.getKey(), entry.getValue()); 6102 } 6103 } 6104 for (Map.Entry<byte[], List<Pair<Path, Path>>> entry : familyWithFinalPath.entrySet()) { 6105 byte[] familyName = entry.getKey(); 6106 for (Pair<Path, Path> p : entry.getValue()) { 6107 String path = p.getFirst().toString(); 6108 Path commitedStoreFile = p.getSecond(); 6109 HStore store = getStore(familyName); 6110 try { 6111 store.bulkLoadHFile(familyName, path, commitedStoreFile); 6112 // Note the size of the store file 6113 try { 6114 FileSystem fs = commitedStoreFile.getFileSystem(baseConf); 6115 storeFilesSizes.put(commitedStoreFile.getName(), fs.getFileStatus(commitedStoreFile) 6116 .getLen()); 6117 } catch (IOException e) { 6118 LOG.warn("Failed to find the size of hfile " + commitedStoreFile, e); 6119 storeFilesSizes.put(commitedStoreFile.getName(), 0L); 6120 } 6121 6122 if(storeFiles.containsKey(familyName)) { 6123 storeFiles.get(familyName).add(commitedStoreFile); 6124 } else { 6125 List<Path> storeFileNames = new ArrayList<>(); 6126 storeFileNames.add(commitedStoreFile); 6127 storeFiles.put(familyName, storeFileNames); 6128 } 6129 if (bulkLoadListener != null) { 6130 bulkLoadListener.doneBulkLoad(familyName, path); 6131 } 6132 } catch (IOException ioe) { 6133 // A failure here can cause an atomicity violation that we currently 6134 // cannot recover from since it is likely a failed HDFS operation. 6135 6136 // TODO Need a better story for reverting partial failures due to HDFS. 6137 LOG.error("There was a partial failure due to IO when attempting to" + 6138 " load " + Bytes.toString(familyName) + " : " + p.getSecond(), ioe); 6139 if (bulkLoadListener != null) { 6140 try { 6141 bulkLoadListener.failedBulkLoad(familyName, path); 6142 } catch (Exception ex) { 6143 LOG.error("Error while calling failedBulkLoad for family " + 6144 Bytes.toString(familyName) + " with path " + path, ex); 6145 } 6146 } 6147 throw ioe; 6148 } 6149 } 6150 } 6151 6152 isSuccessful = true; 6153 } finally { 6154 if (wal != null && !storeFiles.isEmpty()) { 6155 // Write a bulk load event for hfiles that are loaded 6156 try { 6157 WALProtos.BulkLoadDescriptor loadDescriptor = 6158 ProtobufUtil.toBulkLoadDescriptor(this.getRegionInfo().getTable(), 6159 UnsafeByteOperations.unsafeWrap(this.getRegionInfo().getEncodedNameAsBytes()), 6160 storeFiles, 6161 storeFilesSizes, seqId); 6162 WALUtil.writeBulkLoadMarkerAndSync(this.wal, this.getReplicationScope(), getRegionInfo(), 6163 loadDescriptor, mvcc); 6164 } catch (IOException ioe) { 6165 if (this.rsServices != null) { 6166 // Have to abort region server because some hfiles has been loaded but we can't write 6167 // the event into WAL 6168 isSuccessful = false; 6169 this.rsServices.abort("Failed to write bulk load event into WAL.", ioe); 6170 } 6171 } 6172 } 6173 6174 closeBulkRegionOperation(); 6175 } 6176 return isSuccessful ? storeFiles : null; 6177 } 6178 6179 @Override 6180 public boolean equals(Object o) { 6181 return o instanceof HRegion && Bytes.equals(getRegionInfo().getRegionName(), 6182 ((HRegion) o).getRegionInfo().getRegionName()); 6183 } 6184 6185 @Override 6186 public int hashCode() { 6187 return Bytes.hashCode(getRegionInfo().getRegionName()); 6188 } 6189 6190 @Override 6191 public String toString() { 6192 return getRegionInfo().getRegionNameAsString(); 6193 } 6194 6195 /** 6196 * RegionScannerImpl is used to combine scanners from multiple Stores (aka column families). 6197 */ 6198 class RegionScannerImpl 6199 implements RegionScanner, Shipper, org.apache.hadoop.hbase.ipc.RpcCallback { 6200 // Package local for testability 6201 KeyValueHeap storeHeap = null; 6202 /** Heap of key-values that are not essential for the provided filters and are thus read 6203 * on demand, if on-demand column family loading is enabled.*/ 6204 KeyValueHeap joinedHeap = null; 6205 /** 6206 * If the joined heap data gathering is interrupted due to scan limits, this will 6207 * contain the row for which we are populating the values.*/ 6208 protected Cell joinedContinuationRow = null; 6209 private boolean filterClosed = false; 6210 6211 protected final byte[] stopRow; 6212 protected final boolean includeStopRow; 6213 protected final HRegion region; 6214 protected final CellComparator comparator; 6215 6216 private final long readPt; 6217 private final long maxResultSize; 6218 private final ScannerContext defaultScannerContext; 6219 private final FilterWrapper filter; 6220 6221 @Override 6222 public RegionInfo getRegionInfo() { 6223 return region.getRegionInfo(); 6224 } 6225 6226 RegionScannerImpl(Scan scan, List<KeyValueScanner> additionalScanners, HRegion region) 6227 throws IOException { 6228 this(scan, additionalScanners, region, HConstants.NO_NONCE, HConstants.NO_NONCE); 6229 } 6230 6231 RegionScannerImpl(Scan scan, List<KeyValueScanner> additionalScanners, HRegion region, 6232 long nonceGroup, long nonce) throws IOException { 6233 this.region = region; 6234 this.maxResultSize = scan.getMaxResultSize(); 6235 if (scan.hasFilter()) { 6236 this.filter = new FilterWrapper(scan.getFilter()); 6237 } else { 6238 this.filter = null; 6239 } 6240 this.comparator = region.getCellComparator(); 6241 /** 6242 * By default, calls to next/nextRaw must enforce the batch limit. Thus, construct a default 6243 * scanner context that can be used to enforce the batch limit in the event that a 6244 * ScannerContext is not specified during an invocation of next/nextRaw 6245 */ 6246 defaultScannerContext = ScannerContext.newBuilder() 6247 .setBatchLimit(scan.getBatch()).build(); 6248 this.stopRow = scan.getStopRow(); 6249 this.includeStopRow = scan.includeStopRow(); 6250 6251 // synchronize on scannerReadPoints so that nobody calculates 6252 // getSmallestReadPoint, before scannerReadPoints is updated. 6253 IsolationLevel isolationLevel = scan.getIsolationLevel(); 6254 long mvccReadPoint = PackagePrivateFieldAccessor.getMvccReadPoint(scan); 6255 synchronized (scannerReadPoints) { 6256 if (mvccReadPoint > 0) { 6257 this.readPt = mvccReadPoint; 6258 } else if (nonce == HConstants.NO_NONCE || rsServices == null 6259 || rsServices.getNonceManager() == null) { 6260 this.readPt = getReadPoint(isolationLevel); 6261 } else { 6262 this.readPt = rsServices.getNonceManager().getMvccFromOperationContext(nonceGroup, nonce); 6263 } 6264 scannerReadPoints.put(this, this.readPt); 6265 } 6266 initializeScanners(scan, additionalScanners); 6267 } 6268 6269 protected void initializeScanners(Scan scan, List<KeyValueScanner> additionalScanners) 6270 throws IOException { 6271 // Here we separate all scanners into two lists - scanner that provide data required 6272 // by the filter to operate (scanners list) and all others (joinedScanners list). 6273 List<KeyValueScanner> scanners = new ArrayList<>(scan.getFamilyMap().size()); 6274 List<KeyValueScanner> joinedScanners = new ArrayList<>(scan.getFamilyMap().size()); 6275 // Store all already instantiated scanners for exception handling 6276 List<KeyValueScanner> instantiatedScanners = new ArrayList<>(); 6277 // handle additionalScanners 6278 if (additionalScanners != null && !additionalScanners.isEmpty()) { 6279 scanners.addAll(additionalScanners); 6280 instantiatedScanners.addAll(additionalScanners); 6281 } 6282 6283 try { 6284 for (Map.Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) { 6285 HStore store = stores.get(entry.getKey()); 6286 KeyValueScanner scanner = store.getScanner(scan, entry.getValue(), this.readPt); 6287 instantiatedScanners.add(scanner); 6288 if (this.filter == null || !scan.doLoadColumnFamiliesOnDemand() 6289 || this.filter.isFamilyEssential(entry.getKey())) { 6290 scanners.add(scanner); 6291 } else { 6292 joinedScanners.add(scanner); 6293 } 6294 } 6295 initializeKVHeap(scanners, joinedScanners, region); 6296 } catch (Throwable t) { 6297 throw handleException(instantiatedScanners, t); 6298 } 6299 } 6300 6301 protected void initializeKVHeap(List<KeyValueScanner> scanners, 6302 List<KeyValueScanner> joinedScanners, HRegion region) 6303 throws IOException { 6304 this.storeHeap = new KeyValueHeap(scanners, comparator); 6305 if (!joinedScanners.isEmpty()) { 6306 this.joinedHeap = new KeyValueHeap(joinedScanners, comparator); 6307 } 6308 } 6309 6310 private IOException handleException(List<KeyValueScanner> instantiatedScanners, 6311 Throwable t) { 6312 // remove scaner read point before throw the exception 6313 scannerReadPoints.remove(this); 6314 if (storeHeap != null) { 6315 storeHeap.close(); 6316 storeHeap = null; 6317 if (joinedHeap != null) { 6318 joinedHeap.close(); 6319 joinedHeap = null; 6320 } 6321 } else { 6322 // close all already instantiated scanners before throwing the exception 6323 for (KeyValueScanner scanner : instantiatedScanners) { 6324 scanner.close(); 6325 } 6326 } 6327 return t instanceof IOException ? (IOException) t : new IOException(t); 6328 } 6329 6330 @Override 6331 public long getMaxResultSize() { 6332 return maxResultSize; 6333 } 6334 6335 @Override 6336 public long getMvccReadPoint() { 6337 return this.readPt; 6338 } 6339 6340 @Override 6341 public int getBatch() { 6342 return this.defaultScannerContext.getBatchLimit(); 6343 } 6344 6345 /** 6346 * Reset both the filter and the old filter. 6347 * 6348 * @throws IOException in case a filter raises an I/O exception. 6349 */ 6350 protected void resetFilters() throws IOException { 6351 if (filter != null) { 6352 filter.reset(); 6353 } 6354 } 6355 6356 @Override 6357 public boolean next(List<Cell> outResults) 6358 throws IOException { 6359 // apply the batching limit by default 6360 return next(outResults, defaultScannerContext); 6361 } 6362 6363 @Override 6364 public synchronized boolean next(List<Cell> outResults, ScannerContext scannerContext) 6365 throws IOException { 6366 if (this.filterClosed) { 6367 throw new UnknownScannerException("Scanner was closed (timed out?) " + 6368 "after we renewed it. Could be caused by a very slow scanner " + 6369 "or a lengthy garbage collection"); 6370 } 6371 startRegionOperation(Operation.SCAN); 6372 try { 6373 return nextRaw(outResults, scannerContext); 6374 } finally { 6375 closeRegionOperation(Operation.SCAN); 6376 } 6377 } 6378 6379 @Override 6380 public boolean nextRaw(List<Cell> outResults) throws IOException { 6381 // Use the RegionScanner's context by default 6382 return nextRaw(outResults, defaultScannerContext); 6383 } 6384 6385 @Override 6386 public boolean nextRaw(List<Cell> outResults, ScannerContext scannerContext) 6387 throws IOException { 6388 if (storeHeap == null) { 6389 // scanner is closed 6390 throw new UnknownScannerException("Scanner was closed"); 6391 } 6392 boolean moreValues = false; 6393 if (outResults.isEmpty()) { 6394 // Usually outResults is empty. This is true when next is called 6395 // to handle scan or get operation. 6396 moreValues = nextInternal(outResults, scannerContext); 6397 } else { 6398 List<Cell> tmpList = new ArrayList<>(); 6399 moreValues = nextInternal(tmpList, scannerContext); 6400 outResults.addAll(tmpList); 6401 } 6402 6403 if (!outResults.isEmpty()) { 6404 readRequestsCount.increment(); 6405 } 6406 6407 // If the size limit was reached it means a partial Result is being returned. Returning a 6408 // partial Result means that we should not reset the filters; filters should only be reset in 6409 // between rows 6410 if (!scannerContext.mayHaveMoreCellsInRow()) { 6411 resetFilters(); 6412 } 6413 6414 if (isFilterDoneInternal()) { 6415 moreValues = false; 6416 } 6417 return moreValues; 6418 } 6419 6420 /** 6421 * @return true if more cells exist after this batch, false if scanner is done 6422 */ 6423 private boolean populateFromJoinedHeap(List<Cell> results, ScannerContext scannerContext) 6424 throws IOException { 6425 assert joinedContinuationRow != null; 6426 boolean moreValues = populateResult(results, this.joinedHeap, scannerContext, 6427 joinedContinuationRow); 6428 6429 if (!scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) { 6430 // We are done with this row, reset the continuation. 6431 joinedContinuationRow = null; 6432 } 6433 // As the data is obtained from two independent heaps, we need to 6434 // ensure that result list is sorted, because Result relies on that. 6435 sort(results, comparator); 6436 return moreValues; 6437 } 6438 6439 /** 6440 * Fetches records with currentRow into results list, until next row, batchLimit (if not -1) is 6441 * reached, or remainingResultSize (if not -1) is reaced 6442 * @param heap KeyValueHeap to fetch data from.It must be positioned on correct row before call. 6443 * @param scannerContext 6444 * @param currentRowCell 6445 * @return state of last call to {@link KeyValueHeap#next()} 6446 */ 6447 private boolean populateResult(List<Cell> results, KeyValueHeap heap, 6448 ScannerContext scannerContext, Cell currentRowCell) throws IOException { 6449 Cell nextKv; 6450 boolean moreCellsInRow = false; 6451 boolean tmpKeepProgress = scannerContext.getKeepProgress(); 6452 // Scanning between column families and thus the scope is between cells 6453 LimitScope limitScope = LimitScope.BETWEEN_CELLS; 6454 do { 6455 // We want to maintain any progress that is made towards the limits while scanning across 6456 // different column families. To do this, we toggle the keep progress flag on during calls 6457 // to the StoreScanner to ensure that any progress made thus far is not wiped away. 6458 scannerContext.setKeepProgress(true); 6459 heap.next(results, scannerContext); 6460 scannerContext.setKeepProgress(tmpKeepProgress); 6461 6462 nextKv = heap.peek(); 6463 moreCellsInRow = moreCellsInRow(nextKv, currentRowCell); 6464 if (!moreCellsInRow) incrementCountOfRowsScannedMetric(scannerContext); 6465 if (moreCellsInRow && scannerContext.checkBatchLimit(limitScope)) { 6466 return scannerContext.setScannerState(NextState.BATCH_LIMIT_REACHED).hasMoreValues(); 6467 } else if (scannerContext.checkSizeLimit(limitScope)) { 6468 ScannerContext.NextState state = 6469 moreCellsInRow ? NextState.SIZE_LIMIT_REACHED_MID_ROW : NextState.SIZE_LIMIT_REACHED; 6470 return scannerContext.setScannerState(state).hasMoreValues(); 6471 } else if (scannerContext.checkTimeLimit(limitScope)) { 6472 ScannerContext.NextState state = 6473 moreCellsInRow ? NextState.TIME_LIMIT_REACHED_MID_ROW : NextState.TIME_LIMIT_REACHED; 6474 return scannerContext.setScannerState(state).hasMoreValues(); 6475 } 6476 } while (moreCellsInRow); 6477 return nextKv != null; 6478 } 6479 6480 /** 6481 * Based on the nextKv in the heap, and the current row, decide whether or not there are more 6482 * cells to be read in the heap. If the row of the nextKv in the heap matches the current row 6483 * then there are more cells to be read in the row. 6484 * @param nextKv 6485 * @param currentRowCell 6486 * @return true When there are more cells in the row to be read 6487 */ 6488 private boolean moreCellsInRow(final Cell nextKv, Cell currentRowCell) { 6489 return nextKv != null && CellUtil.matchingRows(nextKv, currentRowCell); 6490 } 6491 6492 /* 6493 * @return True if a filter rules the scanner is over, done. 6494 */ 6495 @Override 6496 public synchronized boolean isFilterDone() throws IOException { 6497 return isFilterDoneInternal(); 6498 } 6499 6500 private boolean isFilterDoneInternal() throws IOException { 6501 return this.filter != null && this.filter.filterAllRemaining(); 6502 } 6503 6504 private boolean nextInternal(List<Cell> results, ScannerContext scannerContext) 6505 throws IOException { 6506 if (!results.isEmpty()) { 6507 throw new IllegalArgumentException("First parameter should be an empty list"); 6508 } 6509 if (scannerContext == null) { 6510 throw new IllegalArgumentException("Scanner context cannot be null"); 6511 } 6512 Optional<RpcCall> rpcCall = RpcServer.getCurrentCall(); 6513 6514 // Save the initial progress from the Scanner context in these local variables. The progress 6515 // may need to be reset a few times if rows are being filtered out so we save the initial 6516 // progress. 6517 int initialBatchProgress = scannerContext.getBatchProgress(); 6518 long initialSizeProgress = scannerContext.getDataSizeProgress(); 6519 long initialHeapSizeProgress = scannerContext.getHeapSizeProgress(); 6520 6521 // Used to check time limit 6522 LimitScope limitScope = LimitScope.BETWEEN_CELLS; 6523 6524 // The loop here is used only when at some point during the next we determine 6525 // that due to effects of filters or otherwise, we have an empty row in the result. 6526 // Then we loop and try again. Otherwise, we must get out on the first iteration via return, 6527 // "true" if there's more data to read, "false" if there isn't (storeHeap is at a stop row, 6528 // and joinedHeap has no more data to read for the last row (if set, joinedContinuationRow). 6529 while (true) { 6530 // Starting to scan a new row. Reset the scanner progress according to whether or not 6531 // progress should be kept. 6532 if (scannerContext.getKeepProgress()) { 6533 // Progress should be kept. Reset to initial values seen at start of method invocation. 6534 scannerContext.setProgress(initialBatchProgress, initialSizeProgress, 6535 initialHeapSizeProgress); 6536 } else { 6537 scannerContext.clearProgress(); 6538 } 6539 if (rpcCall.isPresent()) { 6540 // If a user specifies a too-restrictive or too-slow scanner, the 6541 // client might time out and disconnect while the server side 6542 // is still processing the request. We should abort aggressively 6543 // in that case. 6544 long afterTime = rpcCall.get().disconnectSince(); 6545 if (afterTime >= 0) { 6546 throw new CallerDisconnectedException( 6547 "Aborting on region " + getRegionInfo().getRegionNameAsString() + ", call " + 6548 this + " after " + afterTime + " ms, since " + 6549 "caller disconnected"); 6550 } 6551 } 6552 6553 // Let's see what we have in the storeHeap. 6554 Cell current = this.storeHeap.peek(); 6555 6556 boolean shouldStop = shouldStop(current); 6557 // When has filter row is true it means that the all the cells for a particular row must be 6558 // read before a filtering decision can be made. This means that filters where hasFilterRow 6559 // run the risk of enLongAddering out of memory errors in the case that they are applied to a 6560 // table that has very large rows. 6561 boolean hasFilterRow = this.filter != null && this.filter.hasFilterRow(); 6562 6563 // If filter#hasFilterRow is true, partial results are not allowed since allowing them 6564 // would prevent the filters from being evaluated. Thus, if it is true, change the 6565 // scope of any limits that could potentially create partial results to 6566 // LimitScope.BETWEEN_ROWS so that those limits are not reached mid-row 6567 if (hasFilterRow) { 6568 if (LOG.isTraceEnabled()) { 6569 LOG.trace("filter#hasFilterRow is true which prevents partial results from being " 6570 + " formed. Changing scope of limits that may create partials"); 6571 } 6572 scannerContext.setSizeLimitScope(LimitScope.BETWEEN_ROWS); 6573 scannerContext.setTimeLimitScope(LimitScope.BETWEEN_ROWS); 6574 limitScope = LimitScope.BETWEEN_ROWS; 6575 } 6576 6577 if (scannerContext.checkTimeLimit(LimitScope.BETWEEN_CELLS)) { 6578 if (hasFilterRow) { 6579 throw new IncompatibleFilterException( 6580 "Filter whose hasFilterRow() returns true is incompatible with scans that must " + 6581 " stop mid-row because of a limit. ScannerContext:" + scannerContext); 6582 } 6583 return true; 6584 } 6585 6586 // Check if we were getting data from the joinedHeap and hit the limit. 6587 // If not, then it's main path - getting results from storeHeap. 6588 if (joinedContinuationRow == null) { 6589 // First, check if we are at a stop row. If so, there are no more results. 6590 if (shouldStop) { 6591 if (hasFilterRow) { 6592 filter.filterRowCells(results); 6593 } 6594 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6595 } 6596 6597 // Check if rowkey filter wants to exclude this row. If so, loop to next. 6598 // Technically, if we hit limits before on this row, we don't need this call. 6599 if (filterRowKey(current)) { 6600 incrementCountOfRowsFilteredMetric(scannerContext); 6601 // early check, see HBASE-16296 6602 if (isFilterDoneInternal()) { 6603 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6604 } 6605 // Typically the count of rows scanned is incremented inside #populateResult. However, 6606 // here we are filtering a row based purely on its row key, preventing us from calling 6607 // #populateResult. Thus, perform the necessary increment here to rows scanned metric 6608 incrementCountOfRowsScannedMetric(scannerContext); 6609 boolean moreRows = nextRow(scannerContext, current); 6610 if (!moreRows) { 6611 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6612 } 6613 results.clear(); 6614 6615 // Read nothing as the rowkey was filtered, but still need to check time limit 6616 if (scannerContext.checkTimeLimit(limitScope)) { 6617 return true; 6618 } 6619 continue; 6620 } 6621 6622 // Ok, we are good, let's try to get some results from the main heap. 6623 populateResult(results, this.storeHeap, scannerContext, current); 6624 if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) { 6625 if (hasFilterRow) { 6626 throw new IncompatibleFilterException( 6627 "Filter whose hasFilterRow() returns true is incompatible with scans that must " 6628 + " stop mid-row because of a limit. ScannerContext:" + scannerContext); 6629 } 6630 return true; 6631 } 6632 6633 Cell nextKv = this.storeHeap.peek(); 6634 shouldStop = shouldStop(nextKv); 6635 // save that the row was empty before filters applied to it. 6636 final boolean isEmptyRow = results.isEmpty(); 6637 6638 // We have the part of the row necessary for filtering (all of it, usually). 6639 // First filter with the filterRow(List). 6640 FilterWrapper.FilterRowRetCode ret = FilterWrapper.FilterRowRetCode.NOT_CALLED; 6641 if (hasFilterRow) { 6642 ret = filter.filterRowCellsWithRet(results); 6643 6644 // We don't know how the results have changed after being filtered. Must set progress 6645 // according to contents of results now. 6646 if (scannerContext.getKeepProgress()) { 6647 scannerContext.setProgress(initialBatchProgress, initialSizeProgress, 6648 initialHeapSizeProgress); 6649 } else { 6650 scannerContext.clearProgress(); 6651 } 6652 scannerContext.incrementBatchProgress(results.size()); 6653 for (Cell cell : results) { 6654 scannerContext.incrementSizeProgress(PrivateCellUtil.estimatedSerializedSizeOf(cell), 6655 PrivateCellUtil.estimatedSizeOfCell(cell)); 6656 } 6657 } 6658 6659 if (isEmptyRow || ret == FilterWrapper.FilterRowRetCode.EXCLUDE || filterRow()) { 6660 incrementCountOfRowsFilteredMetric(scannerContext); 6661 results.clear(); 6662 boolean moreRows = nextRow(scannerContext, current); 6663 if (!moreRows) { 6664 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6665 } 6666 6667 // This row was totally filtered out, if this is NOT the last row, 6668 // we should continue on. Otherwise, nothing else to do. 6669 if (!shouldStop) { 6670 // Read nothing as the cells was filtered, but still need to check time limit 6671 if (scannerContext.checkTimeLimit(limitScope)) { 6672 return true; 6673 } 6674 continue; 6675 } 6676 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6677 } 6678 6679 // Ok, we are done with storeHeap for this row. 6680 // Now we may need to fetch additional, non-essential data into row. 6681 // These values are not needed for filter to work, so we postpone their 6682 // fetch to (possibly) reduce amount of data loads from disk. 6683 if (this.joinedHeap != null) { 6684 boolean mayHaveData = joinedHeapMayHaveData(current); 6685 if (mayHaveData) { 6686 joinedContinuationRow = current; 6687 populateFromJoinedHeap(results, scannerContext); 6688 6689 if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) { 6690 return true; 6691 } 6692 } 6693 } 6694 } else { 6695 // Populating from the joined heap was stopped by limits, populate some more. 6696 populateFromJoinedHeap(results, scannerContext); 6697 if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) { 6698 return true; 6699 } 6700 } 6701 // We may have just called populateFromJoinedMap and hit the limits. If that is 6702 // the case, we need to call it again on the next next() invocation. 6703 if (joinedContinuationRow != null) { 6704 return scannerContext.setScannerState(NextState.MORE_VALUES).hasMoreValues(); 6705 } 6706 6707 // Finally, we are done with both joinedHeap and storeHeap. 6708 // Double check to prevent empty rows from appearing in result. It could be 6709 // the case when SingleColumnValueExcludeFilter is used. 6710 if (results.isEmpty()) { 6711 incrementCountOfRowsFilteredMetric(scannerContext); 6712 boolean moreRows = nextRow(scannerContext, current); 6713 if (!moreRows) { 6714 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6715 } 6716 if (!shouldStop) continue; 6717 } 6718 6719 if (shouldStop) { 6720 return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues(); 6721 } else { 6722 return scannerContext.setScannerState(NextState.MORE_VALUES).hasMoreValues(); 6723 } 6724 } 6725 } 6726 6727 protected void incrementCountOfRowsFilteredMetric(ScannerContext scannerContext) { 6728 filteredReadRequestsCount.increment(); 6729 6730 if (scannerContext == null || !scannerContext.isTrackingMetrics()) return; 6731 6732 scannerContext.getMetrics().countOfRowsFiltered.incrementAndGet(); 6733 } 6734 6735 protected void incrementCountOfRowsScannedMetric(ScannerContext scannerContext) { 6736 if (scannerContext == null || !scannerContext.isTrackingMetrics()) return; 6737 6738 scannerContext.getMetrics().countOfRowsScanned.incrementAndGet(); 6739 } 6740 6741 /** 6742 * @param currentRowCell 6743 * @return true when the joined heap may have data for the current row 6744 * @throws IOException 6745 */ 6746 private boolean joinedHeapMayHaveData(Cell currentRowCell) 6747 throws IOException { 6748 Cell nextJoinedKv = joinedHeap.peek(); 6749 boolean matchCurrentRow = 6750 nextJoinedKv != null && CellUtil.matchingRows(nextJoinedKv, currentRowCell); 6751 boolean matchAfterSeek = false; 6752 6753 // If the next value in the joined heap does not match the current row, try to seek to the 6754 // correct row 6755 if (!matchCurrentRow) { 6756 Cell firstOnCurrentRow = PrivateCellUtil.createFirstOnRow(currentRowCell); 6757 boolean seekSuccessful = this.joinedHeap.requestSeek(firstOnCurrentRow, true, true); 6758 matchAfterSeek = 6759 seekSuccessful && joinedHeap.peek() != null 6760 && CellUtil.matchingRows(joinedHeap.peek(), currentRowCell); 6761 } 6762 6763 return matchCurrentRow || matchAfterSeek; 6764 } 6765 6766 /** 6767 * This function is to maintain backward compatibility for 0.94 filters. HBASE-6429 combines 6768 * both filterRow & filterRow({@code List<KeyValue> kvs}) functions. While 0.94 code or older, 6769 * it may not implement hasFilterRow as HBase-6429 expects because 0.94 hasFilterRow() only 6770 * returns true when filterRow({@code List<KeyValue> kvs}) is overridden not the filterRow(). 6771 * Therefore, the filterRow() will be skipped. 6772 */ 6773 private boolean filterRow() throws IOException { 6774 // when hasFilterRow returns true, filter.filterRow() will be called automatically inside 6775 // filterRowCells(List<Cell> kvs) so we skip that scenario here. 6776 return filter != null && (!filter.hasFilterRow()) 6777 && filter.filterRow(); 6778 } 6779 6780 private boolean filterRowKey(Cell current) throws IOException { 6781 return filter != null && filter.filterRowKey(current); 6782 } 6783 6784 protected boolean nextRow(ScannerContext scannerContext, Cell curRowCell) throws IOException { 6785 assert this.joinedContinuationRow == null: "Trying to go to next row during joinedHeap read."; 6786 Cell next; 6787 while ((next = this.storeHeap.peek()) != null && 6788 CellUtil.matchingRows(next, curRowCell)) { 6789 this.storeHeap.next(MOCKED_LIST); 6790 } 6791 resetFilters(); 6792 6793 // Calling the hook in CP which allows it to do a fast forward 6794 return this.region.getCoprocessorHost() == null 6795 || this.region.getCoprocessorHost() 6796 .postScannerFilterRow(this, curRowCell); 6797 } 6798 6799 protected boolean shouldStop(Cell currentRowCell) { 6800 if (currentRowCell == null) { 6801 return true; 6802 } 6803 if (stopRow == null || Bytes.equals(stopRow, HConstants.EMPTY_END_ROW)) { 6804 return false; 6805 } 6806 int c = comparator.compareRows(currentRowCell, stopRow, 0, stopRow.length); 6807 return c > 0 || (c == 0 && !includeStopRow); 6808 } 6809 6810 @Override 6811 public synchronized void close() { 6812 if (storeHeap != null) { 6813 storeHeap.close(); 6814 storeHeap = null; 6815 } 6816 if (joinedHeap != null) { 6817 joinedHeap.close(); 6818 joinedHeap = null; 6819 } 6820 // no need to synchronize here. 6821 scannerReadPoints.remove(this); 6822 this.filterClosed = true; 6823 } 6824 6825 KeyValueHeap getStoreHeapForTesting() { 6826 return storeHeap; 6827 } 6828 6829 @Override 6830 public synchronized boolean reseek(byte[] row) throws IOException { 6831 if (row == null) { 6832 throw new IllegalArgumentException("Row cannot be null."); 6833 } 6834 boolean result = false; 6835 startRegionOperation(); 6836 Cell kv = PrivateCellUtil.createFirstOnRow(row, 0, (short) row.length); 6837 try { 6838 // use request seek to make use of the lazy seek option. See HBASE-5520 6839 result = this.storeHeap.requestSeek(kv, true, true); 6840 if (this.joinedHeap != null) { 6841 result = this.joinedHeap.requestSeek(kv, true, true) || result; 6842 } 6843 } finally { 6844 closeRegionOperation(); 6845 } 6846 return result; 6847 } 6848 6849 @Override 6850 public void shipped() throws IOException { 6851 if (storeHeap != null) { 6852 storeHeap.shipped(); 6853 } 6854 if (joinedHeap != null) { 6855 joinedHeap.shipped(); 6856 } 6857 } 6858 6859 @Override 6860 public void run() throws IOException { 6861 // This is the RPC callback method executed. We do the close in of the scanner in this 6862 // callback 6863 this.close(); 6864 } 6865 } 6866 6867 // Utility methods 6868 /** 6869 * A utility method to create new instances of HRegion based on the 6870 * {@link HConstants#REGION_IMPL} configuration property. 6871 * @param tableDir qualified path of directory where region should be located, 6872 * usually the table directory. 6873 * @param wal The WAL is the outbound log for any updates to the HRegion 6874 * The wal file is a logfile from the previous execution that's 6875 * custom-computed for this HRegion. The HRegionServer computes and sorts the 6876 * appropriate wal info for this HRegion. If there is a previous file 6877 * (implying that the HRegion has been written-to before), then read it from 6878 * the supplied path. 6879 * @param fs is the filesystem. 6880 * @param conf is global configuration settings. 6881 * @param regionInfo - RegionInfo that describes the region 6882 * is new), then read them from the supplied path. 6883 * @param htd the table descriptor 6884 * @return the new instance 6885 */ 6886 static HRegion newHRegion(Path tableDir, WAL wal, FileSystem fs, 6887 Configuration conf, RegionInfo regionInfo, final TableDescriptor htd, 6888 RegionServerServices rsServices) { 6889 try { 6890 @SuppressWarnings("unchecked") 6891 Class<? extends HRegion> regionClass = 6892 (Class<? extends HRegion>) conf.getClass(HConstants.REGION_IMPL, HRegion.class); 6893 6894 Constructor<? extends HRegion> c = 6895 regionClass.getConstructor(Path.class, WAL.class, FileSystem.class, 6896 Configuration.class, RegionInfo.class, TableDescriptor.class, 6897 RegionServerServices.class); 6898 6899 return c.newInstance(tableDir, wal, fs, conf, regionInfo, htd, rsServices); 6900 } catch (Throwable e) { 6901 // todo: what should I throw here? 6902 throw new IllegalStateException("Could not instantiate a region instance.", e); 6903 } 6904 } 6905 6906 /** 6907 * Convenience method creating new HRegions. Used by createTable. 6908 * 6909 * @param info Info for region to create. 6910 * @param rootDir Root directory for HBase instance 6911 * @param wal shared WAL 6912 * @param initialize - true to initialize the region 6913 * @return new HRegion 6914 * @throws IOException 6915 */ 6916 public static HRegion createHRegion(final RegionInfo info, final Path rootDir, 6917 final Configuration conf, final TableDescriptor hTableDescriptor, 6918 final WAL wal, final boolean initialize) 6919 throws IOException { 6920 LOG.info("creating HRegion " + info.getTable().getNameAsString() 6921 + " HTD == " + hTableDescriptor + " RootDir = " + rootDir + 6922 " Table name == " + info.getTable().getNameAsString()); 6923 FileSystem fs = FileSystem.get(conf); 6924 Path tableDir = FSUtils.getTableDir(rootDir, info.getTable()); 6925 HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, info); 6926 HRegion region = HRegion.newHRegion(tableDir, wal, fs, conf, info, hTableDescriptor, null); 6927 if (initialize) region.initialize(null); 6928 return region; 6929 } 6930 6931 public static HRegion createHRegion(final RegionInfo info, final Path rootDir, 6932 final Configuration conf, 6933 final TableDescriptor hTableDescriptor, 6934 final WAL wal) 6935 throws IOException { 6936 return createHRegion(info, rootDir, conf, hTableDescriptor, wal, true); 6937 } 6938 6939 6940 /** 6941 * Open a Region. 6942 * @param info Info for region to be opened. 6943 * @param wal WAL for region to use. This method will call 6944 * WAL#setSequenceNumber(long) passing the result of the call to 6945 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 6946 * up. HRegionStore does this every time it opens a new region. 6947 * @return new HRegion 6948 * 6949 * @throws IOException 6950 */ 6951 public static HRegion openHRegion(final RegionInfo info, 6952 final TableDescriptor htd, final WAL wal, 6953 final Configuration conf) 6954 throws IOException { 6955 return openHRegion(info, htd, wal, conf, null, null); 6956 } 6957 6958 /** 6959 * Open a Region. 6960 * @param info Info for region to be opened 6961 * @param htd the table descriptor 6962 * @param wal WAL for region to use. This method will call 6963 * WAL#setSequenceNumber(long) passing the result of the call to 6964 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 6965 * up. HRegionStore does this every time it opens a new region. 6966 * @param conf The Configuration object to use. 6967 * @param rsServices An interface we can request flushes against. 6968 * @param reporter An interface we can report progress against. 6969 * @return new HRegion 6970 * 6971 * @throws IOException 6972 */ 6973 public static HRegion openHRegion(final RegionInfo info, 6974 final TableDescriptor htd, final WAL wal, final Configuration conf, 6975 final RegionServerServices rsServices, 6976 final CancelableProgressable reporter) 6977 throws IOException { 6978 return openHRegion(FSUtils.getRootDir(conf), info, htd, wal, conf, rsServices, reporter); 6979 } 6980 6981 /** 6982 * Open a Region. 6983 * @param rootDir Root directory for HBase instance 6984 * @param info Info for region to be opened. 6985 * @param htd the table descriptor 6986 * @param wal WAL for region to use. This method will call 6987 * WAL#setSequenceNumber(long) passing the result of the call to 6988 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 6989 * up. HRegionStore does this every time it opens a new region. 6990 * @param conf The Configuration object to use. 6991 * @return new HRegion 6992 * @throws IOException 6993 */ 6994 public static HRegion openHRegion(Path rootDir, final RegionInfo info, 6995 final TableDescriptor htd, final WAL wal, final Configuration conf) 6996 throws IOException { 6997 return openHRegion(rootDir, info, htd, wal, conf, null, null); 6998 } 6999 7000 /** 7001 * Open a Region. 7002 * @param rootDir Root directory for HBase instance 7003 * @param info Info for region to be opened. 7004 * @param htd the table descriptor 7005 * @param wal WAL for region to use. This method will call 7006 * WAL#setSequenceNumber(long) passing the result of the call to 7007 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 7008 * up. HRegionStore does this every time it opens a new region. 7009 * @param conf The Configuration object to use. 7010 * @param rsServices An interface we can request flushes against. 7011 * @param reporter An interface we can report progress against. 7012 * @return new HRegion 7013 * @throws IOException 7014 */ 7015 public static HRegion openHRegion(final Path rootDir, final RegionInfo info, 7016 final TableDescriptor htd, final WAL wal, final Configuration conf, 7017 final RegionServerServices rsServices, 7018 final CancelableProgressable reporter) 7019 throws IOException { 7020 FileSystem fs = null; 7021 if (rsServices != null) { 7022 fs = rsServices.getFileSystem(); 7023 } 7024 if (fs == null) { 7025 fs = FileSystem.get(conf); 7026 } 7027 return openHRegion(conf, fs, rootDir, info, htd, wal, rsServices, reporter); 7028 } 7029 7030 /** 7031 * Open a Region. 7032 * @param conf The Configuration object to use. 7033 * @param fs Filesystem to use 7034 * @param rootDir Root directory for HBase instance 7035 * @param info Info for region to be opened. 7036 * @param htd the table descriptor 7037 * @param wal WAL for region to use. This method will call 7038 * WAL#setSequenceNumber(long) passing the result of the call to 7039 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 7040 * up. HRegionStore does this every time it opens a new region. 7041 * @return new HRegion 7042 */ 7043 public static HRegion openHRegion(final Configuration conf, final FileSystem fs, 7044 final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal) 7045 throws IOException { 7046 return openHRegion(conf, fs, rootDir, info, htd, wal, null, null); 7047 } 7048 7049 /** 7050 * Open a Region. 7051 * @param conf The Configuration object to use. 7052 * @param fs Filesystem to use 7053 * @param rootDir Root directory for HBase instance 7054 * @param info Info for region to be opened. 7055 * @param htd the table descriptor 7056 * @param wal WAL for region to use. This method will call 7057 * WAL#setSequenceNumber(long) passing the result of the call to 7058 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 7059 * up. HRegionStore does this every time it opens a new region. 7060 * @param rsServices An interface we can request flushes against. 7061 * @param reporter An interface we can report progress against. 7062 * @return new HRegion 7063 */ 7064 public static HRegion openHRegion(final Configuration conf, final FileSystem fs, 7065 final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal, 7066 final RegionServerServices rsServices, final CancelableProgressable reporter) 7067 throws IOException { 7068 Path tableDir = FSUtils.getTableDir(rootDir, info.getTable()); 7069 return openHRegion(conf, fs, rootDir, tableDir, info, htd, wal, rsServices, reporter); 7070 } 7071 7072 /** 7073 * Open a Region. 7074 * @param conf The Configuration object to use. 7075 * @param fs Filesystem to use 7076 * @param rootDir Root directory for HBase instance 7077 * @param info Info for region to be opened. 7078 * @param htd the table descriptor 7079 * @param wal WAL for region to use. This method will call 7080 * WAL#setSequenceNumber(long) passing the result of the call to 7081 * HRegion#getMinSequenceId() to ensure the wal id is properly kept 7082 * up. HRegionStore does this every time it opens a new region. 7083 * @param rsServices An interface we can request flushes against. 7084 * @param reporter An interface we can report progress against. 7085 * @return new HRegion 7086 */ 7087 public static HRegion openHRegion(final Configuration conf, final FileSystem fs, 7088 final Path rootDir, final Path tableDir, final RegionInfo info, final TableDescriptor htd, 7089 final WAL wal, final RegionServerServices rsServices, 7090 final CancelableProgressable reporter) 7091 throws IOException { 7092 if (info == null) throw new NullPointerException("Passed region info is null"); 7093 if (LOG.isDebugEnabled()) { 7094 LOG.debug("Opening region: " + info); 7095 } 7096 HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, rsServices); 7097 return r.openHRegion(reporter); 7098 } 7099 7100 @VisibleForTesting 7101 public NavigableMap<byte[], Integer> getReplicationScope() { 7102 return this.replicationScope; 7103 } 7104 7105 /** 7106 * Useful when reopening a closed region (normally for unit tests) 7107 * @param other original object 7108 * @param reporter An interface we can report progress against. 7109 * @return new HRegion 7110 */ 7111 public static HRegion openHRegion(final HRegion other, final CancelableProgressable reporter) 7112 throws IOException { 7113 HRegionFileSystem regionFs = other.getRegionFileSystem(); 7114 HRegion r = newHRegion(regionFs.getTableDir(), other.getWAL(), regionFs.getFileSystem(), 7115 other.baseConf, other.getRegionInfo(), other.getTableDescriptor(), null); 7116 return r.openHRegion(reporter); 7117 } 7118 7119 public static Region openHRegion(final Region other, final CancelableProgressable reporter) 7120 throws IOException { 7121 return openHRegion((HRegion)other, reporter); 7122 } 7123 7124 /** 7125 * Open HRegion. 7126 * Calls initialize and sets sequenceId. 7127 * @return Returns <code>this</code> 7128 */ 7129 protected HRegion openHRegion(final CancelableProgressable reporter) 7130 throws IOException { 7131 // Refuse to open the region if we are missing local compression support 7132 checkCompressionCodecs(); 7133 // Refuse to open the region if encryption configuration is incorrect or 7134 // codec support is missing 7135 checkEncryption(); 7136 // Refuse to open the region if a required class cannot be loaded 7137 checkClassLoading(); 7138 this.openSeqNum = initialize(reporter); 7139 this.mvcc.advanceTo(openSeqNum); 7140 // The openSeqNum must be increased every time when a region is assigned, as we rely on it to 7141 // determine whether a region has been successfully reopened. So here we always write open 7142 // marker, even if the table is read only. 7143 if (wal != null && getRegionServerServices() != null && 7144 RegionReplicaUtil.isDefaultReplica(getRegionInfo())) { 7145 writeRegionOpenMarker(wal, openSeqNum); 7146 } 7147 return this; 7148 } 7149 7150 /** 7151 * Open a Region on a read-only file-system (like hdfs snapshots) 7152 * @param conf The Configuration object to use. 7153 * @param fs Filesystem to use 7154 * @param info Info for region to be opened. 7155 * @param htd the table descriptor 7156 * @return new HRegion 7157 */ 7158 public static HRegion openReadOnlyFileSystemHRegion(final Configuration conf, final FileSystem fs, 7159 final Path tableDir, RegionInfo info, final TableDescriptor htd) throws IOException { 7160 if (info == null) { 7161 throw new NullPointerException("Passed region info is null"); 7162 } 7163 if (LOG.isDebugEnabled()) { 7164 LOG.debug("Opening region (readOnly filesystem): " + info); 7165 } 7166 if (info.getReplicaId() <= 0) { 7167 info = RegionInfoBuilder.newBuilder(info).setReplicaId(1).build(); 7168 } 7169 HRegion r = HRegion.newHRegion(tableDir, null, fs, conf, info, htd, null); 7170 r.writestate.setReadOnly(true); 7171 return r.openHRegion(null); 7172 } 7173 7174 public static void warmupHRegion(final RegionInfo info, 7175 final TableDescriptor htd, final WAL wal, final Configuration conf, 7176 final RegionServerServices rsServices, 7177 final CancelableProgressable reporter) 7178 throws IOException { 7179 7180 if (info == null) throw new NullPointerException("Passed region info is null"); 7181 7182 if (LOG.isDebugEnabled()) { 7183 LOG.debug("HRegion.Warming up region: " + info); 7184 } 7185 7186 Path rootDir = FSUtils.getRootDir(conf); 7187 Path tableDir = FSUtils.getTableDir(rootDir, info.getTable()); 7188 7189 FileSystem fs = null; 7190 if (rsServices != null) { 7191 fs = rsServices.getFileSystem(); 7192 } 7193 if (fs == null) { 7194 fs = FileSystem.get(conf); 7195 } 7196 7197 HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, null); 7198 r.initializeWarmup(reporter); 7199 } 7200 7201 7202 private void checkCompressionCodecs() throws IOException { 7203 for (ColumnFamilyDescriptor fam: this.htableDescriptor.getColumnFamilies()) { 7204 CompressionTest.testCompression(fam.getCompressionType()); 7205 CompressionTest.testCompression(fam.getCompactionCompressionType()); 7206 } 7207 } 7208 7209 private void checkEncryption() throws IOException { 7210 for (ColumnFamilyDescriptor fam: this.htableDescriptor.getColumnFamilies()) { 7211 EncryptionTest.testEncryption(conf, fam.getEncryptionType(), fam.getEncryptionKey()); 7212 } 7213 } 7214 7215 private void checkClassLoading() throws IOException { 7216 RegionSplitPolicy.getSplitPolicyClass(this.htableDescriptor, conf); 7217 RegionCoprocessorHost.testTableCoprocessorAttrs(conf, this.htableDescriptor); 7218 } 7219 7220 /** 7221 * Computes the Path of the HRegion 7222 * 7223 * @param tabledir qualified path for table 7224 * @param name ENCODED region name 7225 * @return Path of HRegion directory 7226 * @deprecated For tests only; to be removed. 7227 */ 7228 @Deprecated 7229 public static Path getRegionDir(final Path tabledir, final String name) { 7230 return new Path(tabledir, name); 7231 } 7232 7233 /** 7234 * Computes the Path of the HRegion 7235 * 7236 * @param rootdir qualified path of HBase root directory 7237 * @param info RegionInfo for the region 7238 * @return qualified path of region directory 7239 * @deprecated For tests only; to be removed. 7240 */ 7241 @Deprecated 7242 @VisibleForTesting 7243 public static Path getRegionDir(final Path rootdir, final RegionInfo info) { 7244 return new Path( 7245 FSUtils.getTableDir(rootdir, info.getTable()), info.getEncodedName()); 7246 } 7247 7248 /** 7249 * Determines if the specified row is within the row range specified by the 7250 * specified RegionInfo 7251 * 7252 * @param info RegionInfo that specifies the row range 7253 * @param row row to be checked 7254 * @return true if the row is within the range specified by the RegionInfo 7255 */ 7256 public static boolean rowIsInRange(RegionInfo info, final byte [] row) { 7257 return ((info.getStartKey().length == 0) || 7258 (Bytes.compareTo(info.getStartKey(), row) <= 0)) && 7259 ((info.getEndKey().length == 0) || 7260 (Bytes.compareTo(info.getEndKey(), row) > 0)); 7261 } 7262 7263 public static boolean rowIsInRange(RegionInfo info, final byte [] row, final int offset, 7264 final short length) { 7265 return ((info.getStartKey().length == 0) || 7266 (Bytes.compareTo(info.getStartKey(), 0, info.getStartKey().length, 7267 row, offset, length) <= 0)) && 7268 ((info.getEndKey().length == 0) || 7269 (Bytes.compareTo(info.getEndKey(), 0, info.getEndKey().length, row, offset, length) > 0)); 7270 } 7271 7272 @Override 7273 public Result get(final Get get) throws IOException { 7274 prepareGet(get); 7275 List<Cell> results = get(get, true); 7276 boolean stale = this.getRegionInfo().getReplicaId() != 0; 7277 return Result.create(results, get.isCheckExistenceOnly() ? !results.isEmpty() : null, stale); 7278 } 7279 7280 void prepareGet(final Get get) throws IOException { 7281 checkRow(get.getRow(), "Get"); 7282 // Verify families are all valid 7283 if (get.hasFamilies()) { 7284 for (byte[] family : get.familySet()) { 7285 checkFamily(family); 7286 } 7287 } else { // Adding all families to scanner 7288 for (byte[] family : this.htableDescriptor.getColumnFamilyNames()) { 7289 get.addFamily(family); 7290 } 7291 } 7292 } 7293 7294 @Override 7295 public List<Cell> get(Get get, boolean withCoprocessor) throws IOException { 7296 return get(get, withCoprocessor, HConstants.NO_NONCE, HConstants.NO_NONCE); 7297 } 7298 7299 public List<Cell> get(Get get, boolean withCoprocessor, long nonceGroup, long nonce) 7300 throws IOException { 7301 List<Cell> results = new ArrayList<>(); 7302 long before = EnvironmentEdgeManager.currentTime(); 7303 7304 // pre-get CP hook 7305 if (withCoprocessor && (coprocessorHost != null)) { 7306 if (coprocessorHost.preGet(get, results)) { 7307 metricsUpdateForGet(results, before); 7308 return results; 7309 } 7310 } 7311 Scan scan = new Scan(get); 7312 if (scan.getLoadColumnFamiliesOnDemandValue() == null) { 7313 scan.setLoadColumnFamiliesOnDemand(isLoadingCfsOnDemandDefault()); 7314 } 7315 RegionScanner scanner = null; 7316 try { 7317 scanner = getScanner(scan, null, nonceGroup, nonce); 7318 scanner.next(results); 7319 } finally { 7320 if (scanner != null) 7321 scanner.close(); 7322 } 7323 7324 // post-get CP hook 7325 if (withCoprocessor && (coprocessorHost != null)) { 7326 coprocessorHost.postGet(get, results); 7327 } 7328 7329 metricsUpdateForGet(results, before); 7330 7331 return results; 7332 } 7333 7334 void metricsUpdateForGet(List<Cell> results, long before) { 7335 if (this.metricsRegion != null) { 7336 this.metricsRegion.updateGet(EnvironmentEdgeManager.currentTime() - before); 7337 } 7338 } 7339 7340 @Override 7341 public void mutateRow(RowMutations rm) throws IOException { 7342 // Don't need nonces here - RowMutations only supports puts and deletes 7343 final List<Mutation> m = rm.getMutations(); 7344 batchMutate(m.toArray(new Mutation[m.size()]), true, HConstants.NO_NONCE, 7345 HConstants.NO_NONCE); 7346 } 7347 7348 /** 7349 * Perform atomic (all or none) mutations within the region. 7350 * @param mutations The list of mutations to perform. 7351 * <code>mutations</code> can contain operations for multiple rows. 7352 * Caller has to ensure that all rows are contained in this region. 7353 * @param rowsToLock Rows to lock 7354 * @param nonceGroup Optional nonce group of the operation (client Id) 7355 * @param nonce Optional nonce of the operation (unique random id to ensure "more idempotence") 7356 * If multiple rows are locked care should be taken that 7357 * <code>rowsToLock</code> is sorted in order to avoid deadlocks. 7358 * @throws IOException 7359 */ 7360 @Override 7361 public void mutateRowsWithLocks(Collection<Mutation> mutations, 7362 Collection<byte[]> rowsToLock, long nonceGroup, long nonce) throws IOException { 7363 batchMutate(new MutationBatchOperation(this, mutations.toArray(new Mutation[mutations.size()]), 7364 true, nonceGroup, nonce) { 7365 @Override 7366 public MiniBatchOperationInProgress<Mutation> lockRowsAndBuildMiniBatch( 7367 List<RowLock> acquiredRowLocks) throws IOException { 7368 RowLock prevRowLock = null; 7369 for (byte[] row : rowsToLock) { 7370 try { 7371 RowLock rowLock = region.getRowLockInternal(row, false, prevRowLock); // write lock 7372 if (rowLock != prevRowLock) { 7373 acquiredRowLocks.add(rowLock); 7374 prevRowLock = rowLock; 7375 } 7376 } catch (IOException ioe) { 7377 LOG.warn("Failed getting lock, row=" + Bytes.toStringBinary(row), ioe); 7378 throw ioe; 7379 } 7380 } 7381 return createMiniBatch(size(), size()); 7382 } 7383 }); 7384 } 7385 7386 /** 7387 * @return statistics about the current load of the region 7388 */ 7389 public ClientProtos.RegionLoadStats getLoadStatistics() { 7390 if (!regionStatsEnabled) { 7391 return null; 7392 } 7393 ClientProtos.RegionLoadStats.Builder stats = ClientProtos.RegionLoadStats.newBuilder(); 7394 stats.setMemStoreLoad((int) (Math.min(100, 7395 (this.memStoreSizing.getMemStoreSize().getHeapSize() * 100) / this.memstoreFlushSize))); 7396 if (rsServices.getHeapMemoryManager() != null) { 7397 // the HeapMemoryManager uses -0.0 to signal a problem asking the JVM, 7398 // so we could just do the calculation below and we'll get a 0. 7399 // treating it as a special case analogous to no HMM instead so that it can be 7400 // programatically treated different from using <1% of heap. 7401 final float occupancy = rsServices.getHeapMemoryManager().getHeapOccupancyPercent(); 7402 if (occupancy != HeapMemoryManager.HEAP_OCCUPANCY_ERROR_VALUE) { 7403 stats.setHeapOccupancy((int)(occupancy * 100)); 7404 } 7405 } 7406 stats.setCompactionPressure((int) (rsServices.getCompactionPressure() * 100 > 100 ? 100 7407 : rsServices.getCompactionPressure() * 100)); 7408 return stats.build(); 7409 } 7410 7411 @Override 7412 public void processRowsWithLocks(RowProcessor<?,?> processor) throws IOException { 7413 processRowsWithLocks(processor, rowProcessorTimeout, HConstants.NO_NONCE, HConstants.NO_NONCE); 7414 } 7415 7416 @Override 7417 public void processRowsWithLocks(RowProcessor<?,?> processor, long nonceGroup, long nonce) 7418 throws IOException { 7419 processRowsWithLocks(processor, rowProcessorTimeout, nonceGroup, nonce); 7420 } 7421 7422 @Override 7423 public void processRowsWithLocks(RowProcessor<?,?> processor, long timeout, 7424 long nonceGroup, long nonce) throws IOException { 7425 for (byte[] row : processor.getRowsToLock()) { 7426 checkRow(row, "processRowsWithLocks"); 7427 } 7428 if (!processor.readOnly()) { 7429 checkReadOnly(); 7430 } 7431 checkResources(); 7432 startRegionOperation(); 7433 WALEdit walEdit = new WALEdit(); 7434 7435 // STEP 1. Run pre-process hook 7436 preProcess(processor, walEdit); 7437 // Short circuit the read only case 7438 if (processor.readOnly()) { 7439 try { 7440 long now = EnvironmentEdgeManager.currentTime(); 7441 doProcessRowWithTimeout(processor, now, this, null, null, timeout); 7442 processor.postProcess(this, walEdit, true); 7443 } finally { 7444 closeRegionOperation(); 7445 } 7446 return; 7447 } 7448 7449 boolean locked = false; 7450 List<RowLock> acquiredRowLocks = null; 7451 List<Mutation> mutations = new ArrayList<>(); 7452 Collection<byte[]> rowsToLock = processor.getRowsToLock(); 7453 // This is assigned by mvcc either explicity in the below or in the guts of the WAL append 7454 // when it assigns the edit a sequencedid (A.K.A the mvcc write number). 7455 WriteEntry writeEntry = null; 7456 MemStoreSizing memstoreAccounting = new NonThreadSafeMemStoreSizing(); 7457 try { 7458 boolean success = false; 7459 try { 7460 // STEP 2. Acquire the row lock(s) 7461 acquiredRowLocks = new ArrayList<>(rowsToLock.size()); 7462 RowLock prevRowLock = null; 7463 for (byte[] row : rowsToLock) { 7464 // Attempt to lock all involved rows, throw if any lock times out 7465 // use a writer lock for mixed reads and writes 7466 RowLock rowLock = getRowLockInternal(row, false, prevRowLock); 7467 if (rowLock != prevRowLock) { 7468 acquiredRowLocks.add(rowLock); 7469 prevRowLock = rowLock; 7470 } 7471 } 7472 // STEP 3. Region lock 7473 lock(this.updatesLock.readLock(), acquiredRowLocks.isEmpty() ? 1 : acquiredRowLocks.size()); 7474 locked = true; 7475 long now = EnvironmentEdgeManager.currentTime(); 7476 // STEP 4. Let the processor scan the rows, generate mutations and add waledits 7477 doProcessRowWithTimeout(processor, now, this, mutations, walEdit, timeout); 7478 if (!mutations.isEmpty()) { 7479 writeRequestsCount.add(mutations.size()); 7480 // STEP 5. Call the preBatchMutate hook 7481 processor.preBatchMutate(this, walEdit); 7482 7483 // STEP 6. Append and sync if walEdit has data to write out. 7484 if (!walEdit.isEmpty()) { 7485 writeEntry = doWALAppend(walEdit, getEffectiveDurability(processor.useDurability()), 7486 processor.getClusterIds(), now, nonceGroup, nonce); 7487 } else { 7488 // We are here if WAL is being skipped. 7489 writeEntry = this.mvcc.begin(); 7490 } 7491 7492 // STEP 7. Apply to memstore 7493 long sequenceId = writeEntry.getWriteNumber(); 7494 for (Mutation m : mutations) { 7495 // Handle any tag based cell features. 7496 // TODO: Do we need to call rewriteCellTags down in applyToMemStore()? Why not before 7497 // so tags go into WAL? 7498 rewriteCellTags(m.getFamilyCellMap(), m); 7499 for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) { 7500 Cell cell = cellScanner.current(); 7501 if (walEdit.isEmpty()) { 7502 // If walEdit is empty, we put nothing in WAL. WAL stamps Cells with sequence id. 7503 // If no WAL, need to stamp it here. 7504 PrivateCellUtil.setSequenceId(cell, sequenceId); 7505 } 7506 applyToMemStore(getStore(cell), cell, memstoreAccounting); 7507 } 7508 } 7509 7510 // STEP 8. call postBatchMutate hook 7511 processor.postBatchMutate(this); 7512 7513 // STEP 9. Complete mvcc. 7514 mvcc.completeAndWait(writeEntry); 7515 writeEntry = null; 7516 7517 // STEP 10. Release region lock 7518 if (locked) { 7519 this.updatesLock.readLock().unlock(); 7520 locked = false; 7521 } 7522 7523 // STEP 11. Release row lock(s) 7524 releaseRowLocks(acquiredRowLocks); 7525 } 7526 success = true; 7527 } finally { 7528 // Call complete rather than completeAndWait because we probably had error if walKey != null 7529 if (writeEntry != null) mvcc.complete(writeEntry); 7530 if (locked) { 7531 this.updatesLock.readLock().unlock(); 7532 } 7533 // release locks if some were acquired but another timed out 7534 releaseRowLocks(acquiredRowLocks); 7535 } 7536 7537 // 12. Run post-process hook 7538 processor.postProcess(this, walEdit, success); 7539 } finally { 7540 closeRegionOperation(); 7541 if (!mutations.isEmpty()) { 7542 this.incMemStoreSize(memstoreAccounting.getMemStoreSize()); 7543 requestFlushIfNeeded(); 7544 } 7545 } 7546 } 7547 7548 private void preProcess(final RowProcessor<?,?> processor, final WALEdit walEdit) 7549 throws IOException { 7550 try { 7551 processor.preProcess(this, walEdit); 7552 } catch (IOException e) { 7553 closeRegionOperation(); 7554 throw e; 7555 } 7556 } 7557 7558 private void doProcessRowWithTimeout(final RowProcessor<?,?> processor, 7559 final long now, 7560 final HRegion region, 7561 final List<Mutation> mutations, 7562 final WALEdit walEdit, 7563 final long timeout) throws IOException { 7564 // Short circuit the no time bound case. 7565 if (timeout < 0) { 7566 try { 7567 processor.process(now, region, mutations, walEdit); 7568 } catch (IOException e) { 7569 String row = processor.getRowsToLock().isEmpty() ? "" : 7570 " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "..."; 7571 LOG.warn("RowProcessor:" + processor.getClass().getName() + 7572 " throws Exception" + row, e); 7573 throw e; 7574 } 7575 return; 7576 } 7577 7578 // Case with time bound 7579 FutureTask<Void> task = new FutureTask<>(new Callable<Void>() { 7580 @Override 7581 public Void call() throws IOException { 7582 try { 7583 processor.process(now, region, mutations, walEdit); 7584 return null; 7585 } catch (IOException e) { 7586 String row = processor.getRowsToLock().isEmpty() ? "" : 7587 " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "..."; 7588 LOG.warn("RowProcessor:" + processor.getClass().getName() + 7589 " throws Exception" + row, e); 7590 throw e; 7591 } 7592 } 7593 }); 7594 rowProcessorExecutor.execute(task); 7595 try { 7596 task.get(timeout, TimeUnit.MILLISECONDS); 7597 } catch (TimeoutException te) { 7598 String row = processor.getRowsToLock().isEmpty() ? "" : 7599 " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "..."; 7600 LOG.error("RowProcessor timeout:" + timeout + " ms" + row); 7601 throw new IOException(te); 7602 } catch (Exception e) { 7603 throw new IOException(e); 7604 } 7605 } 7606 7607 @Override 7608 public Result append(Append append) throws IOException { 7609 return append(append, HConstants.NO_NONCE, HConstants.NO_NONCE); 7610 } 7611 7612 public Result append(Append mutation, long nonceGroup, long nonce) throws IOException { 7613 return doDelta(Operation.APPEND, mutation, nonceGroup, nonce, mutation.isReturnResults()); 7614 } 7615 7616 @Override 7617 public Result increment(Increment increment) throws IOException { 7618 return increment(increment, HConstants.NO_NONCE, HConstants.NO_NONCE); 7619 } 7620 7621 public Result increment(Increment mutation, long nonceGroup, long nonce) throws IOException { 7622 return doDelta(Operation.INCREMENT, mutation, nonceGroup, nonce, mutation.isReturnResults()); 7623 } 7624 7625 /** 7626 * Add "deltas" to Cells. Deltas are increments or appends. Switch on <code>op</code>. 7627 * 7628 * <p>If increment, add deltas to current values or if an append, then 7629 * append the deltas to the current Cell values. 7630 * 7631 * <p>Append and Increment code paths are mostly the same. They differ in just a few places. 7632 * This method does the code path for increment and append and then in key spots, switches 7633 * on the passed in <code>op</code> to do increment or append specific paths. 7634 */ 7635 private Result doDelta(Operation op, Mutation mutation, long nonceGroup, long nonce, 7636 boolean returnResults) throws IOException { 7637 checkReadOnly(); 7638 checkResources(); 7639 checkRow(mutation.getRow(), op.toString()); 7640 checkFamilies(mutation.getFamilyCellMap().keySet()); 7641 this.writeRequestsCount.increment(); 7642 WriteEntry writeEntry = null; 7643 startRegionOperation(op); 7644 List<Cell> results = returnResults? new ArrayList<>(mutation.size()): null; 7645 RowLock rowLock = null; 7646 MemStoreSizing memstoreAccounting = new NonThreadSafeMemStoreSizing(); 7647 try { 7648 rowLock = getRowLockInternal(mutation.getRow(), false, null); 7649 lock(this.updatesLock.readLock()); 7650 try { 7651 Result cpResult = doCoprocessorPreCall(op, mutation); 7652 if (cpResult != null) { 7653 // Metrics updated below in the finally block. 7654 return returnResults? cpResult: null; 7655 } 7656 Durability effectiveDurability = getEffectiveDurability(mutation.getDurability()); 7657 Map<HStore, List<Cell>> forMemStore = new HashMap<>(mutation.getFamilyCellMap().size()); 7658 // Reckon Cells to apply to WAL -- in returned walEdit -- and what to add to memstore and 7659 // what to return back to the client (in 'forMemStore' and 'results' respectively). 7660 WALEdit walEdit = reckonDeltas(op, mutation, effectiveDurability, forMemStore, results); 7661 // Actually write to WAL now if a walEdit to apply. 7662 if (walEdit != null && !walEdit.isEmpty()) { 7663 writeEntry = doWALAppend(walEdit, effectiveDurability, nonceGroup, nonce); 7664 } else { 7665 // If walEdits is empty, it means we skipped the WAL; update LongAdders and start an mvcc 7666 // transaction. 7667 recordMutationWithoutWal(mutation.getFamilyCellMap()); 7668 writeEntry = mvcc.begin(); 7669 updateSequenceId(forMemStore.values(), writeEntry.getWriteNumber()); 7670 } 7671 // Now write to MemStore. Do it a column family at a time. 7672 for (Map.Entry<HStore, List<Cell>> e : forMemStore.entrySet()) { 7673 applyToMemStore(e.getKey(), e.getValue(), true, memstoreAccounting); 7674 } 7675 mvcc.completeAndWait(writeEntry); 7676 if (rsServices != null && rsServices.getNonceManager() != null) { 7677 rsServices.getNonceManager().addMvccToOperationContext(nonceGroup, nonce, 7678 writeEntry.getWriteNumber()); 7679 } 7680 writeEntry = null; 7681 } finally { 7682 this.updatesLock.readLock().unlock(); 7683 } 7684 // If results is null, then client asked that we not return the calculated results. 7685 return results != null && returnResults? Result.create(results): Result.EMPTY_RESULT; 7686 } finally { 7687 // Call complete always, even on success. doDelta is doing a Get READ_UNCOMMITTED when it goes 7688 // to get current value under an exclusive lock so no need so no need to wait to return to 7689 // the client. Means only way to read-your-own-increment or append is to come in with an 7690 // a 0 increment. 7691 if (writeEntry != null) mvcc.complete(writeEntry); 7692 if (rowLock != null) { 7693 rowLock.release(); 7694 } 7695 // Request a cache flush if over the limit. Do it outside update lock. 7696 incMemStoreSize(memstoreAccounting.getMemStoreSize()); 7697 requestFlushIfNeeded(); 7698 closeRegionOperation(op); 7699 if (this.metricsRegion != null) { 7700 switch (op) { 7701 case INCREMENT: 7702 this.metricsRegion.updateIncrement(); 7703 break; 7704 case APPEND: 7705 this.metricsRegion.updateAppend(); 7706 break; 7707 default: 7708 break; 7709 } 7710 } 7711 } 7712 } 7713 7714 private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, long nonceGroup, 7715 long nonce) 7716 throws IOException { 7717 return doWALAppend(walEdit, durability, WALKey.EMPTY_UUIDS, System.currentTimeMillis(), 7718 nonceGroup, nonce); 7719 } 7720 7721 private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, List<UUID> clusterIds, 7722 long now, long nonceGroup, long nonce) throws IOException { 7723 return doWALAppend(walEdit, durability, clusterIds, now, nonceGroup, nonce, 7724 SequenceId.NO_SEQUENCE_ID); 7725 } 7726 7727 /** 7728 * @return writeEntry associated with this append 7729 */ 7730 private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, List<UUID> clusterIds, 7731 long now, long nonceGroup, long nonce, long origLogSeqNum) throws IOException { 7732 Preconditions.checkArgument(walEdit != null && !walEdit.isEmpty(), 7733 "WALEdit is null or empty!"); 7734 Preconditions.checkArgument(!walEdit.isReplay() || origLogSeqNum != SequenceId.NO_SEQUENCE_ID, 7735 "Invalid replay sequence Id for replay WALEdit!"); 7736 // Using default cluster id, as this can only happen in the originating cluster. 7737 // A slave cluster receives the final value (not the delta) as a Put. We use HLogKey 7738 // here instead of WALKeyImpl directly to support legacy coprocessors. 7739 WALKeyImpl walKey = walEdit.isReplay()? 7740 new WALKeyImpl(this.getRegionInfo().getEncodedNameAsBytes(), 7741 this.htableDescriptor.getTableName(), SequenceId.NO_SEQUENCE_ID, now, clusterIds, 7742 nonceGroup, nonce, mvcc) : 7743 new WALKeyImpl(this.getRegionInfo().getEncodedNameAsBytes(), 7744 this.htableDescriptor.getTableName(), SequenceId.NO_SEQUENCE_ID, now, clusterIds, 7745 nonceGroup, nonce, mvcc, this.getReplicationScope()); 7746 if (walEdit.isReplay()) { 7747 walKey.setOrigLogSeqNum(origLogSeqNum); 7748 } 7749 WriteEntry writeEntry = null; 7750 try { 7751 long txid = this.wal.append(this.getRegionInfo(), walKey, walEdit, true); 7752 // Call sync on our edit. 7753 if (txid != 0) { 7754 sync(txid, durability); 7755 } 7756 writeEntry = walKey.getWriteEntry(); 7757 } catch (IOException ioe) { 7758 if (walKey != null && walKey.getWriteEntry() != null) { 7759 mvcc.complete(walKey.getWriteEntry()); 7760 } 7761 throw ioe; 7762 } 7763 return writeEntry; 7764 } 7765 7766 /** 7767 * Do coprocessor pre-increment or pre-append call. 7768 * @return Result returned out of the coprocessor, which means bypass all further processing and 7769 * return the proffered Result instead, or null which means proceed. 7770 */ 7771 private Result doCoprocessorPreCall(final Operation op, final Mutation mutation) 7772 throws IOException { 7773 Result result = null; 7774 if (this.coprocessorHost != null) { 7775 switch(op) { 7776 case INCREMENT: 7777 result = this.coprocessorHost.preIncrementAfterRowLock((Increment)mutation); 7778 break; 7779 case APPEND: 7780 result = this.coprocessorHost.preAppendAfterRowLock((Append)mutation); 7781 break; 7782 default: throw new UnsupportedOperationException(op.toString()); 7783 } 7784 } 7785 return result; 7786 } 7787 7788 /** 7789 * Reckon the Cells to apply to WAL, memstore, and to return to the Client; these Sets are not 7790 * always the same dependent on whether to write WAL. 7791 * 7792 * @param results Fill in here what goes back to the Client if it is non-null (if null, client 7793 * doesn't want results). 7794 * @param forMemStore Fill in here what to apply to the MemStore (by Store). 7795 * @return A WALEdit to apply to WAL or null if we are to skip the WAL. 7796 */ 7797 private WALEdit reckonDeltas(Operation op, Mutation mutation, Durability effectiveDurability, 7798 Map<HStore, List<Cell>> forMemStore, List<Cell> results) throws IOException { 7799 WALEdit walEdit = null; 7800 long now = EnvironmentEdgeManager.currentTime(); 7801 final boolean writeToWAL = effectiveDurability != Durability.SKIP_WAL; 7802 // Process a Store/family at a time. 7803 for (Map.Entry<byte [], List<Cell>> entry: mutation.getFamilyCellMap().entrySet()) { 7804 final byte[] columnFamilyName = entry.getKey(); 7805 List<Cell> deltas = entry.getValue(); 7806 HStore store = this.stores.get(columnFamilyName); 7807 // Reckon for the Store what to apply to WAL and MemStore. 7808 List<Cell> toApply = 7809 reckonDeltasByStore(store, op, mutation, effectiveDurability, now, deltas, results); 7810 if (!toApply.isEmpty()) { 7811 forMemStore.put(store, toApply); 7812 if (writeToWAL) { 7813 if (walEdit == null) { 7814 walEdit = new WALEdit(); 7815 } 7816 walEdit.getCells().addAll(toApply); 7817 } 7818 } 7819 } 7820 return walEdit; 7821 } 7822 7823 /** 7824 * Reckon the Cells to apply to WAL, memstore, and to return to the Client in passed 7825 * column family/Store. 7826 * 7827 * Does Get of current value and then adds passed in deltas for this Store returning the result. 7828 * 7829 * @param op Whether Increment or Append 7830 * @param mutation The encompassing Mutation object 7831 * @param deltas Changes to apply to this Store; either increment amount or data to append 7832 * @param results In here we accumulate all the Cells we are to return to the client. If null, 7833 * client doesn't want results returned. 7834 * @return Resulting Cells after <code>deltas</code> have been applied to current 7835 * values. Side effect is our filling out of the <code>results</code> List. 7836 */ 7837 private List<Cell> reckonDeltasByStore(HStore store, Operation op, Mutation mutation, 7838 Durability effectiveDurability, long now, List<Cell> deltas, List<Cell> results) 7839 throws IOException { 7840 byte[] columnFamily = store.getColumnFamilyDescriptor().getName(); 7841 List<Cell> toApply = new ArrayList<>(deltas.size()); 7842 // Get previous values for all columns in this family. 7843 TimeRange tr = null; 7844 switch (op) { 7845 case INCREMENT: 7846 tr = ((Increment)mutation).getTimeRange(); 7847 break; 7848 case APPEND: 7849 tr = ((Append)mutation).getTimeRange(); 7850 break; 7851 default: 7852 break; 7853 } 7854 List<Cell> currentValues = get(mutation, store, deltas,null, tr); 7855 // Iterate the input columns and update existing values if they were found, otherwise 7856 // add new column initialized to the delta amount 7857 int currentValuesIndex = 0; 7858 for (int i = 0; i < deltas.size(); i++) { 7859 Cell delta = deltas.get(i); 7860 Cell currentValue = null; 7861 if (currentValuesIndex < currentValues.size() && 7862 CellUtil.matchingQualifier(currentValues.get(currentValuesIndex), delta)) { 7863 currentValue = currentValues.get(currentValuesIndex); 7864 if (i < (deltas.size() - 1) && !CellUtil.matchingQualifier(delta, deltas.get(i + 1))) { 7865 currentValuesIndex++; 7866 } 7867 } 7868 // Switch on whether this an increment or an append building the new Cell to apply. 7869 Cell newCell = null; 7870 MutationType mutationType = null; 7871 switch (op) { 7872 case INCREMENT: 7873 mutationType = MutationType.INCREMENT; 7874 long deltaAmount = getLongValue(delta); 7875 final long newValue = currentValue == null ? deltaAmount : getLongValue(currentValue) + deltaAmount; 7876 newCell = reckonDelta(delta, currentValue, columnFamily, now, mutation, (oldCell) -> Bytes.toBytes(newValue)); 7877 break; 7878 case APPEND: 7879 mutationType = MutationType.APPEND; 7880 newCell = reckonDelta(delta, currentValue, columnFamily, now, mutation, (oldCell) -> 7881 ByteBuffer.wrap(new byte[delta.getValueLength() + oldCell.getValueLength()]) 7882 .put(oldCell.getValueArray(), oldCell.getValueOffset(), oldCell.getValueLength()) 7883 .put(delta.getValueArray(), delta.getValueOffset(), delta.getValueLength()) 7884 .array() 7885 ); 7886 break; 7887 default: throw new UnsupportedOperationException(op.toString()); 7888 } 7889 7890 // Give coprocessors a chance to update the new cell 7891 if (coprocessorHost != null) { 7892 newCell = 7893 coprocessorHost.postMutationBeforeWAL(mutationType, mutation, currentValue, newCell); 7894 } 7895 toApply.add(newCell); 7896 // Add to results to get returned to the Client. If null, cilent does not want results. 7897 if (results != null) { 7898 results.add(newCell); 7899 } 7900 } 7901 return toApply; 7902 } 7903 7904 private static Cell reckonDelta(final Cell delta, final Cell currentCell, 7905 final byte[] columnFamily, final long now, 7906 Mutation mutation, Function<Cell, byte[]> supplier) throws IOException { 7907 // Forward any tags found on the delta. 7908 List<Tag> tags = TagUtil.carryForwardTags(delta); 7909 tags = TagUtil.carryForwardTTLTag(tags, mutation.getTTL()); 7910 if (currentCell != null) { 7911 tags = TagUtil.carryForwardTags(tags, currentCell); 7912 byte[] newValue = supplier.apply(currentCell); 7913 return ExtendedCellBuilderFactory.create(CellBuilderType.SHALLOW_COPY) 7914 .setRow(mutation.getRow(), 0, mutation.getRow().length) 7915 .setFamily(columnFamily, 0, columnFamily.length) 7916 // copy the qualifier if the cell is located in shared memory. 7917 .setQualifier(CellUtil.cloneQualifier(delta)) 7918 .setTimestamp(Math.max(currentCell.getTimestamp() + 1, now)) 7919 .setType(KeyValue.Type.Put.getCode()) 7920 .setValue(newValue, 0, newValue.length) 7921 .setTags(TagUtil.fromList(tags)) 7922 .build(); 7923 } else { 7924 PrivateCellUtil.updateLatestStamp(delta, now); 7925 return CollectionUtils.isEmpty(tags) ? delta : PrivateCellUtil.createCell(delta, tags); 7926 } 7927 } 7928 7929 /** 7930 * @return Get the long out of the passed in Cell 7931 */ 7932 private static long getLongValue(final Cell cell) throws DoNotRetryIOException { 7933 int len = cell.getValueLength(); 7934 if (len != Bytes.SIZEOF_LONG) { 7935 // throw DoNotRetryIOException instead of IllegalArgumentException 7936 throw new DoNotRetryIOException("Field is not a long, it's " + len + " bytes wide"); 7937 } 7938 return PrivateCellUtil.getValueAsLong(cell); 7939 } 7940 7941 /** 7942 * Do a specific Get on passed <code>columnFamily</code> and column qualifiers. 7943 * @param mutation Mutation we are doing this Get for. 7944 * @param store Which column family on row (TODO: Go all Gets in one go) 7945 * @param coordinates Cells from <code>mutation</code> used as coordinates applied to Get. 7946 * @return Return list of Cells found. 7947 */ 7948 private List<Cell> get(Mutation mutation, HStore store, List<Cell> coordinates, 7949 IsolationLevel isolation, TimeRange tr) throws IOException { 7950 // Sort the cells so that they match the order that they appear in the Get results. Otherwise, 7951 // we won't be able to find the existing values if the cells are not specified in order by the 7952 // client since cells are in an array list. 7953 // TODO: I don't get why we are sorting. St.Ack 20150107 7954 sort(coordinates, store.getComparator()); 7955 Get get = new Get(mutation.getRow()); 7956 if (isolation != null) { 7957 get.setIsolationLevel(isolation); 7958 } 7959 for (Cell cell: coordinates) { 7960 get.addColumn(store.getColumnFamilyDescriptor().getName(), CellUtil.cloneQualifier(cell)); 7961 } 7962 // Increments carry time range. If an Increment instance, put it on the Get. 7963 if (tr != null) { 7964 get.setTimeRange(tr.getMin(), tr.getMax()); 7965 } 7966 return get(get, false); 7967 } 7968 7969 /** 7970 * @return Sorted list of <code>cells</code> using <code>comparator</code> 7971 */ 7972 private static List<Cell> sort(List<Cell> cells, final CellComparator comparator) { 7973 cells.sort(comparator); 7974 return cells; 7975 } 7976 7977 // 7978 // New HBASE-880 Helpers 7979 // 7980 7981 void checkFamily(final byte [] family) 7982 throws NoSuchColumnFamilyException { 7983 if (!this.htableDescriptor.hasColumnFamily(family)) { 7984 throw new NoSuchColumnFamilyException("Column family " + 7985 Bytes.toString(family) + " does not exist in region " + this 7986 + " in table " + this.htableDescriptor); 7987 } 7988 } 7989 7990 public static final long FIXED_OVERHEAD = ClassSize.align( 7991 ClassSize.OBJECT + 7992 ClassSize.ARRAY + 7993 53 * ClassSize.REFERENCE + 3 * Bytes.SIZEOF_INT + 7994 (14 * Bytes.SIZEOF_LONG) + 7995 3 * Bytes.SIZEOF_BOOLEAN); 7996 7997 // woefully out of date - currently missing: 7998 // 1 x HashMap - coprocessorServiceHandlers 7999 // 6 x LongAdder - numMutationsWithoutWAL, dataInMemoryWithoutWAL, 8000 // checkAndMutateChecksPassed, checkAndMutateChecksFailed, readRequestsCount, 8001 // writeRequestsCount 8002 // 1 x HRegion$WriteState - writestate 8003 // 1 x RegionCoprocessorHost - coprocessorHost 8004 // 1 x RegionSplitPolicy - splitPolicy 8005 // 1 x MetricsRegion - metricsRegion 8006 // 1 x MetricsRegionWrapperImpl - metricsRegionWrapper 8007 public static final long DEEP_OVERHEAD = FIXED_OVERHEAD + 8008 ClassSize.OBJECT + // closeLock 8009 (2 * ClassSize.ATOMIC_BOOLEAN) + // closed, closing 8010 (3 * ClassSize.ATOMIC_LONG) + // numPutsWithoutWAL, dataInMemoryWithoutWAL, 8011 // compactionsFailed 8012 (2 * ClassSize.CONCURRENT_HASHMAP) + // lockedRows, scannerReadPoints 8013 WriteState.HEAP_SIZE + // writestate 8014 ClassSize.CONCURRENT_SKIPLISTMAP + ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY + // stores 8015 (2 * ClassSize.REENTRANT_LOCK) + // lock, updatesLock 8016 MultiVersionConcurrencyControl.FIXED_SIZE // mvcc 8017 + 2 * ClassSize.TREEMAP // maxSeqIdInStores, replicationScopes 8018 + 2 * ClassSize.ATOMIC_INTEGER // majorInProgress, minorInProgress 8019 + ClassSize.STORE_SERVICES // store services 8020 ; 8021 8022 @Override 8023 public long heapSize() { 8024 // this does not take into account row locks, recent flushes, mvcc entries, and more 8025 return DEEP_OVERHEAD + stores.values().stream().mapToLong(HStore::heapSize).sum(); 8026 } 8027 8028 /** 8029 * Registers a new protocol buffer {@link Service} subclass as a coprocessor endpoint to 8030 * be available for handling Region#execService(com.google.protobuf.RpcController, 8031 * org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceCall) calls. 8032 * 8033 * <p> 8034 * Only a single instance may be registered per region for a given {@link Service} subclass (the 8035 * instances are keyed on {@link com.google.protobuf.Descriptors.ServiceDescriptor#getFullName()}. 8036 * After the first registration, subsequent calls with the same service name will fail with 8037 * a return value of {@code false}. 8038 * </p> 8039 * @param instance the {@code Service} subclass instance to expose as a coprocessor endpoint 8040 * @return {@code true} if the registration was successful, {@code false} 8041 * otherwise 8042 */ 8043 public boolean registerService(com.google.protobuf.Service instance) { 8044 /* 8045 * No stacking of instances is allowed for a single service name 8046 */ 8047 com.google.protobuf.Descriptors.ServiceDescriptor serviceDesc = instance.getDescriptorForType(); 8048 String serviceName = CoprocessorRpcUtils.getServiceName(serviceDesc); 8049 if (coprocessorServiceHandlers.containsKey(serviceName)) { 8050 LOG.error("Coprocessor service " + serviceName + 8051 " already registered, rejecting request from " + instance); 8052 return false; 8053 } 8054 8055 coprocessorServiceHandlers.put(serviceName, instance); 8056 if (LOG.isDebugEnabled()) { 8057 LOG.debug("Registered coprocessor service: region=" + 8058 Bytes.toStringBinary(getRegionInfo().getRegionName()) + 8059 " service=" + serviceName); 8060 } 8061 return true; 8062 } 8063 8064 /** 8065 * Executes a single protocol buffer coprocessor endpoint {@link Service} method using 8066 * the registered protocol handlers. {@link Service} implementations must be registered via the 8067 * {@link #registerService(com.google.protobuf.Service)} 8068 * method before they are available. 8069 * 8070 * @param controller an {@code RpcContoller} implementation to pass to the invoked service 8071 * @param call a {@code CoprocessorServiceCall} instance identifying the service, method, 8072 * and parameters for the method invocation 8073 * @return a protocol buffer {@code Message} instance containing the method's result 8074 * @throws IOException if no registered service handler is found or an error 8075 * occurs during the invocation 8076 * @see #registerService(com.google.protobuf.Service) 8077 */ 8078 public com.google.protobuf.Message execService(com.google.protobuf.RpcController controller, 8079 CoprocessorServiceCall call) throws IOException { 8080 String serviceName = call.getServiceName(); 8081 com.google.protobuf.Service service = coprocessorServiceHandlers.get(serviceName); 8082 if (service == null) { 8083 throw new UnknownProtocolException(null, "No registered coprocessor service found for " + 8084 serviceName + " in region " + Bytes.toStringBinary(getRegionInfo().getRegionName())); 8085 } 8086 com.google.protobuf.Descriptors.ServiceDescriptor serviceDesc = service.getDescriptorForType(); 8087 8088 String methodName = call.getMethodName(); 8089 com.google.protobuf.Descriptors.MethodDescriptor methodDesc = 8090 CoprocessorRpcUtils.getMethodDescriptor(methodName, serviceDesc); 8091 8092 com.google.protobuf.Message.Builder builder = 8093 service.getRequestPrototype(methodDesc).newBuilderForType(); 8094 8095 org.apache.hadoop.hbase.protobuf.ProtobufUtil.mergeFrom(builder, 8096 call.getRequest().toByteArray()); 8097 com.google.protobuf.Message request = 8098 CoprocessorRpcUtils.getRequest(service, methodDesc, call.getRequest()); 8099 8100 if (coprocessorHost != null) { 8101 request = coprocessorHost.preEndpointInvocation(service, methodName, request); 8102 } 8103 8104 final com.google.protobuf.Message.Builder responseBuilder = 8105 service.getResponsePrototype(methodDesc).newBuilderForType(); 8106 service.callMethod(methodDesc, controller, request, 8107 new com.google.protobuf.RpcCallback<com.google.protobuf.Message>() { 8108 @Override 8109 public void run(com.google.protobuf.Message message) { 8110 if (message != null) { 8111 responseBuilder.mergeFrom(message); 8112 } 8113 } 8114 }); 8115 8116 if (coprocessorHost != null) { 8117 coprocessorHost.postEndpointInvocation(service, methodName, request, responseBuilder); 8118 } 8119 IOException exception = 8120 org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils.getControllerException(controller); 8121 if (exception != null) { 8122 throw exception; 8123 } 8124 8125 return responseBuilder.build(); 8126 } 8127 8128 boolean shouldForceSplit() { 8129 return this.splitRequest; 8130 } 8131 8132 byte[] getExplicitSplitPoint() { 8133 return this.explicitSplitPoint; 8134 } 8135 8136 void forceSplit(byte[] sp) { 8137 // This HRegion will go away after the forced split is successful 8138 // But if a forced split fails, we need to clear forced split. 8139 this.splitRequest = true; 8140 if (sp != null) { 8141 this.explicitSplitPoint = sp; 8142 } 8143 } 8144 8145 void clearSplit() { 8146 this.splitRequest = false; 8147 this.explicitSplitPoint = null; 8148 } 8149 8150 /** 8151 * Return the splitpoint. null indicates the region isn't splittable 8152 * If the splitpoint isn't explicitly specified, it will go over the stores 8153 * to find the best splitpoint. Currently the criteria of best splitpoint 8154 * is based on the size of the store. 8155 */ 8156 public byte[] checkSplit() { 8157 // Can't split META 8158 if (this.getRegionInfo().isMetaRegion() || 8159 TableName.NAMESPACE_TABLE_NAME.equals(this.getRegionInfo().getTable())) { 8160 if (shouldForceSplit()) { 8161 LOG.warn("Cannot split meta region in HBase 0.20 and above"); 8162 } 8163 return null; 8164 } 8165 8166 // Can't split a region that is closing. 8167 if (this.isClosing()) { 8168 return null; 8169 } 8170 8171 if (!splitPolicy.shouldSplit()) { 8172 return null; 8173 } 8174 8175 byte[] ret = splitPolicy.getSplitPoint(); 8176 8177 if (ret != null) { 8178 try { 8179 checkRow(ret, "calculated split"); 8180 } catch (IOException e) { 8181 LOG.error("Ignoring invalid split", e); 8182 return null; 8183 } 8184 } 8185 return ret; 8186 } 8187 8188 /** 8189 * @return The priority that this region should have in the compaction queue 8190 */ 8191 public int getCompactPriority() { 8192 return stores.values().stream().mapToInt(HStore::getCompactPriority).min() 8193 .orElse(Store.NO_PRIORITY); 8194 } 8195 8196 /** @return the coprocessor host */ 8197 public RegionCoprocessorHost getCoprocessorHost() { 8198 return coprocessorHost; 8199 } 8200 8201 /** @param coprocessorHost the new coprocessor host */ 8202 @VisibleForTesting 8203 public void setCoprocessorHost(final RegionCoprocessorHost coprocessorHost) { 8204 this.coprocessorHost = coprocessorHost; 8205 } 8206 8207 @Override 8208 public void startRegionOperation() throws IOException { 8209 startRegionOperation(Operation.ANY); 8210 } 8211 8212 @Override 8213 public void startRegionOperation(Operation op) throws IOException { 8214 switch (op) { 8215 case GET: // read operations 8216 case SCAN: 8217 checkReadsEnabled(); 8218 break; 8219 default: 8220 break; 8221 } 8222 if (op == Operation.MERGE_REGION || op == Operation.SPLIT_REGION 8223 || op == Operation.COMPACT_REGION) { 8224 // split, merge or compact region doesn't need to check the closing/closed state or lock the 8225 // region 8226 return; 8227 } 8228 if (this.closing.get()) { 8229 throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closing"); 8230 } 8231 lock(lock.readLock()); 8232 if (this.closed.get()) { 8233 lock.readLock().unlock(); 8234 throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closed"); 8235 } 8236 // The unit for snapshot is a region. So, all stores for this region must be 8237 // prepared for snapshot operation before proceeding. 8238 if (op == Operation.SNAPSHOT) { 8239 stores.values().forEach(HStore::preSnapshotOperation); 8240 } 8241 try { 8242 if (coprocessorHost != null) { 8243 coprocessorHost.postStartRegionOperation(op); 8244 } 8245 } catch (Exception e) { 8246 lock.readLock().unlock(); 8247 throw new IOException(e); 8248 } 8249 } 8250 8251 @Override 8252 public void closeRegionOperation() throws IOException { 8253 closeRegionOperation(Operation.ANY); 8254 } 8255 8256 @Override 8257 public void closeRegionOperation(Operation operation) throws IOException { 8258 if (operation == Operation.SNAPSHOT) { 8259 stores.values().forEach(HStore::postSnapshotOperation); 8260 } 8261 lock.readLock().unlock(); 8262 if (coprocessorHost != null) { 8263 coprocessorHost.postCloseRegionOperation(operation); 8264 } 8265 } 8266 8267 /** 8268 * This method needs to be called before any public call that reads or 8269 * modifies stores in bulk. It has to be called just before a try. 8270 * #closeBulkRegionOperation needs to be called in the try's finally block 8271 * Acquires a writelock and checks if the region is closing or closed. 8272 * @throws NotServingRegionException when the region is closing or closed 8273 * @throws RegionTooBusyException if failed to get the lock in time 8274 * @throws InterruptedIOException if interrupted while waiting for a lock 8275 */ 8276 private void startBulkRegionOperation(boolean writeLockNeeded) 8277 throws NotServingRegionException, RegionTooBusyException, InterruptedIOException { 8278 if (this.closing.get()) { 8279 throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closing"); 8280 } 8281 if (writeLockNeeded) lock(lock.writeLock()); 8282 else lock(lock.readLock()); 8283 if (this.closed.get()) { 8284 if (writeLockNeeded) lock.writeLock().unlock(); 8285 else lock.readLock().unlock(); 8286 throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closed"); 8287 } 8288 } 8289 8290 /** 8291 * Closes the lock. This needs to be called in the finally block corresponding 8292 * to the try block of #startRegionOperation 8293 */ 8294 private void closeBulkRegionOperation(){ 8295 if (lock.writeLock().isHeldByCurrentThread()) lock.writeLock().unlock(); 8296 else lock.readLock().unlock(); 8297 } 8298 8299 /** 8300 * Update LongAdders for number of puts without wal and the size of possible data loss. 8301 * These information are exposed by the region server metrics. 8302 */ 8303 private void recordMutationWithoutWal(final Map<byte [], List<Cell>> familyMap) { 8304 numMutationsWithoutWAL.increment(); 8305 if (numMutationsWithoutWAL.sum() <= 1) { 8306 LOG.info("writing data to region " + this + 8307 " with WAL disabled. Data may be lost in the event of a crash."); 8308 } 8309 8310 long mutationSize = 0; 8311 for (List<Cell> cells: familyMap.values()) { 8312 // Optimization: 'foreach' loop is not used. See: 8313 // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects 8314 assert cells instanceof RandomAccess; 8315 int listSize = cells.size(); 8316 for (int i=0; i < listSize; i++) { 8317 Cell cell = cells.get(i); 8318 mutationSize += KeyValueUtil.length(cell); 8319 } 8320 } 8321 8322 dataInMemoryWithoutWAL.add(mutationSize); 8323 } 8324 8325 private void lock(final Lock lock) throws RegionTooBusyException, InterruptedIOException { 8326 lock(lock, 1); 8327 } 8328 8329 /** 8330 * Try to acquire a lock. Throw RegionTooBusyException 8331 * if failed to get the lock in time. Throw InterruptedIOException 8332 * if interrupted while waiting for the lock. 8333 */ 8334 private void lock(final Lock lock, final int multiplier) 8335 throws RegionTooBusyException, InterruptedIOException { 8336 try { 8337 final long waitTime = Math.min(maxBusyWaitDuration, 8338 busyWaitDuration * Math.min(multiplier, maxBusyWaitMultiplier)); 8339 if (!lock.tryLock(waitTime, TimeUnit.MILLISECONDS)) { 8340 // Don't print millis. Message is used as a key over in 8341 // RetriesExhaustedWithDetailsException processing. 8342 throw new RegionTooBusyException("Failed to obtain lock; regionName=" + 8343 (this.getRegionInfo() == null? "unknown": 8344 this.getRegionInfo().getRegionNameAsString()) + 8345 ", server=" + (this.getRegionServerServices() == null? "unknown": 8346 this.getRegionServerServices().getServerName())); 8347 } 8348 } catch (InterruptedException ie) { 8349 LOG.info("Interrupted while waiting for a lock"); 8350 InterruptedIOException iie = new InterruptedIOException(); 8351 iie.initCause(ie); 8352 throw iie; 8353 } 8354 } 8355 8356 /** 8357 * Calls sync with the given transaction ID 8358 * @param txid should sync up to which transaction 8359 * @throws IOException If anything goes wrong with DFS 8360 */ 8361 private void sync(long txid, Durability durability) throws IOException { 8362 if (this.getRegionInfo().isMetaRegion()) { 8363 this.wal.sync(txid); 8364 } else { 8365 switch(durability) { 8366 case USE_DEFAULT: 8367 // do what table defaults to 8368 if (shouldSyncWAL()) { 8369 this.wal.sync(txid); 8370 } 8371 break; 8372 case SKIP_WAL: 8373 // nothing do to 8374 break; 8375 case ASYNC_WAL: 8376 // nothing do to 8377 break; 8378 case SYNC_WAL: 8379 case FSYNC_WAL: 8380 // sync the WAL edit (SYNC and FSYNC treated the same for now) 8381 this.wal.sync(txid); 8382 break; 8383 default: 8384 throw new RuntimeException("Unknown durability " + durability); 8385 } 8386 } 8387 } 8388 8389 /** 8390 * Check whether we should sync the wal from the table's durability settings 8391 */ 8392 private boolean shouldSyncWAL() { 8393 return regionDurability.ordinal() > Durability.ASYNC_WAL.ordinal(); 8394 } 8395 8396 /** 8397 * A mocked list implementation - discards all updates. 8398 */ 8399 private static final List<Cell> MOCKED_LIST = new AbstractList<Cell>() { 8400 8401 @Override 8402 public void add(int index, Cell element) { 8403 // do nothing 8404 } 8405 8406 @Override 8407 public boolean addAll(int index, Collection<? extends Cell> c) { 8408 return false; // this list is never changed as a result of an update 8409 } 8410 8411 @Override 8412 public KeyValue get(int index) { 8413 throw new UnsupportedOperationException(); 8414 } 8415 8416 @Override 8417 public int size() { 8418 return 0; 8419 } 8420 }; 8421 8422 /** @return the latest sequence number that was read from storage when this region was opened */ 8423 public long getOpenSeqNum() { 8424 return this.openSeqNum; 8425 } 8426 8427 @Override 8428 public Map<byte[], Long> getMaxStoreSeqId() { 8429 return this.maxSeqIdInStores; 8430 } 8431 8432 public long getOldestSeqIdOfStore(byte[] familyName) { 8433 return wal.getEarliestMemStoreSeqNum(getRegionInfo().getEncodedNameAsBytes(), familyName); 8434 } 8435 8436 @Override 8437 public CompactionState getCompactionState() { 8438 boolean hasMajor = majorInProgress.get() > 0, hasMinor = minorInProgress.get() > 0; 8439 return (hasMajor ? (hasMinor ? CompactionState.MAJOR_AND_MINOR : CompactionState.MAJOR) 8440 : (hasMinor ? CompactionState.MINOR : CompactionState.NONE)); 8441 } 8442 8443 public void reportCompactionRequestStart(boolean isMajor){ 8444 (isMajor ? majorInProgress : minorInProgress).incrementAndGet(); 8445 } 8446 8447 public void reportCompactionRequestEnd(boolean isMajor, int numFiles, long filesSizeCompacted) { 8448 int newValue = (isMajor ? majorInProgress : minorInProgress).decrementAndGet(); 8449 8450 // metrics 8451 compactionsFinished.increment(); 8452 compactionNumFilesCompacted.add(numFiles); 8453 compactionNumBytesCompacted.add(filesSizeCompacted); 8454 8455 assert newValue >= 0; 8456 } 8457 8458 public void reportCompactionRequestFailure() { 8459 compactionsFailed.increment(); 8460 } 8461 8462 public void incrementCompactionsQueuedCount() { 8463 compactionsQueued.increment(); 8464 } 8465 8466 public void decrementCompactionsQueuedCount() { 8467 compactionsQueued.decrement(); 8468 } 8469 8470 public void incrementFlushesQueuedCount() { 8471 flushesQueued.increment(); 8472 } 8473 8474 @VisibleForTesting 8475 public long getReadPoint() { 8476 return getReadPoint(IsolationLevel.READ_COMMITTED); 8477 } 8478 8479 /** 8480 * {@inheritDoc} 8481 */ 8482 @Override 8483 public void onConfigurationChange(Configuration conf) { 8484 // Do nothing for now. 8485 } 8486 8487 /** 8488 * {@inheritDoc} 8489 */ 8490 @Override 8491 public void registerChildren(ConfigurationManager manager) { 8492 configurationManager = Optional.of(manager); 8493 stores.values().forEach(manager::registerObserver); 8494 } 8495 8496 /** 8497 * {@inheritDoc} 8498 */ 8499 @Override 8500 public void deregisterChildren(ConfigurationManager manager) { 8501 stores.values().forEach(configurationManager.get()::deregisterObserver); 8502 } 8503 8504 @Override 8505 public CellComparator getCellComparator() { 8506 return this.getRegionInfo().isMetaRegion() ? CellComparatorImpl.META_COMPARATOR 8507 : CellComparatorImpl.COMPARATOR; 8508 } 8509 8510 public long getMemStoreFlushSize() { 8511 return this.memstoreFlushSize; 8512 } 8513 8514 8515 //// method for debugging tests 8516 void throwException(String title, String regionName) { 8517 StringBuilder buf = new StringBuilder(); 8518 buf.append(title + ", "); 8519 buf.append(getRegionInfo().toString()); 8520 buf.append(getRegionInfo().isMetaRegion() ? " meta region " : " "); 8521 buf.append("stores: "); 8522 for (HStore s : stores.values()) { 8523 buf.append(s.getColumnFamilyDescriptor().getNameAsString()); 8524 buf.append(" size: "); 8525 buf.append(s.getMemStoreSize().getDataSize()); 8526 buf.append(" "); 8527 } 8528 buf.append("end-of-stores"); 8529 buf.append(", memstore size "); 8530 buf.append(getMemStoreDataSize()); 8531 if (getRegionInfo().getRegionNameAsString().startsWith(regionName)) { 8532 throw new RuntimeException(buf.toString()); 8533 } 8534 } 8535 8536 @Override 8537 public void requestCompaction(String why, int priority, boolean major, 8538 CompactionLifeCycleTracker tracker) throws IOException { 8539 if (major) { 8540 stores.values().forEach(HStore::triggerMajorCompaction); 8541 } 8542 rsServices.getCompactionRequestor().requestCompaction(this, why, priority, tracker, 8543 RpcServer.getRequestUser().orElse(null)); 8544 } 8545 8546 @Override 8547 public void requestCompaction(byte[] family, String why, int priority, boolean major, 8548 CompactionLifeCycleTracker tracker) throws IOException { 8549 HStore store = stores.get(family); 8550 if (store == null) { 8551 throw new NoSuchColumnFamilyException("column family " + Bytes.toString(family) + 8552 " does not exist in region " + getRegionInfo().getRegionNameAsString()); 8553 } 8554 if (major) { 8555 store.triggerMajorCompaction(); 8556 } 8557 rsServices.getCompactionRequestor().requestCompaction(this, store, why, priority, tracker, 8558 RpcServer.getRequestUser().orElse(null)); 8559 } 8560 8561 private void requestFlushIfNeeded() throws RegionTooBusyException { 8562 if(isFlushSize(this.memStoreSizing.getMemStoreSize())) { 8563 requestFlush(); 8564 } 8565 } 8566 8567 private void requestFlush() { 8568 if (this.rsServices == null) { 8569 return; 8570 } 8571 requestFlush0(FlushLifeCycleTracker.DUMMY); 8572 } 8573 8574 private void requestFlush0(FlushLifeCycleTracker tracker) { 8575 boolean shouldFlush = false; 8576 synchronized (writestate) { 8577 if (!this.writestate.isFlushRequested()) { 8578 shouldFlush = true; 8579 writestate.flushRequested = true; 8580 } 8581 } 8582 if (shouldFlush) { 8583 // Make request outside of synchronize block; HBASE-818. 8584 this.rsServices.getFlushRequester().requestFlush(this, false, tracker); 8585 if (LOG.isDebugEnabled()) { 8586 LOG.debug("Flush requested on " + this.getRegionInfo().getEncodedName()); 8587 } 8588 } else { 8589 tracker.notExecuted("Flush already requested on " + this); 8590 } 8591 } 8592 8593 @Override 8594 public void requestFlush(FlushLifeCycleTracker tracker) throws IOException { 8595 requestFlush0(tracker); 8596 } 8597 8598 /** 8599 * This method modifies the region's configuration in order to inject replication-related 8600 * features 8601 * @param conf region configurations 8602 */ 8603 static void decorateRegionConfiguration(Configuration conf) { 8604 if (ReplicationUtils.isReplicationForBulkLoadDataEnabled(conf)) { 8605 String plugins = conf.get(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,""); 8606 String replicationCoprocessorClass = ReplicationObserver.class.getCanonicalName(); 8607 if (!plugins.contains(replicationCoprocessorClass)) { 8608 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, 8609 (plugins.equals("") ? "" : (plugins + ",")) + replicationCoprocessorClass); 8610 } 8611 } 8612 } 8613}