/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.datasource; import org.ethereum.crypto.HashUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Special optimization when the majority of get requests to the slower underlying source * are targeted to missing entries. The BloomFilter handles most of these requests. * * Created by Anton Nashatyrev on 16.01.2017. */ public class BloomedSource extends AbstractChainedSource<byte[], byte[], byte[], byte[]> { private final static Logger logger = LoggerFactory.getLogger("db"); private byte[] filterKey = HashUtil.sha3("filterKey".getBytes()); QuotientFilter filter; int hits = 0; int misses = 0; int falseMisses = 0; boolean dirty = false; int maxBloomSize; public BloomedSource(Source<byte[], byte[]> source, int maxBloomSize) { super(source); this.maxBloomSize = maxBloomSize; byte[] filterBytes = source.get(filterKey); if (filterBytes != null) { if (filterBytes.length > 0) { filter = QuotientFilter.deserialize(filterBytes); } else { // filter size exceeded limit and is disabled forever filter = null; } } else { if (maxBloomSize > 0) { filter = QuotientFilter.create(50_000_000, 100_000); } else { // we can't re-enable filter later getSource().put(filterKey, new byte[0]); } } // // new Thread() { // @Override // public void run() { // while(true) { // synchronized (BloomedSource.this) { // logger.debug("BloomedSource: hits: " + hits + ", misses: " + misses + ", false: " + falseMisses); // hits = misses = falseMisses = 0; // } // // try { // Thread.sleep(5000); // } catch (InterruptedException e) {} // } // } // }.start(); } public void startBlooming(QuotientFilter filter) { this.filter = filter; } public void stopBlooming() { filter = null; } @Override public void put(byte[] key, byte[] val) { if (filter != null) { filter.insert(key); dirty = true; if (filter.getAllocatedBytes() > maxBloomSize) { logger.info("Bloom filter became too large (" + filter.getAllocatedBytes() + " exceeds max threshold " + maxBloomSize + ") and is now disabled forever."); getSource().put(filterKey, new byte[0]); filter = null; dirty = false; } } getSource().put(key, val); } @Override public byte[] get(byte[] key) { if (filter == null) return getSource().get(key); if (!filter.maybeContains(key)) { hits++; return null; } else { byte[] ret = getSource().get(key); if (ret == null) falseMisses++; else misses++; return ret; } } @Override public void delete(byte[] key) { if (filter != null) filter.remove(key); getSource().delete(key); } @Override protected boolean flushImpl() { if (filter != null && dirty) { getSource().put(filterKey, filter.serialize()); dirty = false; return true; } else { return false; } } }