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