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