summaryrefslogtreecommitdiffstats
path: root/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java')
-rw-r--r--gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java715
1 files changed, 0 insertions, 715 deletions
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
deleted file mode 100644
index eaa9af9d62..0000000000
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache.h2;
-
-import com.google.common.base.Throwables;
-import com.google.common.cache.AbstractLoadingCache;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.CacheStats;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.hash.BloomFilter;
-import com.google.common.hash.Funnel;
-import com.google.common.hash.Funnels;
-import com.google.common.hash.PrimitiveSink;
-import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.server.cache.PersistentCache;
-import com.google.inject.TypeLiteral;
-import java.io.IOException;
-import java.io.InvalidClassException;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.sql.Types;
-import java.util.Calendar;
-import java.util.Map;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import org.h2.jdbc.JdbcSQLException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Hybrid in-memory and database backed cache built on H2.
- *
- * <p>This cache can be used as either a recall cache, or a loading cache if a CacheLoader was
- * supplied to its constructor at build time. Before creating an entry the in-memory cache is
- * checked for the item, then the database is checked, and finally the CacheLoader is used to
- * construct the item. This is mostly useful for CacheLoaders that are computationally intensive,
- * such as the PatchListCache.
- *
- * <p>Cache stores and invalidations are performed on a background thread, hiding the latency
- * associated with serializing the key and value pairs and writing them to the database log.
- *
- * <p>A BloomFilter is used around the database to reduce the number of SELECTs issued against the
- * database for new cache items that have not been seen before, a common operation for the
- * PatchListCache. The BloomFilter is sized when the cache starts to be 64,000 entries or double the
- * number of items currently in the database table.
- *
- * <p>This cache does not export its items as a ConcurrentMap.
- *
- * @see H2CacheFactory
- */
-public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements PersistentCache {
- private static final Logger log = LoggerFactory.getLogger(H2CacheImpl.class);
-
- private static final ImmutableSet<String> OLD_CLASS_NAMES =
- ImmutableSet.of("com.google.gerrit.server.change.ChangeKind");
-
- private final Executor executor;
- private final SqlStore<K, V> store;
- private final TypeLiteral<K> keyType;
- private final Cache<K, ValueHolder<V>> mem;
-
- H2CacheImpl(
- Executor executor,
- SqlStore<K, V> store,
- TypeLiteral<K> keyType,
- Cache<K, ValueHolder<V>> mem) {
- this.executor = executor;
- this.store = store;
- this.keyType = keyType;
- this.mem = mem;
- }
-
- @Override
- public V getIfPresent(Object objKey) {
- if (!keyType.getRawType().isInstance(objKey)) {
- return null;
- }
-
- @SuppressWarnings("unchecked")
- K key = (K) objKey;
-
- ValueHolder<V> h = mem.getIfPresent(key);
- if (h != null) {
- return h.value;
- }
-
- if (store.mightContain(key)) {
- h = store.getIfPresent(key);
- if (h != null) {
- mem.put(key, h);
- return h.value;
- }
- }
- return null;
- }
-
- @Override
- public V get(K key) throws ExecutionException {
- if (mem instanceof LoadingCache) {
- return ((LoadingCache<K, ValueHolder<V>>) mem).get(key).value;
- }
- throw new UnsupportedOperationException();
- }
-
- @Override
- public V get(K key, Callable<? extends V> valueLoader) throws ExecutionException {
- return mem.get(
- key,
- () -> {
- if (store.mightContain(key)) {
- ValueHolder<V> h = store.getIfPresent(key);
- if (h != null) {
- return h;
- }
- }
-
- ValueHolder<V> h = new ValueHolder<>(valueLoader.call());
- h.created = TimeUtil.nowMs();
- executor.execute(() -> store.put(key, h));
- return h;
- })
- .value;
- }
-
- @Override
- public void put(K key, V val) {
- final ValueHolder<V> h = new ValueHolder<>(val);
- h.created = TimeUtil.nowMs();
- mem.put(key, h);
- executor.execute(() -> store.put(key, h));
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void invalidate(Object key) {
- if (keyType.getRawType().isInstance(key) && store.mightContain((K) key)) {
- executor.execute(() -> store.invalidate((K) key));
- }
- mem.invalidate(key);
- }
-
- @Override
- public void invalidateAll() {
- store.invalidateAll();
- mem.invalidateAll();
- }
-
- @Override
- public long size() {
- return mem.size();
- }
-
- @Override
- public CacheStats stats() {
- return mem.stats();
- }
-
- @Override
- public DiskStats diskStats() {
- return store.diskStats();
- }
-
- void start() {
- store.open();
- }
-
- void stop() {
- for (Map.Entry<K, ValueHolder<V>> e : mem.asMap().entrySet()) {
- ValueHolder<V> h = e.getValue();
- if (!h.clean) {
- store.put(e.getKey(), h);
- }
- }
- store.close();
- }
-
- void prune(ScheduledExecutorService service) {
- store.prune(mem);
-
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.HOUR_OF_DAY, 01);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
- cal.add(Calendar.DAY_OF_MONTH, 1);
-
- long delay = cal.getTimeInMillis() - TimeUtil.nowMs();
- @SuppressWarnings("unused")
- Future<?> possiblyIgnoredError =
- service.schedule(() -> prune(service), delay, TimeUnit.MILLISECONDS);
- }
-
- static class ValueHolder<V> {
- final V value;
- long created;
- volatile boolean clean;
-
- ValueHolder(V value) {
- this.value = value;
- }
- }
-
- static class Loader<K, V> extends CacheLoader<K, ValueHolder<V>> {
- private final Executor executor;
- private final SqlStore<K, V> store;
- private final CacheLoader<K, V> loader;
-
- Loader(Executor executor, SqlStore<K, V> store, CacheLoader<K, V> loader) {
- this.executor = executor;
- this.store = store;
- this.loader = loader;
- }
-
- @Override
- public ValueHolder<V> load(K key) throws Exception {
- if (store.mightContain(key)) {
- ValueHolder<V> h = store.getIfPresent(key);
- if (h != null) {
- return h;
- }
- }
-
- final ValueHolder<V> h = new ValueHolder<>(loader.load(key));
- h.created = TimeUtil.nowMs();
- executor.execute(() -> store.put(key, h));
- return h;
- }
- }
-
- private static class KeyType<K> {
- String columnType() {
- return "OTHER";
- }
-
- @SuppressWarnings("unchecked")
- K get(ResultSet rs, int col) throws SQLException {
- return (K) rs.getObject(col);
- }
-
- void set(PreparedStatement ps, int col, K value) throws SQLException {
- ps.setObject(col, value, Types.JAVA_OBJECT);
- }
-
- Funnel<K> funnel() {
- return new Funnel<K>() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void funnel(K from, PrimitiveSink into) {
- try (ObjectOutputStream ser = new ObjectOutputStream(new SinkOutputStream(into))) {
- ser.writeObject(from);
- ser.flush();
- } catch (IOException err) {
- throw new RuntimeException("Cannot hash as Serializable", err);
- }
- }
- };
- }
-
- @SuppressWarnings("unchecked")
- static <K> KeyType<K> create(TypeLiteral<K> type) {
- if (type.getRawType() == String.class) {
- return (KeyType<K>) STRING;
- }
- return (KeyType<K>) OTHER;
- }
-
- static final KeyType<?> OTHER = new KeyType<>();
- static final KeyType<String> STRING =
- new KeyType<String>() {
- @Override
- String columnType() {
- return "VARCHAR(4096)";
- }
-
- @Override
- String get(ResultSet rs, int col) throws SQLException {
- return rs.getString(col);
- }
-
- @Override
- void set(PreparedStatement ps, int col, String value) throws SQLException {
- ps.setString(col, value);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- Funnel<String> funnel() {
- Funnel<?> s = Funnels.unencodedCharsFunnel();
- return (Funnel<String>) s;
- }
- };
- }
-
- static class SqlStore<K, V> {
- private final String url;
- private final KeyType<K> keyType;
- private final long maxSize;
- private final long expireAfterWrite;
- private final BlockingQueue<SqlHandle> handles;
- private final AtomicLong hitCount = new AtomicLong();
- private final AtomicLong missCount = new AtomicLong();
- private volatile BloomFilter<K> bloomFilter;
- private int estimatedSize;
-
- SqlStore(String jdbcUrl, TypeLiteral<K> keyType, long maxSize, long expireAfterWrite) {
- this.url = jdbcUrl;
- this.keyType = KeyType.create(keyType);
- this.maxSize = maxSize;
- this.expireAfterWrite = expireAfterWrite;
-
- int cores = Runtime.getRuntime().availableProcessors();
- int keep = Math.min(cores, 16);
- this.handles = new ArrayBlockingQueue<>(keep);
- }
-
- synchronized void open() {
- if (bloomFilter == null) {
- bloomFilter = buildBloomFilter();
- }
- }
-
- void close() {
- SqlHandle h;
- while ((h = handles.poll()) != null) {
- h.close();
- }
- }
-
- boolean mightContain(K key) {
- BloomFilter<K> b = bloomFilter;
- if (b == null) {
- synchronized (this) {
- b = bloomFilter;
- if (b == null) {
- b = buildBloomFilter();
- bloomFilter = b;
- }
- }
- }
- return b == null || b.mightContain(key);
- }
-
- private BloomFilter<K> buildBloomFilter() {
- SqlHandle c = null;
- try {
- c = acquire();
- try (Statement s = c.conn.createStatement()) {
- if (estimatedSize <= 0) {
- try (ResultSet r = s.executeQuery("SELECT COUNT(*) FROM data")) {
- estimatedSize = r.next() ? r.getInt(1) : 0;
- }
- }
-
- BloomFilter<K> b = newBloomFilter();
- try (ResultSet r = s.executeQuery("SELECT k FROM data")) {
- while (r.next()) {
- b.put(keyType.get(r, 1));
- }
- } catch (JdbcSQLException e) {
- if (e.getCause() instanceof InvalidClassException) {
- log.warn(
- "Entries cached for "
- + url
- + " have an incompatible class and can't be deserialized. "
- + "Cache is flushed.");
- invalidateAll();
- } else {
- throw e;
- }
- }
- return b;
- }
- } catch (SQLException e) {
- log.warn("Cannot build BloomFilter for " + url + ": " + e.getMessage());
- c = close(c);
- return null;
- } finally {
- release(c);
- }
- }
-
- ValueHolder<V> getIfPresent(K key) {
- SqlHandle c = null;
- try {
- c = acquire();
- if (c.get == null) {
- c.get = c.conn.prepareStatement("SELECT v, created FROM data WHERE k=?");
- }
- keyType.set(c.get, 1, key);
- try (ResultSet r = c.get.executeQuery()) {
- if (!r.next()) {
- missCount.incrementAndGet();
- return null;
- }
-
- Timestamp created = r.getTimestamp(2);
- if (expired(created)) {
- invalidate(key);
- missCount.incrementAndGet();
- return null;
- }
-
- @SuppressWarnings("unchecked")
- V val = (V) r.getObject(1);
- ValueHolder<V> h = new ValueHolder<>(val);
- h.clean = true;
- hitCount.incrementAndGet();
- touch(c, key);
- return h;
- } finally {
- c.get.clearParameters();
- }
- } catch (SQLException e) {
- if (!isOldClassNameError(e)) {
- log.warn("Cannot read cache " + url + " for " + key, e);
- }
- c = close(c);
- return null;
- } finally {
- release(c);
- }
- }
-
- private static boolean isOldClassNameError(Throwable t) {
- for (Throwable c : Throwables.getCausalChain(t)) {
- if (c instanceof ClassNotFoundException && OLD_CLASS_NAMES.contains(c.getMessage())) {
- return true;
- }
- }
- return false;
- }
-
- private boolean expired(Timestamp created) {
- if (expireAfterWrite == 0) {
- return false;
- }
- long age = TimeUtil.nowMs() - created.getTime();
- return 1000 * expireAfterWrite < age;
- }
-
- private void touch(SqlHandle c, K key) throws SQLException {
- if (c.touch == null) {
- c.touch = c.conn.prepareStatement("UPDATE data SET accessed=? WHERE k=?");
- }
- try {
- c.touch.setTimestamp(1, TimeUtil.nowTs());
- keyType.set(c.touch, 2, key);
- c.touch.executeUpdate();
- } finally {
- c.touch.clearParameters();
- }
- }
-
- void put(K key, ValueHolder<V> holder) {
- if (holder.clean) {
- return;
- }
-
- BloomFilter<K> b = bloomFilter;
- if (b != null) {
- b.put(key);
- bloomFilter = b;
- }
-
- SqlHandle c = null;
- try {
- c = acquire();
- if (c.put == null) {
- c.put =
- c.conn.prepareStatement("MERGE INTO data (k, v, created, accessed) VALUES(?,?,?,?)");
- }
- try {
- keyType.set(c.put, 1, key);
- c.put.setObject(2, holder.value, Types.JAVA_OBJECT);
- c.put.setTimestamp(3, new Timestamp(holder.created));
- c.put.setTimestamp(4, TimeUtil.nowTs());
- c.put.executeUpdate();
- holder.clean = true;
- } finally {
- c.put.clearParameters();
- }
- } catch (SQLException e) {
- log.warn("Cannot put into cache " + url, e);
- c = close(c);
- } finally {
- release(c);
- }
- }
-
- void invalidate(K key) {
- SqlHandle c = null;
- try {
- c = acquire();
- invalidate(c, key);
- } catch (SQLException e) {
- log.warn("Cannot invalidate cache " + url, e);
- c = close(c);
- } finally {
- release(c);
- }
- }
-
- private void invalidate(SqlHandle c, K key) throws SQLException {
- if (c.invalidate == null) {
- c.invalidate = c.conn.prepareStatement("DELETE FROM data WHERE k=?");
- }
- try {
- keyType.set(c.invalidate, 1, key);
- c.invalidate.executeUpdate();
- } finally {
- c.invalidate.clearParameters();
- }
- }
-
- void invalidateAll() {
- SqlHandle c = null;
- try {
- c = acquire();
- try (Statement s = c.conn.createStatement()) {
- s.executeUpdate("DELETE FROM data");
- }
- bloomFilter = newBloomFilter();
- } catch (SQLException e) {
- log.warn("Cannot invalidate cache " + url, e);
- c = close(c);
- } finally {
- release(c);
- }
- }
-
- void prune(Cache<K, ?> mem) {
- SqlHandle c = null;
- try {
- c = acquire();
- try (Statement s = c.conn.createStatement()) {
- long used = 0;
- try (ResultSet r = s.executeQuery("SELECT SUM(space) FROM data")) {
- used = r.next() ? r.getLong(1) : 0;
- }
- if (used <= maxSize) {
- return;
- }
-
- try (ResultSet r =
- s.executeQuery(
- "SELECT" + " k" + ",space" + ",created" + " FROM data" + " ORDER BY accessed")) {
- while (maxSize < used && r.next()) {
- K key = keyType.get(r, 1);
- Timestamp created = r.getTimestamp(3);
- if (mem.getIfPresent(key) != null && !expired(created)) {
- touch(c, key);
- } else {
- invalidate(c, key);
- used -= r.getLong(2);
- }
- }
- }
- }
- } catch (SQLException e) {
- log.warn("Cannot prune cache " + url, e);
- c = close(c);
- } finally {
- release(c);
- }
- }
-
- DiskStats diskStats() {
- long size = 0;
- long space = 0;
- SqlHandle c = null;
- try {
- c = acquire();
- try (Statement s = c.conn.createStatement();
- ResultSet r = s.executeQuery("SELECT" + " COUNT(*)" + ",SUM(space)" + " FROM data")) {
- if (r.next()) {
- size = r.getLong(1);
- space = r.getLong(2);
- }
- }
- } catch (SQLException e) {
- log.warn("Cannot get DiskStats for " + url, e);
- c = close(c);
- } finally {
- release(c);
- }
- return new DiskStats(size, space, hitCount.get(), missCount.get());
- }
-
- private SqlHandle acquire() throws SQLException {
- SqlHandle h = handles.poll();
- return h != null ? h : new SqlHandle(url, keyType);
- }
-
- private void release(SqlHandle h) {
- if (h != null && !handles.offer(h)) {
- h.close();
- }
- }
-
- private SqlHandle close(SqlHandle h) {
- if (h != null) {
- h.close();
- }
- return null;
- }
-
- private BloomFilter<K> newBloomFilter() {
- int cnt = Math.max(64 * 1024, 2 * estimatedSize);
- return BloomFilter.create(keyType.funnel(), cnt);
- }
- }
-
- static class SqlHandle {
- private final String url;
- Connection conn;
- PreparedStatement get;
- PreparedStatement put;
- PreparedStatement touch;
- PreparedStatement invalidate;
-
- SqlHandle(String url, KeyType<?> type) throws SQLException {
- this.url = url;
- this.conn = org.h2.Driver.load().connect(url, null);
- try (Statement stmt = conn.createStatement()) {
- stmt.addBatch(
- "CREATE TABLE IF NOT EXISTS data"
- + "(k "
- + type.columnType()
- + " NOT NULL PRIMARY KEY HASH"
- + ",v OTHER NOT NULL"
- + ",created TIMESTAMP NOT NULL"
- + ",accessed TIMESTAMP NOT NULL"
- + ")");
- stmt.addBatch(
- "ALTER TABLE data ADD COLUMN IF NOT EXISTS "
- + "space BIGINT AS OCTET_LENGTH(k) + OCTET_LENGTH(v)");
- stmt.executeBatch();
- }
- }
-
- void close() {
- get = closeStatement(get);
- put = closeStatement(put);
- touch = closeStatement(touch);
- invalidate = closeStatement(invalidate);
-
- if (conn != null) {
- try {
- conn.close();
- } catch (SQLException e) {
- log.warn("Cannot close connection to " + url, e);
- } finally {
- conn = null;
- }
- }
- }
-
- private PreparedStatement closeStatement(PreparedStatement ps) {
- if (ps != null) {
- try {
- ps.close();
- } catch (SQLException e) {
- log.warn("Cannot close statement for " + url, e);
- }
- }
- return null;
- }
- }
-
- private static class SinkOutputStream extends OutputStream {
- private final PrimitiveSink sink;
-
- SinkOutputStream(PrimitiveSink sink) {
- this.sink = sink;
- }
-
- @Override
- public void write(int b) {
- sink.putByte((byte) b);
- }
-
- @Override
- public void write(byte[] b, int p, int n) {
- sink.putBytes(b, p, n);
- }
- }
-}