001/* 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package org.apache.hadoop.hbase.regionserver; 020 021import java.util.ArrayList; 022import java.util.Iterator; 023import java.util.LinkedList; 024import java.util.List; 025 026import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 027import org.apache.hadoop.hbase.util.Bytes; 028import org.apache.hadoop.hbase.util.ClassSize; 029import org.apache.yetus.audience.InterfaceAudience; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * The compaction pipeline of a {@link CompactingMemStore}, is a FIFO queue of segments. 035 * It supports pushing a segment at the head of the pipeline and removing a segment from the 036 * tail when it is flushed to disk. 037 * It also supports swap method to allow the in-memory compaction swap a subset of the segments 038 * at the tail of the pipeline with a new (compacted) one. This swap succeeds only if the version 039 * number passed with the list of segments to swap is the same as the current version of the 040 * pipeline. 041 * Essentially, there are two methods which can change the structure of the pipeline: pushHead() 042 * and swap(), the later is used both by a flush to disk and by an in-memory compaction. 043 * The pipeline version is updated by swap(); it allows to identify conflicting operations at the 044 * suffix of the pipeline. 045 * 046 * The synchronization model is copy-on-write. Methods which change the structure of the 047 * pipeline (pushHead() and swap()) apply their changes in the context of a lock. They also make 048 * a read-only copy of the pipeline's list. Read methods read from a read-only copy. If a read 049 * method accesses the read-only copy more than once it makes a local copy of it 050 * to ensure it accesses the same copy. 051 * 052 * The methods getVersionedList(), getVersionedTail(), and flattenOneSegment() are also 053 * protected by a lock since they need to have a consistent (atomic) view of the pipeline list 054 * and version number. 055 */ 056@InterfaceAudience.Private 057public class CompactionPipeline { 058 private static final Logger LOG = LoggerFactory.getLogger(CompactionPipeline.class); 059 060 public final static long FIXED_OVERHEAD = ClassSize 061 .align(ClassSize.OBJECT + (3 * ClassSize.REFERENCE) + Bytes.SIZEOF_LONG); 062 public final static long DEEP_OVERHEAD = FIXED_OVERHEAD + (2 * ClassSize.LINKEDLIST); 063 064 private final RegionServicesForStores region; 065 private final LinkedList<ImmutableSegment> pipeline = new LinkedList<>(); 066 // The list is volatile to avoid reading a new allocated reference before the c'tor is executed 067 private volatile LinkedList<ImmutableSegment> readOnlyCopy = new LinkedList<>(); 068 // Version is volatile to ensure it is atomically read when not using a lock 069 private volatile long version = 0; 070 071 public CompactionPipeline(RegionServicesForStores region) { 072 this.region = region; 073 } 074 075 public boolean pushHead(MutableSegment segment) { 076 // Record the ImmutableSegment' heap overhead when initialing 077 MemStoreSizing memstoreAccounting = new NonThreadSafeMemStoreSizing(); 078 ImmutableSegment immutableSegment = SegmentFactory.instance(). 079 createImmutableSegment(segment, memstoreAccounting); 080 if (region != null) { 081 region.addMemStoreSize(memstoreAccounting.getDataSize(), 082 memstoreAccounting.getHeapSize(), 083 memstoreAccounting.getOffHeapSize()); 084 } 085 synchronized (pipeline){ 086 boolean res = addFirst(immutableSegment); 087 readOnlyCopy = new LinkedList<>(pipeline); 088 return res; 089 } 090 } 091 092 public VersionedSegmentsList getVersionedList() { 093 synchronized (pipeline){ 094 return new VersionedSegmentsList(readOnlyCopy, version); 095 } 096 } 097 098 public VersionedSegmentsList getVersionedTail() { 099 synchronized (pipeline){ 100 List<ImmutableSegment> segmentList = new ArrayList<>(); 101 if(!pipeline.isEmpty()) { 102 segmentList.add(0, pipeline.getLast()); 103 } 104 return new VersionedSegmentsList(segmentList, version); 105 } 106 } 107 108 /** 109 * Swaps the versioned list at the tail of the pipeline with a new segment. 110 * Swapping only if there were no changes to the suffix of the list since the version list was 111 * created. 112 * @param versionedList suffix of the pipeline to be replaced can be tail or all the pipeline 113 * @param segment new segment to replace the suffix. Can be null if the suffix just needs to be 114 * removed. 115 * @param closeSuffix whether to close the suffix (to release memory), as part of swapping it out 116 * During index merge op this will be false and for compaction it will be true. 117 * @param updateRegionSize whether to update the region size. Update the region size, 118 * when the pipeline is swapped as part of in-memory-flush and 119 * further merge/compaction. Don't update the region size when the 120 * swap is result of the snapshot (flush-to-disk). 121 * @return true iff swapped tail with new segment 122 */ 123 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="VO_VOLATILE_INCREMENT", 124 justification="Increment is done under a synchronize block so safe") 125 public boolean swap(VersionedSegmentsList versionedList, ImmutableSegment segment, 126 boolean closeSuffix, boolean updateRegionSize) { 127 if (versionedList.getVersion() != version) { 128 return false; 129 } 130 List<ImmutableSegment> suffix; 131 synchronized (pipeline){ 132 if(versionedList.getVersion() != version) { 133 return false; 134 } 135 suffix = versionedList.getStoreSegments(); 136 LOG.debug("Swapping pipeline suffix; before={}, new segment={}", 137 versionedList.getStoreSegments().size(), segment); 138 swapSuffix(suffix, segment, closeSuffix); 139 readOnlyCopy = new LinkedList<>(pipeline); 140 version++; 141 } 142 if (updateRegionSize && region != null) { 143 // update the global memstore size counter 144 long suffixDataSize = getSegmentsKeySize(suffix); 145 long newDataSize = 0; 146 if(segment != null) { 147 newDataSize = segment.getDataSize(); 148 } 149 long dataSizeDelta = suffixDataSize - newDataSize; 150 long suffixHeapSize = getSegmentsHeapSize(suffix); 151 long suffixOffHeapSize = getSegmentsOffHeapSize(suffix); 152 long newHeapSize = 0; 153 long newOffHeapSize = 0; 154 if(segment != null) { 155 newHeapSize = segment.getHeapSize(); 156 newOffHeapSize = segment.getOffHeapSize(); 157 } 158 long offHeapSizeDelta = suffixOffHeapSize - newOffHeapSize; 159 long heapSizeDelta = suffixHeapSize - newHeapSize; 160 region.addMemStoreSize(-dataSizeDelta, -heapSizeDelta, -offHeapSizeDelta); 161 LOG.debug("Suffix data size={}, new segment data size={}, " 162 + "suffix heap size={}," + "new segment heap size={}" 163 + "suffix off heap size={}," + "new segment off heap size={}" 164 , suffixDataSize 165 , newDataSize 166 , suffixHeapSize 167 , newHeapSize 168 , suffixOffHeapSize 169 , newOffHeapSize); 170 } 171 return true; 172 } 173 174 private static long getSegmentsHeapSize(List<? extends Segment> list) { 175 long res = 0; 176 for (Segment segment : list) { 177 res += segment.getHeapSize(); 178 } 179 return res; 180 } 181 182 private static long getSegmentsOffHeapSize(List<? extends Segment> list) { 183 long res = 0; 184 for (Segment segment : list) { 185 res += segment.getOffHeapSize(); 186 } 187 return res; 188 } 189 190 private static long getSegmentsKeySize(List<? extends Segment> list) { 191 long res = 0; 192 for (Segment segment : list) { 193 res += segment.getDataSize(); 194 } 195 return res; 196 } 197 198 /** 199 * If the caller holds the current version, go over the the pipeline and try to flatten each 200 * segment. Flattening is replacing the ConcurrentSkipListMap based CellSet to CellArrayMap based. 201 * Flattening of the segment that initially is not based on ConcurrentSkipListMap has no effect. 202 * Return after one segment is successfully flatten. 203 * 204 * @return true iff a segment was successfully flattened 205 */ 206 public boolean flattenOneSegment(long requesterVersion, 207 CompactingMemStore.IndexType idxType, 208 MemStoreCompactionStrategy.Action action) { 209 210 if(requesterVersion != version) { 211 LOG.warn("Segment flattening failed, because versions do not match. Requester version: " 212 + requesterVersion + ", actual version: " + version); 213 return false; 214 } 215 216 synchronized (pipeline){ 217 if(requesterVersion != version) { 218 LOG.warn("Segment flattening failed, because versions do not match"); 219 return false; 220 } 221 int i = 0; 222 for (ImmutableSegment s : pipeline) { 223 if ( s.canBeFlattened() ) { 224 // size to be updated 225 MemStoreSizing newMemstoreAccounting = new NonThreadSafeMemStoreSizing(); 226 ImmutableSegment newS = SegmentFactory.instance().createImmutableSegmentByFlattening( 227 (CSLMImmutableSegment)s,idxType,newMemstoreAccounting,action); 228 replaceAtIndex(i,newS); 229 if(region != null) { 230 // Update the global memstore size counter upon flattening there is no change in the 231 // data size 232 MemStoreSize mss = newMemstoreAccounting.getMemStoreSize(); 233 Preconditions.checkArgument(mss.getDataSize() == 0, "Not zero!"); 234 region.addMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize()); 235 } 236 LOG.debug("Compaction pipeline segment {} flattened", s); 237 return true; 238 } 239 i++; 240 } 241 242 } 243 // do not update the global memstore size counter and do not increase the version, 244 // because all the cells remain in place 245 return false; 246 } 247 248 public boolean isEmpty() { 249 return readOnlyCopy.isEmpty(); 250 } 251 252 public List<? extends Segment> getSegments() { 253 return readOnlyCopy; 254 } 255 256 public long size() { 257 return readOnlyCopy.size(); 258 } 259 260 public long getMinSequenceId() { 261 long minSequenceId = Long.MAX_VALUE; 262 LinkedList<? extends Segment> localCopy = readOnlyCopy; 263 if (!localCopy.isEmpty()) { 264 minSequenceId = localCopy.getLast().getMinSequenceId(); 265 } 266 return minSequenceId; 267 } 268 269 public MemStoreSize getTailSize() { 270 LinkedList<? extends Segment> localCopy = readOnlyCopy; 271 return localCopy.isEmpty()? new MemStoreSize(): localCopy.peekLast().getMemStoreSize(); 272 } 273 274 public MemStoreSize getPipelineSize() { 275 MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing(); 276 LinkedList<? extends Segment> localCopy = readOnlyCopy; 277 for (Segment segment : localCopy) { 278 memStoreSizing.incMemStoreSize(segment.getMemStoreSize()); 279 } 280 return memStoreSizing.getMemStoreSize(); 281 } 282 283 private void swapSuffix(List<? extends Segment> suffix, ImmutableSegment segment, 284 boolean closeSegmentsInSuffix) { 285 pipeline.removeAll(suffix); 286 if(segment != null) pipeline.addLast(segment); 287 // During index merge we won't be closing the segments undergoing the merge. Segment#close() 288 // will release the MSLAB chunks to pool. But in case of index merge there wont be any data copy 289 // from old MSLABs. So the new cells in new segment also refers to same chunks. In case of data 290 // compaction, we would have copied the cells data from old MSLAB chunks into a new chunk 291 // created for the result segment. So we can release the chunks associated with the compacted 292 // segments. 293 if (closeSegmentsInSuffix) { 294 for (Segment itemInSuffix : suffix) { 295 itemInSuffix.close(); 296 } 297 } 298 } 299 300 // replacing one segment in the pipeline with a new one exactly at the same index 301 // need to be called only within synchronized block 302 private void replaceAtIndex(int idx, ImmutableSegment newSegment) { 303 pipeline.set(idx, newSegment); 304 readOnlyCopy = new LinkedList<>(pipeline); 305 } 306 307 public Segment getTail() { 308 List<? extends Segment> localCopy = getSegments(); 309 if(localCopy.isEmpty()) { 310 return null; 311 } 312 return localCopy.get(localCopy.size() - 1); 313 } 314 315 private boolean addFirst(ImmutableSegment segment) { 316 pipeline.addFirst(segment); 317 return true; 318 } 319 320 // debug method 321 private boolean validateSuffixList(LinkedList<ImmutableSegment> suffix) { 322 if(suffix.isEmpty()) { 323 // empty suffix is always valid 324 return true; 325 } 326 Iterator<ImmutableSegment> pipelineBackwardIterator = pipeline.descendingIterator(); 327 Iterator<ImmutableSegment> suffixBackwardIterator = suffix.descendingIterator(); 328 ImmutableSegment suffixCurrent; 329 ImmutableSegment pipelineCurrent; 330 for( ; suffixBackwardIterator.hasNext(); ) { 331 if(!pipelineBackwardIterator.hasNext()) { 332 // a suffix longer than pipeline is invalid 333 return false; 334 } 335 suffixCurrent = suffixBackwardIterator.next(); 336 pipelineCurrent = pipelineBackwardIterator.next(); 337 if(suffixCurrent != pipelineCurrent) { 338 // non-matching suffix 339 return false; 340 } 341 } 342 // suffix matches pipeline suffix 343 return true; 344 } 345 346}