瀏覽代碼

update localdb and xodus impl

Jason Rivard 9 年之前
父節點
當前提交
731ef5f094

+ 11 - 0
src/main/java/password/pwm/util/localdb/AbstractJDBC_LocalDB.java

@@ -28,6 +28,7 @@ import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.Serializable;
 import java.sql.*;
 import java.util.*;
 import java.util.Date;
@@ -540,4 +541,14 @@ public abstract class AbstractJDBC_LocalDB implements LocalDBProvider {
     }
 
     abstract String getDriverClasspath();
+
+    @Override
+    public Map<String, Serializable> debugInfo() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public Set<Flag> flags() {
+        return Collections.emptySet();
+    }
 }

+ 23 - 0
src/main/java/password/pwm/util/localdb/Berkeley_LocalDB.java

@@ -25,6 +25,8 @@ package password.pwm.util.localdb;
 import com.sleepycat.bind.tuple.TupleBinding;
 import com.sleepycat.collections.StoredMap;
 import com.sleepycat.je.*;
+import com.sleepycat.je.utilint.StatDefinition;
+import com.sleepycat.je.utilint.StatGroup;
 import com.sleepycat.util.RuntimeExceptionWrapper;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -33,6 +35,7 @@ import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReadWriteLock;
@@ -448,4 +451,24 @@ public class Berkeley_LocalDB implements LocalDBProvider {
         }
     }
 
+    @Override
+    public Map<String, Serializable> debugInfo() {
+        final StatsConfig statsConfig = new StatsConfig();
+        final EnvironmentStats environmentStats = environment.getStats(statsConfig);
+        final Map<String,Serializable> outputStats = new LinkedHashMap<>();
+        for (final StatGroup statGroup : environmentStats.getStatGroups()) {
+            for (final StatDefinition stat : statGroup.getStats().keySet()) {
+                final String name = stat.getName();
+                final String value = statGroup.getStat(stat).toStringVerbose();
+                outputStats.put(name,value);
+
+            }
+        }
+        return outputStats;
+    }
+
+    @Override
+    public Set<Flag> flags() {
+        return Collections.singleton(Flag.SlowSizeOperations);
+    }
 }

+ 11 - 0
src/main/java/password/pwm/util/localdb/H2MV_LocalDB.java

@@ -6,6 +6,7 @@ import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.*;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -151,6 +152,11 @@ public class H2MV_LocalDB implements LocalDBProvider {
         }
     }
 
+    @Override
+    public Map<String, Serializable> debugInfo() {
+        return Collections.emptyMap();
+    }
+
     private void compact(boolean full) {
         if (full) {
             executorService.schedule(new Runnable() {
@@ -177,4 +183,9 @@ public class H2MV_LocalDB implements LocalDBProvider {
             }, 0, TimeUnit.MILLISECONDS);
         }
     }
+
+    @Override
+    public Set<Flag> flags() {
+        return Collections.emptySet();
+    }
 }

+ 2 - 0
src/main/java/password/pwm/util/localdb/LocalDB.java

@@ -107,6 +107,8 @@ public interface LocalDB {
 
     File getFileLocation();
 
+    Map<String,Serializable> debugInfo();
+
 // -------------------------- ENUMERATIONS --------------------------
 
     enum DB {

+ 38 - 11
src/main/java/password/pwm/util/localdb/LocalDBAdaptor.java

@@ -30,6 +30,7 @@ import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -40,7 +41,7 @@ public class LocalDBAdaptor implements LocalDB {
 
     private final LocalDBProvider innerDB;
 
-    private final SizeCacheManager SIZE_CACHE_MANAGER = new SizeCacheManager();
+    private final SizeCacheManager SIZE_CACHE_MANAGER;
     private final PwmApplication pwmApplication;
 
     LocalDBAdaptor(final LocalDBProvider innerDB, final PwmApplication pwmApplication) {
@@ -49,6 +50,12 @@ public class LocalDBAdaptor implements LocalDB {
             throw new IllegalArgumentException("innerDB can not be null");
         }
 
+        if (innerDB.flags().contains(LocalDBProvider.Flag.SlowSizeOperations)) {
+            SIZE_CACHE_MANAGER = new SizeCacheManager();
+        } else {
+            SIZE_CACHE_MANAGER = null;
+        }
+
         this.innerDB = innerDB;
 
     }
@@ -113,10 +120,12 @@ public class LocalDBAdaptor implements LocalDB {
 
         public void remove() {
             innerIterator.remove();
-            try {
-                SIZE_CACHE_MANAGER.decrementSize(db);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
+            if (SIZE_CACHE_MANAGER != null) {
+                try {
+                    SIZE_CACHE_MANAGER.decrementSize(db);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
             }
         }
 
@@ -126,6 +135,10 @@ public class LocalDBAdaptor implements LocalDB {
         }
     }
 
+    public Map<String,Serializable> debugInfo() {
+        return innerDB.debugInfo();
+    }
+
     @WriteOperation
     public void putAll(final DB db, final Map<String, String> keyValueMap) throws LocalDBException {
         ParameterValidator.validateDBValue(db);
@@ -143,7 +156,9 @@ public class LocalDBAdaptor implements LocalDB {
         try {
             innerDB.putAll(db, keyValueMap);
         } finally {
-            SIZE_CACHE_MANAGER.clearSize(db);
+            if (SIZE_CACHE_MANAGER != null) {
+                SIZE_CACHE_MANAGER.clearSize(db);
+            }
         }
 
         markWrite(keyValueMap.size());
@@ -157,7 +172,9 @@ public class LocalDBAdaptor implements LocalDB {
 
         final boolean preExisting = innerDB.put(db, key, value);
         if (!preExisting) {
-            SIZE_CACHE_MANAGER.incrementSize(db);
+            if (SIZE_CACHE_MANAGER != null) {
+                SIZE_CACHE_MANAGER.incrementSize(db);
+            }
         }
 
         markWrite(1);
@@ -171,7 +188,9 @@ public class LocalDBAdaptor implements LocalDB {
 
         final boolean result = innerDB.remove(db, key);
         if (result) {
-            SIZE_CACHE_MANAGER.decrementSize(db);
+            if (SIZE_CACHE_MANAGER != null) {
+                SIZE_CACHE_MANAGER.decrementSize(db);
+            }
         }
 
         markWrite(1);
@@ -195,7 +214,9 @@ public class LocalDBAdaptor implements LocalDB {
             try {
                 innerDB.removeAll(db, keys);
             } finally {
-                SIZE_CACHE_MANAGER.clearSize(db);
+                if (SIZE_CACHE_MANAGER != null) {
+                    SIZE_CACHE_MANAGER.clearSize(db);
+                }
             }
         } else {
             for (final String key : keys) {
@@ -208,7 +229,11 @@ public class LocalDBAdaptor implements LocalDB {
 
     public int size(final DB db) throws LocalDBException {
         ParameterValidator.validateDBValue(db);
-        return SIZE_CACHE_MANAGER.getSizeForDB(db, innerDB);
+        if (SIZE_CACHE_MANAGER != null) {
+            return SIZE_CACHE_MANAGER.getSizeForDB(db, innerDB);
+        } else {
+            return innerDB.size(db);
+        }
     }
 
     @WriteOperation
@@ -217,7 +242,9 @@ public class LocalDBAdaptor implements LocalDB {
         try {
             innerDB.truncate(db);
         } finally {
-            SIZE_CACHE_MANAGER.clearSize(db);
+            if (SIZE_CACHE_MANAGER != null) {
+                SIZE_CACHE_MANAGER.clearSize(db);
+            }
         }
     }
 

+ 6 - 0
src/main/java/password/pwm/util/localdb/LocalDBCompressor.java

@@ -28,6 +28,7 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -166,4 +167,9 @@ public class LocalDBCompressor implements LocalDB {
 
         return input;
     }
+
+    public Map<String,Serializable> debugInfo() {
+        return innerLocalDB.debugInfo();
+    }
+
 }

+ 8 - 0
src/main/java/password/pwm/util/localdb/LocalDBProvider.java

@@ -23,11 +23,17 @@
 package password.pwm.util.localdb;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
 public interface LocalDBProvider {
 
+    enum Flag {
+        SlowSizeOperations,
+    }
+
     enum Parameter {
         readOnly,
         aggressiveCompact,
@@ -80,5 +86,7 @@ public interface LocalDBProvider {
 
     LocalDB.Status getStatus();
 
+    Map<String,Serializable> debugInfo();
 
+    Set<Flag> flags();
 }

+ 55 - 25
src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java

@@ -23,10 +23,13 @@
 package password.pwm.util.localdb;
 
 import password.pwm.PwmApplication;
+import password.pwm.util.ConditionalTaskExecutor;
 import password.pwm.util.logging.PwmLogger;
 
 import java.math.BigInteger;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -35,7 +38,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
  * synchronized.
  */
 public class
-        LocalDBStoredQueue implements Queue<String>, Deque<String>
+LocalDBStoredQueue implements Queue<String>, Deque<String>
 {
 // ------------------------------ FIELDS ------------------------------
 
@@ -92,7 +95,7 @@ public class
 
     public void removeLast(final int removalCount) {
         try {
-            internalQueue.removeLast(removalCount);
+            internalQueue.removeLast(removalCount, false);
         } catch (LocalDBException e) {
             throw new IllegalStateException("unexpected localDB error while modifying queue: " + e.getMessage(), e);
         }
@@ -256,7 +259,7 @@ public class
 
     public String pollLast() {
         try {
-            final List<String> values = internalQueue.removeLast(1);
+            final List<String> values = internalQueue.removeLast(1, true);
             if (values == null || values.isEmpty()) {
                 return null;
             }
@@ -502,7 +505,7 @@ public class
         private boolean developerDebug = false;
         private static final int DEBUG_MAX_ROWS = 50;
         private static final int DEBUG_MAX_WIDTH = 120;
-        private static final Set<LocalDB.DB> DEBUG_IGNORED_DBs = Collections.unmodifiableSet(new HashSet(Arrays.asList(
+        private static final Set<LocalDB.DB> DEBUG_IGNORED_DBs = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
                 new LocalDB.DB[] {
                         LocalDB.DB.EVENTLOG_EVENTS
                 }
@@ -571,11 +574,12 @@ public class
 
                 headPosition = new Position("0");
                 tailPosition = new Position("0");
-                localDB.put(DB, KEY_HEAD_POSITION, headPosition.toString());
-                localDB.put(DB, KEY_TAIL_POSITION, tailPosition.toString());
-
-                localDB.put(DB, KEY_VERSION, VALUE_VERSION);
+                final Map<String,String> keyValueMap = new HashMap<>();
+                keyValueMap.put(KEY_HEAD_POSITION, headPosition.toString());
+                keyValueMap.put(KEY_TAIL_POSITION, tailPosition.toString());
+                keyValueMap.put(KEY_VERSION, VALUE_VERSION);
 
+                localDB.putAll(DB,keyValueMap);
                 debugOutput("post clear()");
             } finally {
                 LOCK.writeLock().unlock();
@@ -602,7 +606,7 @@ public class
             return tailPosition.distanceToHead(headPosition).intValue() + 1;
         }
 
-        public List<String> removeFirst(final int removalCount) throws LocalDBException {
+        List<String> removeFirst(final int removalCount) throws LocalDBException {
             try {
                 LOCK.writeLock().lock();
 
@@ -636,7 +640,7 @@ public class
             }
         }
 
-        public List<String> removeLast(final int removalCount) throws LocalDBException {
+        List<String> removeLast(final int removalCount, final boolean returnValues) throws LocalDBException {
             try {
                 LOCK.writeLock().lock();
 
@@ -652,9 +656,11 @@ public class
                 int removedPositions = 0;
                 while (removedPositions < removalCount) {
                     removalKeys.add(nextTail.toString());
-                    final String loopValue = localDB.get(DB, nextTail.toString());
-                    if (loopValue != null) {
-                        removedValues.add(loopValue);
+                    if (returnValues) {
+                        final String loopValue = localDB.get(DB, nextTail.toString());
+                        if (loopValue != null) {
+                            removedValues.add(loopValue);
+                        }
                     }
                     nextTail = nextTail.equals(headPosition) ? nextTail : nextTail.next();
                     removedPositions++;
@@ -670,7 +676,7 @@ public class
             }
         }
 
-        public void addFirst(final Collection<String> values)
+        void addFirst(final Collection<String> values)
                 throws LocalDBException
         {
             try {
@@ -699,8 +705,8 @@ public class
                     keyValueMap.put(nextHead.toString(), valueIterator.next());
                 }
 
+                keyValueMap.put(KEY_HEAD_POSITION, String.valueOf(nextHead));
                 localDB.putAll(DB, keyValueMap);
-                localDB.put(DB, KEY_HEAD_POSITION, String.valueOf(nextHead));
                 headPosition = nextHead;
 
                 debugOutput("post addFirst()");
@@ -709,7 +715,7 @@ public class
             }
         }
 
-        public void addLast(final Collection<String> values) throws LocalDBException {
+        void addLast(final Collection<String> values) throws LocalDBException {
             try {
                 LOCK.writeLock().lock();
                 debugOutput("pre addLast()");
@@ -734,9 +740,8 @@ public class
                     nextTail = nextTail.previous();
                     keyValueMap.put(nextTail.toString(), valueIterator.next());
                 }
-
+                keyValueMap.put(KEY_TAIL_POSITION, String.valueOf(nextTail));
                 localDB.putAll(DB, keyValueMap);
-                localDB.put(DB, KEY_TAIL_POSITION, String.valueOf(nextTail));
                 tailPosition = nextTail;
 
                 debugOutput("post addLast()");
@@ -745,7 +750,7 @@ public class
             }
         }
 
-        public List<String> getFirst(int getCount)
+        List<String> getFirst(int getCount)
                 throws LocalDBException {
             try {
                 LOCK.readLock().lock();
@@ -775,7 +780,7 @@ public class
             }
         }
 
-        public List<String> getLast(int getCount)
+        List<String> getLast(int getCount)
                 throws LocalDBException {
             try {
                 LOCK.readLock().lock();
@@ -806,7 +811,7 @@ public class
             }
         }
 
-        public void debugOutput(final String input) {
+        void debugOutput(final String input) {
             if (!developerDebug || DEBUG_IGNORED_DBs.contains(DB)) {
                 return;
             }
@@ -849,19 +854,44 @@ public class
 
             debugOutput("pre repair()");
 
+            final AtomicInteger examinedRecords = new AtomicInteger(0);
+
+            ConditionalTaskExecutor conditionalTaskExecutor = new ConditionalTaskExecutor(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                localDB.put(DB, KEY_HEAD_POSITION, headPosition.toString());
+                                localDB.put(DB, KEY_TAIL_POSITION, tailPosition.toString());
+                                final int dbSize = size();
+                                LOGGER.debug("repairing db " + DB + ", " + examinedRecords.get() + " records examined"
+                                        + ", size=" + dbSize
+                                        + ", head=" + headPosition.toString() + ", tail=" + tailPosition.toString());
+                            } catch (Exception e) {
+                                LOGGER.error("unexpected error during output of debug message during stored queue repair operation: " + e.getMessage(), e);
+                            }
+                        }
+                    },
+                    new ConditionalTaskExecutor.TimeDurationConditional(30, TimeUnit.SECONDS)
+            );
+
             // trim the top.
-            while (!headPosition.equals(tailPosition) && localDB.get(DB,headPosition.toString()) == null) {
+            while (!headPosition.equals(tailPosition) && localDB.get(DB, headPosition.toString()) == null) {
+                examinedRecords.incrementAndGet();
+                conditionalTaskExecutor.conditionallyExecuteTask();
                 headPosition = headPosition.previous();
-                localDB.put(DB, KEY_HEAD_POSITION, headPosition.toString());
                 headTrim++;
             }
+            localDB.put(DB, KEY_HEAD_POSITION, headPosition.toString());
 
             // trim the bottom.
-            while (!headPosition.equals(tailPosition) && localDB.get(DB,tailPosition.toString()) == null) {
+            while (!headPosition.equals(tailPosition) && localDB.get(DB, tailPosition.toString()) == null) {
+                examinedRecords.incrementAndGet();
+                conditionalTaskExecutor.conditionallyExecuteTask();
                 tailPosition = tailPosition.next();
-                localDB.put(DB, KEY_TAIL_POSITION, tailPosition.toString());
                 tailTrim++;
             }
+            localDB.put(DB, KEY_TAIL_POSITION, tailPosition.toString());
 
             if (tailTrim == 0 && headTrim == 0) {
                 LOGGER.trace("repair unnecessary for " + DB);

+ 10 - 5
src/main/java/password/pwm/util/localdb/MapDB_LocalDB.java

@@ -31,10 +31,8 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
+import java.io.Serializable;
+import java.util.*;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -282,7 +280,10 @@ public class MapDB_LocalDB implements LocalDBProvider {
                 .open();
     }
 
-// -------------------------- INNER CLASSES --------------------------
+    @Override
+    public Map<String, Serializable> debugInfo() {
+        return Collections.emptyMap();
+    }
 
     private class MapDBIterator<K> implements LocalDB.LocalDBIterator<String> {
         private Iterator<String> theIterator;
@@ -317,4 +318,8 @@ public class MapDB_LocalDB implements LocalDBProvider {
         }
     }
 
+    @Override
+    public Set<Flag> flags() {
+        return Collections.emptySet();
+    }
 }

+ 10 - 3
src/main/java/password/pwm/util/localdb/Memory_LocalDB.java

@@ -27,6 +27,7 @@ import password.pwm.error.PwmError;
 import password.pwm.util.Helper;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -180,10 +181,11 @@ public class Memory_LocalDB implements LocalDBProvider {
         return state;
     }
 
-    // -------------------------- ENUMERATIONS --------------------------
-
+    @Override
+    public Map<String, Serializable> debugInfo() {
+        return Collections.emptyMap();
+    }
 
-// -------------------------- INNER CLASSES --------------------------
 
     private class DbIterator<K> implements LocalDB.LocalDBIterator<String> {
         private final Iterator<String> iterator;
@@ -211,4 +213,9 @@ public class Memory_LocalDB implements LocalDBProvider {
     public File getFileLocation() {
         return null;
     }
+
+    @Override
+    public Set<Flag> flags() {
+        return Collections.emptySet();
+    }
 }

+ 111 - 15
src/main/java/password/pwm/util/localdb/Xodus_LocalDB.java

@@ -22,6 +22,7 @@
 
 package password.pwm.util.localdb;
 
+import jetbrains.exodus.ArrayByteIterable;
 import jetbrains.exodus.ByteIterable;
 import jetbrains.exodus.bindings.StringBinding;
 import jetbrains.exodus.env.*;
@@ -33,9 +34,16 @@ import password.pwm.util.StringUtil;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterOutputStream;
 
 
 public class Xodus_LocalDB implements LocalDBProvider {
@@ -54,7 +62,9 @@ public class Xodus_LocalDB implements LocalDBProvider {
         public void run() {
             outputStats();
         }
-    },new ConditionalTaskExecutor.TimeDurationConditional(STATS_OUTPUT_INTERVAL));
+    },new ConditionalTaskExecutor.TimeDurationConditional(STATS_OUTPUT_INTERVAL).setNextTimeFromNow(1, TimeUnit.MINUTES));
+
+    private final static BindMachine bindMachine = new BindMachine(false);
 
 
     @Override
@@ -69,6 +79,7 @@ public class Xodus_LocalDB implements LocalDBProvider {
         environmentConfig.setGcEnabled(true);
         environmentConfig.setEnvCloseForcedly(true);
         environmentConfig.setFullFileReadonly(false);
+        environmentConfig.setMemoryUsage(50 * 1024 * 1024);
 
         for (final String key : initParameters.keySet()) {
             final String value = initParameters.get(key);
@@ -116,7 +127,6 @@ public class Xodus_LocalDB implements LocalDBProvider {
     }
     @Override
     public boolean contains(LocalDB.DB db, String key) throws LocalDBException {
-        checkStatus();
         return get(db, key) != null;
     }
 
@@ -127,9 +137,9 @@ public class Xodus_LocalDB implements LocalDBProvider {
             @Override
             public String compute(@NotNull Transaction transaction) {
                 final Store store = getStore(db);
-                final ByteIterable returnValue = store.get(transaction,StringBinding.stringToEntry(key));
+                final ByteIterable returnValue = store.get(transaction, bindMachine.keyToEntry(key));
                 if (returnValue != null) {
-                    return StringBinding.entryToString(returnValue);
+                    return bindMachine.entryToValue(returnValue);
                 }
                 return null;
             }
@@ -165,12 +175,12 @@ public class Xodus_LocalDB implements LocalDBProvider {
                     close();
                     return;
                 }
-                final ByteIterable value = cursor.getKey();
-                if (value == null || value.getLength() == 0) {
+                final ByteIterable nextKey = cursor.getKey();
+                if (nextKey == null || nextKey.getLength() == 0) {
                     close();
                     return;
                 }
-                final String decodedValue = StringBinding.entryToString(value);
+                final String decodedValue = bindMachine.entryToKey(nextKey);
                 if (decodedValue == null) {
                     close();
                     return;
@@ -223,8 +233,8 @@ public class Xodus_LocalDB implements LocalDBProvider {
                 final Store store = getStore(db);
                 for (final String key : keyValueMap.keySet()) {
                     final String value = keyValueMap.get(key);
-                    final ByteIterable k = StringBinding.stringToEntry(key);
-                    final ByteIterable v = StringBinding.stringToEntry(value);
+                    final ByteIterable k = bindMachine.keyToEntry(key);
+                    final ByteIterable v = bindMachine.valueToEntry(value);
                     store.put(transaction,k,v);
                 }
             }
@@ -240,8 +250,8 @@ public class Xodus_LocalDB implements LocalDBProvider {
         return environment.computeInTransaction(new TransactionalComputable<Boolean>() {
             @Override
             public Boolean compute(@NotNull Transaction transaction) {
-                final ByteIterable k = StringBinding.stringToEntry(key);
-                final ByteIterable v = StringBinding.stringToEntry(value);
+                final ByteIterable k = bindMachine.keyToEntry(key);
+                final ByteIterable v = bindMachine.valueToEntry(value);
                 final Store store = getStore(db);
                 return store.put(transaction,k,v);
             }
@@ -256,7 +266,7 @@ public class Xodus_LocalDB implements LocalDBProvider {
             @Override
             public Boolean compute(@NotNull Transaction transaction) {
                 final Store store = getStore(db);
-                return store.delete(transaction,StringBinding.stringToEntry(key));
+                return store.delete(transaction, bindMachine.keyToEntry(key));
             }
         });
     }
@@ -269,7 +279,7 @@ public class Xodus_LocalDB implements LocalDBProvider {
             public void execute(@NotNull Transaction transaction) {
                 final Store store = getStore(db);
                 for (final String key : keys) {
-                    store.delete(transaction, StringBinding.stringToEntry(key));
+                    store.delete(transaction, bindMachine.keyToEntry(key));
                 }
             }
         });
@@ -323,11 +333,97 @@ public class Xodus_LocalDB implements LocalDBProvider {
     }
 
     private void outputStats() {
+        LOGGER.trace("xodus environment stats: " + StringUtil.mapToString(debugInfo()));
+    }
+
+    @Override
+    public Map<String, Serializable> debugInfo() {
         final Statistics statistics = environment.getStatistics();
-        final Map<String,String> outputStats = new LinkedHashMap<>();
+        final Map<String,Serializable> outputStats = new LinkedHashMap<>();
         for (final String name : statistics.getItemNames()) {
             outputStats.put(name, String.valueOf(statistics.getStatisticsItem(name).getTotal()));
         }
-        LOGGER.trace("xodus environment stats: " + StringUtil.mapToString(outputStats));
+        return outputStats;
+    }
+
+    private static class BindMachine {
+        private static final Deflater DEFLATER = new Deflater();
+        private static final Inflater INFLATER = new Inflater();
+        private static final byte COMPRESSED_PREFIX = 98;
+        private static final byte UNCOMPRESSED_PREFIX = 99;
+        private static final int minCompressionLength = 50;
+
+        private final boolean enableCompression;
+
+        BindMachine(boolean enableCompression) {
+            this.enableCompression = enableCompression;
+        }
+
+        ByteIterable keyToEntry(final String key) {
+            return StringBinding.stringToEntry(key);
+        }
+
+        String entryToKey(final ByteIterable entry) {
+            return StringBinding.entryToString(entry);
+        }
+
+        ByteIterable valueToEntry(final String value) {
+            if (!enableCompression || value == null || value.length() < minCompressionLength) {
+                final ByteIterable byteIterable = StringBinding.stringToEntry(value);
+                return new ArrayByteIterable(UNCOMPRESSED_PREFIX, byteIterable);
+            }
+
+            final ByteIterable byteIterable = StringBinding.stringToEntry(value);
+            final byte[] rawArray = byteIterable.getBytesUnsafe();
+            final byte[] compressedArray = compressData(rawArray);
+
+            if (compressedArray.length < rawArray.length) {
+                return new ArrayByteIterable(COMPRESSED_PREFIX, new ArrayByteIterable(compressedArray));
+            } else {
+                return new ArrayByteIterable(UNCOMPRESSED_PREFIX, byteIterable);
+            }
+        }
+
+        String entryToValue(final ByteIterable value) {
+            final byte[] rawValue = value.getBytesUnsafe();
+            final byte[] strippedArray = new byte[rawValue.length -1];
+            System.arraycopy(rawValue,1,strippedArray,0,rawValue.length -1);
+            if (rawValue[0] == UNCOMPRESSED_PREFIX) {
+                return StringBinding.entryToString(new ArrayByteIterable(strippedArray));
+            } else if (rawValue[0] == COMPRESSED_PREFIX) {
+                final byte[] decompressedValue = decompressData(strippedArray);
+                return StringBinding.entryToString(new ArrayByteIterable(decompressedValue));
+            }
+            throw new IllegalStateException("unknown value prefix " + Byte.toString(rawValue[0]));
+        }
+
+        static byte[] compressData(byte[] data) {
+            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, DEFLATER);
+            try {
+                deflaterOutputStream.write(data);
+                deflaterOutputStream.close();
+            } catch (IOException e) {
+                throw new IllegalStateException("unexpected exception compressing data stream", e);
+            }
+            return byteArrayOutputStream.toByteArray();
+        }
+
+        static byte[] decompressData(byte[] data) {
+            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            final InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(byteArrayOutputStream, INFLATER);
+            try {
+                inflaterOutputStream.write(data);
+                inflaterOutputStream.close();
+            } catch (IOException e) {
+                throw new IllegalStateException("unexpected exception decompressing data stream", e);
+            }
+            return byteArrayOutputStream.toByteArray();
+        }
+    }
+
+    @Override
+    public Set<Flag> flags() {
+        return Collections.emptySet();
     }
 }