1 2 12 package com.versant.core.storagemanager; 13 14 import com.versant.core.common.State; 15 import com.versant.core.metadata.FetchGroup; 16 import com.versant.core.metadata.ClassMetaData; 17 import com.versant.core.metadata.ModelMetaData; 18 import com.versant.core.common.OID; 19 import com.versant.core.common.*; 20 import com.versant.core.server.CachedQueryResult; 21 import com.versant.core.server.CompiledQuery; 22 import com.versant.core.metric.*; 23 24 import java.util.*; 25 import java.io.PrintStream ; 26 27 31 public final class LRUStorageCache implements StorageCache, HasMetrics { 32 33 private final Map stateMap; private final Map queryMap; 36 private ModelMetaData jmd; 37 private boolean enabled = true; 38 private boolean queryCacheEnabled = true; 39 private int maxObjects = 10000; 40 private int maxQueries = 1000; 41 private int objectCount; 42 private int queryCount; 43 44 private long now; 45 private long evictAllTimestamp; 46 47 private Tx txTail, txHead; 49 50 private StateEntry stateTail; private StateEntry stateHead; 54 private StateEntry[] classStateHead; 56 57 private long[] classEvictionTimestamp; 59 60 private QueryEntry queryHead; 62 private QueryEntry queryTail; 63 64 private QueryEntryNode[] classQueryHead; 66 67 private int hitCount; 68 private int missCount; 69 private int queryHitCount; 70 private int queryMissCount; 71 72 private static final Tx DISABLED_TX = new Tx(0); 73 74 private static final String CAT_CACHE = "L2Cache"; 75 76 private final BaseMetric metricCacheSize = 77 new BaseMetric("CacheSize", "Cache Size", CAT_CACHE, 78 "Number of objects in the level 2 cache", 0, 79 Metric.CALC_AVERAGE); 80 private final BaseMetric metricCacheMaxSize = 81 new BaseMetric("CacheMaxSize", "Cache Max Size", CAT_CACHE, 82 "Max number of objects to store in the level 2 cache", 0, 83 Metric.CALC_AVERAGE); 84 private final BaseMetric metricCacheHit = 85 new BaseMetric("CacheHit", "Cache Hit", CAT_CACHE, 86 "Number of times data was found in cache", 3, 87 Metric.CALC_DELTA_PER_SECOND); 88 private final BaseMetric metricCacheMiss = 89 new BaseMetric("CacheMiss", "Cache Miss", CAT_CACHE, 90 "Number of times data was not found in cache", 3, 91 Metric.CALC_DELTA_PER_SECOND); 92 93 private final BaseMetric metricQueryCacheSize = 94 new BaseMetric("QueryCacheSize", "Query Cache Size", CAT_CACHE, 95 "Number of queries in the cache", 0, Metric.CALC_AVERAGE); 96 private final BaseMetric metricQueryCacheMaxSize = 97 new BaseMetric("QueryCacheMaxSize", "Query Cache Max Size", 98 CAT_CACHE, 99 "Max number of queries to store in the cache", 0, 100 Metric.CALC_AVERAGE); 101 private final BaseMetric metricQueryCacheHit = 102 new BaseMetric("QueryCacheHit", "Query Cache Hit", CAT_CACHE, 103 "Number of times query results were found in cache", 3, 104 Metric.CALC_DELTA_PER_SECOND); 105 private final BaseMetric metricQueryCacheMiss = 106 new BaseMetric("QueryCacheMiss", "Query Cache Miss", CAT_CACHE, 107 "Number of times query results were not found in cache", 3, 108 Metric.CALC_DELTA_PER_SECOND); 109 110 private final PercentageMetric metricCacheFullPercent = 111 new PercentageMetric("CacheFullPercent", "Cache Full %", CAT_CACHE, 112 "Number of objects in the cache as a percentage of the max", 113 metricCacheSize, metricCacheMaxSize); 114 private final PercentageSumMetric metricCacheHitPercent = 115 new PercentageSumMetric("CacheHitPercent", "Cache Hit %", CAT_CACHE, 116 "Cache hit rate percentage", 117 metricCacheHit, metricCacheMiss); 118 private final PercentageMetric metricQueryCacheFullPercent = 119 new PercentageMetric("QueryCacheFullPercent", "Query Cache Full %", CAT_CACHE, 120 "Number of queries in the cache as a percentage of the max", 121 metricQueryCacheSize, metricQueryCacheMaxSize); 122 private final PercentageSumMetric metricQueryCacheHitPercent = 123 new PercentageSumMetric("QueryCacheHitPercent", "Query Cache Hit %", CAT_CACHE, 124 "Query Cache hit rate percentage", 125 metricQueryCacheHit, metricQueryCacheMiss); 126 127 public LRUStorageCache() { 128 stateMap = new HashMap(); 129 queryMap = new HashMap(); 130 } 131 132 public void setJDOMetaData(ModelMetaData jmd) { 133 this.jmd = jmd; 134 int n = jmd.classes.length; 135 classStateHead = new StateEntry[n]; 136 classEvictionTimestamp = new long[n]; 137 classQueryHead = new QueryEntryNode[n]; 138 } 139 140 public boolean isEnabled() { 141 return enabled; 142 } 143 144 public synchronized void setEnabled(boolean enabled) { 145 this.enabled = enabled; 146 if (!enabled) { 147 evictAll(null); 148 } 149 } 150 151 public boolean isQueryCacheEnabled() { 152 return queryCacheEnabled; 153 } 154 155 public void setQueryCacheEnabled(boolean queryCacheEnabled) { 156 this.queryCacheEnabled = queryCacheEnabled; 157 } 158 159 public synchronized Object beginTx() { 160 if (!enabled) { 161 return DISABLED_TX; 162 } 163 Tx tx = new Tx(++now); 164 if (txHead == null) { 165 txTail = txHead = tx; 166 } else { 167 txHead.next = tx; 168 txHead = tx; 169 } 170 return tx; 171 } 172 173 public synchronized void endTx(Object o) { 174 if (o == DISABLED_TX) { 175 return; 176 } 177 Tx tx = (Tx)o; 178 tx.finished = true; 179 for (; txTail.finished; ) { 182 for (int i = txTail.evictedCount - 1; i >= 0; i--) { 183 OID oid = txTail.evicted[i]; 184 StateEntry e = (StateEntry)stateMap.get(oid); 185 if (e != null && e.timestamp == tx.started) { 186 stateMap.remove(oid); 187 } 188 } 189 if (txTail.next == null) { 190 txTail = txHead = null; 191 break; 192 } 193 txTail = txTail.next; 194 } 195 } 196 197 public synchronized State getState(OID oid, FetchGroup fetchGroup) { 198 if (!enabled) { 199 return null; 200 } 201 StateEntry e = (StateEntry)stateMap.get(oid); 202 if (e != null && e.state != null) { 203 removeFromStateList(e); 204 addToHeadOfStateList(e); 205 if (fetchGroup == null || e.state.containsFetchGroup(fetchGroup)) { 206 ++hitCount; 207 return e.state.getCopy(); 208 } 209 } 210 ++missCount; 211 return null; 212 } 213 214 public synchronized boolean contains(OID oid) { 215 if (!enabled) { 216 return false; 217 } 218 StateEntry e = (StateEntry)stateMap.get(oid); 219 return e != null && e.state != null; 220 } 221 222 public synchronized CachedQueryResult getQueryResult(CompiledQuery cq, 223 Object [] params) { 224 if (!enabled || !queryCacheEnabled) { 225 return null; 226 } 227 QueryEntry e = (QueryEntry)queryMap.get(new QueryEntry(cq, params)); 228 if (e != null && e.res != null) { 229 ++queryHitCount; 230 removeFromQueryList(e); 231 addToHeadOfQueryList(e); 232 return e.res; 233 } else { 234 ++queryMissCount; 235 return null; 236 } 237 } 238 239 public synchronized int getQueryResultCount(CompiledQuery cq, 240 Object [] params) { 241 if (!enabled || !queryCacheEnabled) { 242 return -1; 243 } 244 QueryEntry e = (QueryEntry)queryMap.get(new QueryEntry(cq, params)); 245 if (e != null) { 246 ++queryHitCount; 247 removeFromQueryList(e); 248 addToHeadOfQueryList(e); 249 return e.res != null ? e.res.results.size() : e.resultCount; 250 } else { 251 ++queryMissCount; 252 return -1; 253 } 254 } 255 256 public synchronized void evict(Object tx, CompiledQuery cq, 257 Object [] params) { 258 if (!enabled) { 259 return; 260 } 261 QueryEntry e = (QueryEntry)queryMap.get(new QueryEntry(cq, params)); 262 if (e != null) { 263 removeFromQueryList(e); 264 removeFromClassQueryLists(e); 265 queryMap.remove(e); 266 } 267 } 268 269 public synchronized void add(Object otx, StatesReturned container) { 270 if (!enabled) { 271 return; 272 } 273 Tx tx = (Tx)otx; 274 if (tx.started <= evictAllTimestamp) { 275 return; 278 } 279 for (Iterator i = container.iterator(); i.hasNext(); ) { 280 EntrySet.Entry me = (EntrySet.Entry)i.next(); 281 OID oid = (OID)me.getKey(); 282 State state = (State)me.getValue(); 283 if (!state.isCacheble() 284 || tx.started <= classEvictionTimestamp[state.getClassIndex()]) { 285 continue; } 287 StateEntry e = (StateEntry)stateMap.get(oid); 288 if (e != null) { 289 if (tx.started <= e.timestamp || e.state == null) { 290 continue; } 292 e.state.updateFrom(state); 293 e.timestamp = now; 294 } else { 295 e = new StateEntry(now, oid, state); 296 stateMap.put(oid, e); 297 addToHeadOfStateList(e); 298 addToClassStateList(e); 299 discardExcessStates(); 300 } 301 } 302 } 303 304 public synchronized void add(Object tx, CompiledQuery cq, Object [] params, 305 CachedQueryResult queryData) { 306 if (!enabled) { 307 return; 308 } 309 addImp((Tx)tx, cq, params, queryData, 310 queryData.results == null ? 0 : queryData.results.size()); 311 } 312 313 private void addImp(Tx tx, CompiledQuery cq, Object [] params, 314 CachedQueryResult queryData, int resultCount) { 315 if (tx.started <= evictAllTimestamp) { 316 return; 319 } 320 QueryEntry e = new QueryEntry(cq, params); 321 QueryEntry existing = (QueryEntry)queryMap.get(e); 322 if (existing != null) { 323 if (tx.started <= existing.timestamp) { 324 return; } 326 e = existing; 327 } else { 328 int[] indexes = e.cq.getClassIndexes(); 329 for (int i = indexes.length - 1; i >= 0; i--) { 332 if (classEvictionTimestamp[indexes[i]] >= tx.started) { 333 return; 334 } 335 } 336 queryMap.put(e, e); 337 addToHeadOfQueryList(e); 338 discardExcessQueries(); 339 addToClassQueryLists(e); 340 } 341 e.res = queryData; 342 e.resultCount = resultCount; 343 e.timestamp = tx.started; 344 } 345 346 public synchronized void add(Object tx, CompiledQuery cq, Object [] params, 347 int count) { 348 if (!queryCacheEnabled) { 349 return; 350 } 351 addImp((Tx)tx, cq, params, null, count); 352 } 353 354 public synchronized void evict(Object otx, OID[] oids, int offset, 355 int length, int expected) { 356 if (!enabled) { 357 return; 358 } 359 Tx tx = (Tx)otx; 360 OID[] a; 361 int evictedCount = tx.evictedCount; 362 if (evictedCount > 0) { 363 if (tx.evicted.length - evictedCount >= length) { 364 a = tx.evicted; 365 } else { 366 a = new OID[evictedCount + length]; 367 System.arraycopy(tx.evicted, 0, a, 0, evictedCount); 368 tx.evicted = a; 369 } 370 } else { 371 tx.evicted = a = new OID[expected < length ? length : expected]; 372 } 373 System.arraycopy(oids, offset, a, evictedCount, length); 374 long started = tx.started; 375 for (int i = 0; i < length; i++) { 376 OID oid = a[i + evictedCount]; 377 StateEntry e = (StateEntry)stateMap.get(oid); 378 if (e == null) { stateMap.put(oid, new StateEntry(started, oid, null)); 380 } else if (e.state != null) { 381 removeFromStateList(e); 382 removeFromClassStateList(e); 383 e.state = null; 384 e.timestamp = started; 385 } else if (e.timestamp < started) { 386 e.timestamp = started; 387 continue; 388 } 389 int ci = oid.getClassIndex(); 390 classEvictionTimestamp[ci] = now; 391 removeQueriesForClass(ci); 392 } 393 tx.evictedCount += length; 394 } 395 396 public synchronized void evict(Object tx, ClassMetaData[] classes, 397 int classCount) { 398 if (!enabled) { 399 return; 400 } 401 for (int i = 0; i < classCount; i++) { 402 int ci = classes[i].index; 403 classEvictionTimestamp[ci] = now; 404 for (StateEntry e = classStateHead[ci]; e != null; ) { 406 removeFromStateList(e); 407 stateMap.remove(e.oid); 408 e.state = null; 409 e.oid = null; 410 StateEntry prev = e.classPrev; 411 e.classNext = null; 412 e.classPrev = null; 413 e = prev; 414 } 415 classStateHead[ci] = null; 416 removeQueriesForClass(ci); 418 classes[i].cacheStrategyAllDone = false; 419 } 420 } 421 422 public synchronized void evictAll(Object tx) { 423 evictAllTimestamp = now; 424 stateMap.clear(); 425 objectCount = 0; 426 queryMap.clear(); 427 queryCount = 0; 428 for (int i = classQueryHead.length - 1; i >= 0; i--) { 429 classQueryHead[i] = null; 430 } 431 for (int i = jmd.classes.length - 1; i >= 0; i--) { 432 jmd.classes[i].cacheStrategyAllDone = false; 433 } 434 stateHead = stateTail = null; 435 } 436 437 public int getObjectCount() { 438 return objectCount; 439 } 440 441 public int getMaxObjects() { 442 return maxObjects; 443 } 444 445 public synchronized void setMaxObjects(int maxObjects) { 446 this.maxObjects = maxObjects; 447 discardExcessStates(); 448 } 449 450 public int getMaxQueries() { 451 return maxQueries; 452 } 453 454 public synchronized void setMaxQueries(int maxQueries) { 455 this.maxQueries = maxQueries; 456 discardExcessQueries(); 457 } 458 459 public int getHitCount() { 460 return hitCount; 461 } 462 463 public int getMissCount() { 464 return missCount; 465 } 466 467 public int getQueryHitCount() { 468 return queryHitCount; 469 } 470 471 public int getQueryMissCount() { 472 return queryMissCount; 473 } 474 475 private void discardExcessStates() { 476 for (; objectCount > maxObjects && stateTail != null; --objectCount) { 477 StateEntry e = stateTail; 478 stateMap.remove(e.oid); 479 stateTail = e.lruNext; 480 e.lruNext = null; 481 if (stateTail != null) { 482 stateTail.lruPrev = null; 483 } else { 484 stateHead = null; 485 } 486 } 487 } 488 489 private void discardExcessQueries() { 490 for (; queryCount > maxQueries && queryTail != null; --queryCount) { 491 QueryEntry e = queryTail; 492 queryMap.remove(e); 493 queryTail = e.next; 494 e.next = null; 495 if (queryTail != null) { 496 queryTail.prev = null; 497 } else { 498 queryHead = null; 499 } 500 removeFromClassQueryLists(e); 501 } 502 } 503 504 507 private void removeFromStateList(StateEntry e) { 508 if (e.lruPrev != null) { 509 e.lruPrev.lruNext = e.lruNext; 510 } else { 511 stateTail = e.lruNext; 512 } 513 if (e.lruNext != null) { 514 e.lruNext.lruPrev = e.lruPrev; 515 } else { 516 stateHead = e.lruPrev; 517 } 518 e.lruNext = e.lruPrev = null; 519 --objectCount; 520 } 521 522 526 private void addToHeadOfStateList(StateEntry e) { 527 e.lruNext = null; 528 e.lruPrev = stateHead; 529 if (stateHead != null) { 530 stateHead.lruNext = e; 531 } 532 stateHead = e; 533 if (stateTail == null) { 534 stateTail = e; 535 } 536 ++objectCount; 537 } 538 539 542 private void addToClassStateList(StateEntry e) { 543 int i = e.state.getClassIndex(); 544 e.classPrev = classStateHead[i]; 545 if (classStateHead[i] != null) { 546 classStateHead[i].classNext = e; 547 } 548 e.classNext = null; 549 classStateHead[i] = e; 550 } 551 552 555 private void removeFromClassStateList(StateEntry e) { 556 if (e.classPrev != null) { 557 e.classPrev.classNext = e.classNext; 558 } else { 559 classStateHead[e.state.getClassIndex()] = e.classNext; 560 } 561 if (e.classNext != null) { 562 e.classNext.classPrev = e.classPrev; 563 e.classNext = null; 564 } 565 e.classPrev = null; 566 } 567 568 571 private void removeFromQueryList(QueryEntry e) { 572 if (e.prev != null) { 573 e.prev.next = e.next; 574 } else { 575 queryTail = e.next; 576 } 577 if (e.next != null) { 578 e.next.prev = e.prev; 579 } else { 580 queryHead = e.prev; 581 } 582 e.next = e.prev = null; 583 --queryCount; 584 } 585 586 590 private void addToHeadOfQueryList(QueryEntry e) { 591 e.next = null; 592 e.prev = queryHead; 593 if (queryHead != null) { 594 queryHead.next = e; 595 } 596 queryHead = e; 597 if (queryTail == null) { 598 queryTail = e; 599 } 600 ++queryCount; 601 } 602 603 607 private void addToClassQueryLists(QueryEntry e) { 608 int[] indexes = e.cq.getClassIndexes(); 609 QueryEntryNode sibling = null; 610 for (int i = 0; i < indexes.length; i++) { 611 int classIndex = indexes[i]; 612 QueryEntryNode head = classQueryHead[classIndex]; 613 QueryEntryNode n = new QueryEntryNode(e); 614 n.prev = head; 615 if (head != null) { 616 head.next = n; 617 } 618 if (sibling != null) { 619 sibling.nextSibling = n; 620 } else { 621 e.queryNodeTail = n; 622 } 623 classQueryHead[classIndex] = sibling = n; 624 } 625 } 626 627 631 private void removeFromClassQueryLists(QueryEntry e) { 632 int i = 0; 633 int[] indexes = e.cq.getClassIndexes(); 634 for (QueryEntryNode n = e.queryNodeTail; n != null; i++) { 635 QueryEntryNode nextSibling = n.nextSibling; 636 n.nextSibling = null; 637 if (n.prev != null) { 638 n.prev.next = n.next; 639 } 640 if (n.next != null) { 641 n.next.prev = n.prev; 642 } else { classQueryHead[indexes[i]] = n.prev; 644 } 645 n.next = n.prev = null; 646 n = nextSibling; 647 } 648 } 649 650 654 private void removeQueriesForClass(int classIndex) { 655 for (QueryEntryNode n = classQueryHead[classIndex]; n != null; ) { 656 removeFromQueryList(n.e); 657 QueryEntryNode prev = n.prev; 658 removeFromClassQueryLists(n.e); 659 queryMap.remove(n.e); 660 n = prev; 661 } 662 } 663 664 public void dump(PrintStream out) { 665 out.println("stateMap.size() = " + stateMap.size()); 666 out.println("objectCount = " + objectCount + 667 ", maxObjects = " + maxObjects); 668 int c = 0; 669 StateEntry p = null; 670 for (StateEntry e = stateTail; e != null; p = e, e = e.lruNext, ++c) { 671 asst(e.lruPrev == p); 672 } 673 out.println("LRU StateList length = " + c + " stateTail = " + 674 stateTail + " stateHead = " + stateHead); 675 HashSet oids = new HashSet(); 676 for (Tx e = txTail; e != null; e = e.next) { 677 if (e.evicted != null) { 678 oids.addAll(Arrays.asList(e.evicted)); 679 } 680 } 681 asst(stateHead == p); 682 asst(objectCount <= maxObjects); 683 asst(objectCount + oids.size() == stateMap.size()); 684 out.println("--- Tx list ---"); 685 c = 0; 686 int totEvictedCount = 0; 687 for (Tx e = txTail; e != null; e = e.next, ++c) { 688 out.println(e + " started " + e.started + " finished " + e.finished + 689 " evictedCount " + e.evictedCount); 690 totEvictedCount += e.evictedCount; 691 } 692 out.println("--- count " + c + " totEvictedCount " + totEvictedCount); 693 } 694 695 private void asst(boolean bool) { 696 if (!bool) { 697 throw BindingSupportImpl.getInstance().internal("assertion failed"); 698 } 699 } 700 701 public void addMetrics(List list) { 702 list.add(metricCacheSize); 703 list.add(metricCacheMaxSize); 704 list.add(metricCacheHit); 705 list.add(metricCacheMiss); 706 list.add(metricQueryCacheSize); 707 list.add(metricQueryCacheMaxSize); 708 list.add(metricQueryCacheHit); 709 list.add(metricQueryCacheMiss); 710 list.add(metricCacheFullPercent); 711 list.add(metricCacheHitPercent); 712 list.add(metricQueryCacheFullPercent); 713 list.add(metricQueryCacheHitPercent); 714 } 715 716 public void sampleMetrics(int[][] buf, int pos) { 717 buf[metricCacheSize.getIndex()][pos] = objectCount; 718 buf[metricCacheMaxSize.getIndex()][pos] = maxObjects; 719 buf[metricCacheHit.getIndex()][pos] = hitCount; 720 buf[metricCacheMiss.getIndex()][pos] = missCount; 721 buf[metricQueryCacheSize.getIndex()][pos] = queryCount; 722 buf[metricQueryCacheMaxSize.getIndex()][pos] = maxQueries; 723 buf[metricQueryCacheHit.getIndex()][pos] = queryHitCount; 724 buf[metricQueryCacheMiss.getIndex()][pos] = queryMissCount; 725 } 726 727 730 private static final class Tx { 731 final long started; 732 Tx next; 733 boolean finished; 734 OID[] evicted; 735 int evictedCount; 736 737 public Tx(long timestamp) { 738 this.started = timestamp; 739 } 740 } 741 742 746 private static final class StateEntry { 747 long timestamp; OID oid; 750 State state; 751 StateEntry lruPrev, lruNext; 752 StateEntry classPrev, classNext; 753 754 public StateEntry(long txId, OID oid, State state) { 755 this.timestamp = txId; 756 this.oid = oid; 757 this.state = state; 758 } 759 } 760 761 765 private static final class QueryEntry { 766 final CompiledQuery cq; 767 final Object [] params; 768 final int hashCode; 769 long timestamp; CachedQueryResult res; 772 int resultCount; 773 QueryEntry prev, next; 774 QueryEntryNode queryNodeTail; 775 777 public QueryEntry(CompiledQuery cq, Object [] params) { 778 this.cq = cq; 779 this.params = params; 780 int hc = cq.hashCode(); 781 if (params != null) { 782 hc = cq.hashCode(); 783 for (int i = params.length - 1; i >= 0; i--) { 784 Object o = params[i]; 785 if (o != null) { 786 hc = hc * 29 + o.hashCode(); 787 } 788 } 789 } 790 hashCode = hc; 791 } 792 793 public int hashCode() { 794 return hashCode; 795 } 796 797 public boolean equals(Object o) { 798 QueryEntry k = (QueryEntry)o; 799 if (hashCode != k.hashCode) { 800 return false; 801 } 802 if (!cq.equals(k.cq)) { 803 return false; 804 } 805 if (params != null) { 806 for (int i = params.length - 1; i >= 0; i--) { 807 Object a = params[i]; 808 Object b = k.params[i]; 809 if (a == null) { 810 if (b != null) return false; 811 } else if (b == null || !a.equals(b)) { 812 return false; 813 } 814 } 815 } 816 return true; 817 } 818 } 819 820 826 private static final class QueryEntryNode { 827 QueryEntry e; 828 QueryEntryNode prev, next, nextSibling; 829 830 public QueryEntryNode(QueryEntry e) { 831 this.e = e; 832 } 833 834 } 835 836 } 837 | Popular Tags |