001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.store.kahadb.disk.index; 018 019import java.io.DataInput; 020import java.io.DataOutput; 021import java.io.IOException; 022import java.util.Iterator; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.concurrent.atomic.AtomicBoolean; 026 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.apache.activemq.store.kahadb.disk.page.Page; 030import org.apache.activemq.store.kahadb.disk.page.PageFile; 031import org.apache.activemq.store.kahadb.disk.page.Transaction; 032import org.apache.activemq.store.kahadb.disk.util.Marshaller; 033import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller; 034 035/** 036 * BTree implementation 037 * 038 * 039 */ 040public class HashIndex<Key,Value> implements Index<Key,Value> { 041 042 public static final int CLOSED_STATE = 1; 043 public static final int OPEN_STATE = 2; 044 045 046 private static final Logger LOG = LoggerFactory.getLogger(HashIndex.class); 047 048 public static final int DEFAULT_BIN_CAPACITY; 049 public static final int DEFAULT_MAXIMUM_BIN_CAPACITY; 050 public static final int DEFAULT_MINIMUM_BIN_CAPACITY; 051 public static final int DEFAULT_LOAD_FACTOR; 052 053 static { 054 DEFAULT_BIN_CAPACITY = Integer.parseInt(System.getProperty("defaultBinSize", "1024")); 055 DEFAULT_MAXIMUM_BIN_CAPACITY = Integer.parseInt(System.getProperty("maximumCapacity", "16384")); 056 DEFAULT_MINIMUM_BIN_CAPACITY = Integer.parseInt(System.getProperty("minimumCapacity", "16")); 057 DEFAULT_LOAD_FACTOR = Integer.parseInt(System.getProperty("defaultLoadFactor", "75")); 058 } 059 060 private AtomicBoolean loaded = new AtomicBoolean(); 061 062 063 private int increaseThreshold; 064 private int decreaseThreshold; 065 066 // Where the bin page array starts at. 067 private int maximumBinCapacity = DEFAULT_MAXIMUM_BIN_CAPACITY; 068 private int minimumBinCapacity = DEFAULT_MINIMUM_BIN_CAPACITY; 069 070 071 072 // Once binsActive/binCapacity reaches the loadFactor, then we need to 073 // increase the capacity 074 private int loadFactor = DEFAULT_LOAD_FACTOR; 075 076 private PageFile pageFile; 077 // This page holds the index metadata. 078 private long pageId; 079 080 static class Metadata { 081 082 private Page<Metadata> page; 083 084 // When the index is initializing or resizing.. state changes so that 085 // on failure it can be properly recovered. 086 private int state; 087 private long binPageId; 088 private int binCapacity = DEFAULT_BIN_CAPACITY; 089 private int binsActive; 090 private int size; 091 092 093 public void read(DataInput is) throws IOException { 094 state = is.readInt(); 095 binPageId = is.readLong(); 096 binCapacity = is.readInt(); 097 size = is.readInt(); 098 binsActive = is.readInt(); 099 } 100 public void write(DataOutput os) throws IOException { 101 os.writeInt(state); 102 os.writeLong(binPageId); 103 os.writeInt(binCapacity); 104 os.writeInt(size); 105 os.writeInt(binsActive); 106 } 107 108 static class Marshaller extends VariableMarshaller<Metadata> { 109 public Metadata readPayload(DataInput dataIn) throws IOException { 110 Metadata rc = new Metadata(); 111 rc.read(dataIn); 112 return rc; 113 } 114 115 public void writePayload(Metadata object, DataOutput dataOut) throws IOException { 116 object.write(dataOut); 117 } 118 } 119 } 120 121 private Metadata metadata = new Metadata(); 122 123 private Metadata.Marshaller metadataMarshaller = new Metadata.Marshaller(); 124 private HashBin.Marshaller<Key,Value> hashBinMarshaller = new HashBin.Marshaller<Key,Value>(this); 125 private volatile Marshaller<Key> keyMarshaller; 126 private Marshaller<Value> valueMarshaller; 127 128 129 /** 130 * Constructor 131 * 132 * @param pageFile 133 * @param pageId 134 * @throws IOException 135 */ 136 public HashIndex(PageFile pageFile, long pageId) throws IOException { 137 this.pageFile = pageFile; 138 this.pageId = pageId; 139 } 140 141 public synchronized void load(Transaction tx) throws IOException { 142 if (loaded.compareAndSet(false, true)) { 143 final Page<Metadata> metadataPage = tx.load(pageId, metadataMarshaller); 144 // Is this a brand new index? 145 if (metadataPage.getType() == Page.PAGE_FREE_TYPE) { 146 // We need to create the pages for the bins 147 Page binPage = tx.allocate(metadata.binCapacity); 148 metadata.binPageId = binPage.getPageId(); 149 metadata.page = metadataPage; 150 metadataPage.set(metadata); 151 clear(tx); 152 153 // If failure happens now we can continue initializing the 154 // the hash bins... 155 } else { 156 157 metadata = metadataPage.get(); 158 metadata.page = metadataPage; 159 160 // If we did not have a clean shutdown... 161 if (metadata.state == OPEN_STATE ) { 162 // Figure out the size and the # of bins that are 163 // active. Yeah This loads the first page of every bin. :( 164 // We might want to put this in the metadata page, but 165 // then that page would be getting updated on every write. 166 metadata.size = 0; 167 for (int i = 0; i < metadata.binCapacity; i++) { 168 int t = sizeOfBin(tx, i); 169 if (t > 0) { 170 metadata.binsActive++; 171 } 172 metadata.size += t; 173 } 174 } 175 } 176 177 calcThresholds(); 178 179 metadata.state = OPEN_STATE; 180 tx.store(metadataPage, metadataMarshaller, true); 181 182 LOG.debug("HashIndex loaded. Using "+metadata.binCapacity+" bins starting at page "+metadata.binPageId); 183 } 184 } 185 186 public synchronized void unload(Transaction tx) throws IOException { 187 if (loaded.compareAndSet(true, false)) { 188 metadata.state = CLOSED_STATE; 189 tx.store(metadata.page, metadataMarshaller, true); 190 } 191 } 192 193 private int sizeOfBin(Transaction tx, int index) throws IOException { 194 return getBin(tx, index).size(); 195 } 196 197 public synchronized Value get(Transaction tx, Key key) throws IOException { 198 assertLoaded(); 199 return getBin(tx, key).get(key); 200 } 201 202 public synchronized boolean containsKey(Transaction tx, Key key) throws IOException { 203 assertLoaded(); 204 return getBin(tx, key).containsKey(key); 205 } 206 207 synchronized public Value put(Transaction tx, Key key, Value value) throws IOException { 208 assertLoaded(); 209 HashBin<Key,Value> bin = getBin(tx, key); 210 211 int originalSize = bin.size(); 212 Value result = bin.put(key,value); 213 store(tx, bin); 214 215 int newSize = bin.size(); 216 217 if (newSize != originalSize) { 218 metadata.size++; 219 if (newSize == 1) { 220 metadata.binsActive++; 221 } 222 } 223 224 if (metadata.binsActive >= this.increaseThreshold) { 225 newSize = Math.min(maximumBinCapacity, metadata.binCapacity*2); 226 if(metadata.binCapacity!=newSize) { 227 resize(tx, newSize); 228 } 229 } 230 return result; 231 } 232 233 synchronized public Value remove(Transaction tx, Key key) throws IOException { 234 assertLoaded(); 235 236 HashBin<Key,Value> bin = getBin(tx, key); 237 int originalSize = bin.size(); 238 Value result = bin.remove(key); 239 int newSize = bin.size(); 240 241 if (newSize != originalSize) { 242 store(tx, bin); 243 244 metadata.size--; 245 if (newSize == 0) { 246 metadata.binsActive--; 247 } 248 } 249 250 if (metadata.binsActive <= this.decreaseThreshold) { 251 newSize = Math.max(minimumBinCapacity, metadata.binCapacity/2); 252 if(metadata.binCapacity!=newSize) { 253 resize(tx, newSize); 254 } 255 } 256 return result; 257 } 258 259 260 public synchronized void clear(Transaction tx) throws IOException { 261 assertLoaded(); 262 for (int i = 0; i < metadata.binCapacity; i++) { 263 long pageId = metadata.binPageId + i; 264 clearBinAtPage(tx, pageId); 265 } 266 metadata.size = 0; 267 metadata.binsActive = 0; 268 } 269 270 public Iterator<Entry<Key, Value>> iterator(Transaction tx) throws IOException, UnsupportedOperationException { 271 throw new UnsupportedOperationException(); 272 } 273 274 275 /** 276 * @param tx 277 * @param pageId 278 * @throws IOException 279 */ 280 private void clearBinAtPage(Transaction tx, long pageId) throws IOException { 281 Page<HashBin<Key,Value>> page = tx.load(pageId, null); 282 HashBin<Key, Value> bin = new HashBin<Key,Value>(); 283 bin.setPage(page); 284 page.set(bin); 285 store(tx, bin); 286 } 287 288 public String toString() { 289 String str = "HashIndex" + System.identityHashCode(this) + ": " + pageFile; 290 return str; 291 } 292 293 // ///////////////////////////////////////////////////////////////// 294 // Implementation Methods 295 // ///////////////////////////////////////////////////////////////// 296 297 private void assertLoaded() throws IllegalStateException { 298 if( !loaded.get() ) { 299 throw new IllegalStateException("The HashIndex is not loaded"); 300 } 301 } 302 303 public synchronized void store(Transaction tx, HashBin<Key,Value> bin) throws IOException { 304 tx.store(bin.getPage(), hashBinMarshaller, true); 305 } 306 307 // While resizing, the following contains the new resize data. 308 309 private void resize(Transaction tx, final int newSize) throws IOException { 310 LOG.debug("Resizing to: "+newSize); 311 312 int resizeCapacity = newSize; 313 long resizePageId = tx.allocate(resizeCapacity).getPageId(); 314 315 // In Phase 1 we copy the data to the new bins.. 316 // Initialize the bins.. 317 for (int i = 0; i < resizeCapacity; i++) { 318 long pageId = resizePageId + i; 319 clearBinAtPage(tx, pageId); 320 } 321 322 metadata.binsActive = 0; 323 // Copy the data from the old bins to the new bins. 324 for (int i = 0; i < metadata.binCapacity; i++) { 325 326 HashBin<Key,Value> bin = getBin(tx, i); 327 for (Map.Entry<Key, Value> entry : bin.getAll(tx).entrySet()) { 328 HashBin<Key,Value> resizeBin = getBin(tx, entry.getKey(), resizePageId, resizeCapacity); 329 resizeBin.put(entry.getKey(), entry.getValue()); 330 store(tx, resizeBin); 331 if( resizeBin.size() == 1) { 332 metadata.binsActive++; 333 } 334 } 335 } 336 337 // In phase 2 we free the old bins and switch the the new bins. 338 tx.free(metadata.binPageId, metadata.binCapacity); 339 340 metadata.binCapacity = resizeCapacity; 341 metadata.binPageId = resizePageId; 342 metadata.state = OPEN_STATE; 343 tx.store(metadata.page, metadataMarshaller, true); 344 calcThresholds(); 345 346 LOG.debug("Resizing done. New bins start at: "+metadata.binPageId); 347 resizeCapacity=0; 348 resizePageId=0; 349 } 350 351 private void calcThresholds() { 352 increaseThreshold = (metadata.binCapacity * loadFactor)/100; 353 decreaseThreshold = (metadata.binCapacity * loadFactor * loadFactor ) / 20000; 354 } 355 356 private HashBin<Key,Value> getBin(Transaction tx, Key key) throws IOException { 357 return getBin(tx, key, metadata.binPageId, metadata.binCapacity); 358 } 359 360 private HashBin<Key,Value> getBin(Transaction tx, int i) throws IOException { 361 return getBin(tx, i, metadata.binPageId); 362 } 363 364 private HashBin<Key,Value> getBin(Transaction tx, Key key, long basePage, int capacity) throws IOException { 365 int i = indexFor(key, capacity); 366 return getBin(tx, i, basePage); 367 } 368 369 private HashBin<Key,Value> getBin(Transaction tx, int i, long basePage) throws IOException { 370 Page<HashBin<Key, Value>> page = tx.load(basePage + i, hashBinMarshaller); 371 HashBin<Key, Value> rc = page.get(); 372 rc.setPage(page); 373 return rc; 374 } 375 376 int indexFor(Key x, int length) { 377 return Math.abs(x.hashCode()%length); 378 } 379 380 // ///////////////////////////////////////////////////////////////// 381 // Property Accessors 382 // ///////////////////////////////////////////////////////////////// 383 384 public Marshaller<Key> getKeyMarshaller() { 385 return keyMarshaller; 386 } 387 388 /** 389 * Set the marshaller for key objects 390 * 391 * @param marshaller 392 */ 393 public void setKeyMarshaller(Marshaller<Key> marshaller) { 394 this.keyMarshaller = marshaller; 395 } 396 397 public Marshaller<Value> getValueMarshaller() { 398 return valueMarshaller; 399 } 400 401 /** 402 * Set the marshaller for value objects 403 * 404 * @param valueMarshaller 405 */ 406 public void setValueMarshaller(Marshaller<Value> valueMarshaller) { 407 this.valueMarshaller = valueMarshaller; 408 } 409 410 /** 411 * @return number of bins in the index 412 */ 413 public int getBinCapacity() { 414 return metadata.binCapacity; 415 } 416 417 /** 418 * @param binCapacity 419 */ 420 public void setBinCapacity(int binCapacity) { 421 if (loaded.get() && binCapacity != metadata.binCapacity) { 422 throw new RuntimeException("Pages already loaded - can't reset bin capacity"); 423 } 424 metadata.binCapacity = binCapacity; 425 } 426 427 public boolean isTransient() { 428 return false; 429 } 430 431 /** 432 * @return the loadFactor 433 */ 434 public int getLoadFactor() { 435 return loadFactor; 436 } 437 438 /** 439 * @param loadFactor the loadFactor to set 440 */ 441 public void setLoadFactor(int loadFactor) { 442 this.loadFactor = loadFactor; 443 } 444 445 /** 446 * @return the maximumCapacity 447 */ 448 public int setMaximumBinCapacity() { 449 return maximumBinCapacity; 450 } 451 452 /** 453 * @param maximumCapacity the maximumCapacity to set 454 */ 455 public void setMaximumBinCapacity(int maximumCapacity) { 456 this.maximumBinCapacity = maximumCapacity; 457 } 458 459 public synchronized int size(Transaction tx) { 460 return metadata.size; 461 } 462 463 public synchronized int getActiveBins() { 464 return metadata.binsActive; 465 } 466 467 public long getBinPageId() { 468 return metadata.binPageId; 469 } 470 471 public PageFile getPageFile() { 472 return pageFile; 473 } 474 475 public int getBinsActive() { 476 return metadata.binsActive; 477 } 478 479}