diff --git a/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java b/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java
index c242ab9b7b1d88cadb642b0e15df8b707c2a01d9..b87a7624a67d0438984916c4ede3cc53eda6fd6f 100644
--- a/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java
+++ b/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java
@@ -526,11 +526,17 @@ public class KBucketSet<T extends SimpleDataStructure> {
     public List<T> getExploreKeys(long age) {
         List<T> rv = new ArrayList(_buckets.size());
         long old = _context.clock().now() - age;
+        int prevSize = -1;
         getReadLock();
         try {
             for (KBucket b : _buckets) {
-                if (b.getLastChanged() < old || b.getKeyCount() < BUCKET_SIZE * 3 / 4)
+                int curSize = b.getKeyCount();
+                // The first few buckets are all empty, we only need one
+                // explore key for all of them.
+                if ((prevSize != 0 || curSize != 0) &&
+                    (b.getLastChanged() < old || curSize < BUCKET_SIZE * 3 / 4))
                     rv.add(generateRandomKey(b));
+                prevSize = curSize;
             }
         } finally { releaseReadLock(); }
         return rv;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/DHTNodes.java b/apps/i2psnark/java/src/org/klomp/snark/dht/DHTNodes.java
index 5e2569e2d103e19df4cad09ed5f1eb83ec38c67c..ee0e8f01fde6f23e16848e95090933a96bc46085 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/dht/DHTNodes.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/dht/DHTNodes.java
@@ -69,6 +69,9 @@ class DHTNodes {
 
     // begin ConcurrentHashMap methods
 
+    /**
+     *  @return known nodes, not total net size
+     */
     public int size() {
         return _nodeMap.size();
     }
@@ -128,11 +131,19 @@ class DHTNodes {
         return _kad.getExploreKeys(MAX_BUCKET_AGE);
     }
 
+    /**
+     * Debug info, HTML formatted
+     * @since 0.9.4
+     */
+    public void renderStatusHTML(StringBuilder buf) {
+        buf.append(_kad.toString().replace("\n", "<br>\n"));
+    }
+
     /** */
     private class Cleaner extends SimpleTimer2.TimedEvent {
 
         public Cleaner() {
-            super(SimpleTimer2.getInstance(), CLEAN_TIME);
+            super(SimpleTimer2.getInstance(), 5 * CLEAN_TIME);
         }
 
         public void timeReached() {
diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/DHTTracker.java b/apps/i2psnark/java/src/org/klomp/snark/dht/DHTTracker.java
index 5e8b3a9526043a1ee6a22bfcab8ba8cc5ce02ea1..0f030a20728224c79762f117ef54a6d8f144faeb 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/dht/DHTTracker.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/dht/DHTTracker.java
@@ -39,6 +39,8 @@ class DHTTracker {
     private static final long DELTA_EXPIRE_TIME = 3*60*1000;
     private static final int MAX_PEERS = 2000;
     private static final int MAX_PEERS_PER_TORRENT = 150;
+    private static final int ABSOLUTE_MAX_PER_TORRENT = MAX_PEERS_PER_TORRENT * 2;
+    private static final int MAX_TORRENTS = 400;
 
     DHTTracker(I2PAppContext ctx) {
         _context = ctx;
@@ -62,17 +64,29 @@ class DHTTracker {
             _log.debug("Announce " + hash + " for " + ih);
         Peers peers = _torrents.get(ih);
         if (peers == null) {
+            if (_torrents.size() >= MAX_TORRENTS)
+                return;
             peers = new Peers();
             Peers peers2 = _torrents.putIfAbsent(ih, peers);
             if (peers2 != null)
                 peers = peers2;
         }
 
-        Peer peer = new Peer(hash.getData());
-        Peer peer2 = peers.putIfAbsent(peer, peer);
-        if (peer2 != null)
-            peer = peer2;
-        peer.setLastSeen(_context.clock().now());
+        if (peers.size() < ABSOLUTE_MAX_PER_TORRENT) {
+            Peer peer = new Peer(hash.getData());
+            Peer peer2 = peers.putIfAbsent(peer, peer);
+            if (peer2 != null)
+                peer = peer2;
+            peer.setLastSeen(_context.clock().now());
+        } else {
+            // We could update setLastSeen if he is already
+            // in there, but that would tend to keep
+            // the same set of peers.
+            // So let it expire so new ones can come in.
+            //Peer peer = peers.get(hash);
+            //if (peer != null)
+            //    peer.setLastSeen(_context.clock().now());
+        }
     }
 
     void unannounce(InfoHash ih, Hash hash) {
@@ -113,7 +127,7 @@ class DHTTracker {
     private class Cleaner extends SimpleTimer2.TimedEvent {
 
         public Cleaner() {
-            super(SimpleTimer2.getInstance(), CLEAN_TIME);
+            super(SimpleTimer2.getInstance(), 2 * CLEAN_TIME);
         }
 
         public void timeReached() {
@@ -122,6 +136,7 @@ class DHTTracker {
             long now = _context.clock().now();
             int torrentCount = 0;
             int peerCount = 0;
+            boolean tooMany = false;
             for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
                 Peers p = iter.next();
                 int recent = 0;
@@ -136,6 +151,7 @@ class DHTTracker {
                 }
                 if (recent > MAX_PEERS_PER_TORRENT) {
                     // too many, delete at random
+                    // TODO sort and remove oldest?
                     // TODO per-torrent adjustable expiration?
                     for (Iterator<Peer> iterp = p.values().iterator(); iterp.hasNext() && p.size() > MAX_PEERS_PER_TORRENT; ) {
                          iterp.next();
@@ -143,6 +159,7 @@ class DHTTracker {
                          peerCount--;
                     }
                     torrentCount++;
+                    tooMany = true;
                 } else if (recent <= 0) {
                     iter.remove();
                 } else {
@@ -151,6 +168,8 @@ class DHTTracker {
             }
 
             if (peerCount > MAX_PEERS)
+                tooMany = true;
+            if (tooMany)
                 _expireTime = Math.max(_expireTime - DELTA_EXPIRE_TIME, MIN_EXPIRE_TIME);
             else
                 _expireTime = Math.min(_expireTime + DELTA_EXPIRE_TIME, MAX_EXPIRE_TIME);
@@ -162,7 +181,7 @@ class DHTTracker {
                          DataHelper.formatDuration(_expireTime) + " expiration");
             _peerCount = peerCount;
             _torrentCount = torrentCount;
-            schedule(CLEAN_TIME);
+            schedule(tooMany ? CLEAN_TIME / 3 : CLEAN_TIME);
         }
     }
 }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
index 01a63a259fdd51e85a8e06652ce014ffa3bed7b8..910841e79134e779dda97f66671ac39feb359ad5 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
@@ -613,6 +613,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
                    "Rcvd tokens: ").append(_incomingTokens.size()).append("<br>" +
                    "Pending queries: ").append(_sentQueries.size()).append("<br>");
         _tracker.renderStatusHTML(buf);
+        _knownNodes.renderStatusHTML(buf);
         return buf.toString();
     }
 
@@ -1518,7 +1519,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
     private class Cleaner extends SimpleTimer2.TimedEvent {
 
         public Cleaner() {
-            super(SimpleTimer2.getInstance(), CLEAN_TIME);
+            super(SimpleTimer2.getInstance(), 7 * CLEAN_TIME);
         }
 
         public void timeReached() {
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
index 3aed6631533b7853db7c07b8a312d6311eea2f7d..3e612d166c8f37de7a8f92d183348965da498b9c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
@@ -558,10 +558,22 @@ public class ConsoleUpdateManager implements UpdateManager {
      *  Call once for each type/method pair.
      */
     public void register(Updater updater, UpdateType type, UpdateMethod method, int priority) {
+        // DEBUG slow start for snark updates
+        // For 0.9.4 update, only for dev builds
+        // For 0.9.5 update, only for dev builds and 1% more
+        // Remove this in 0.9.6 or 0.9.7
+        if (method == TORRENT && RouterVersion.BUILD == 0 && _context.random().nextInt(100) != 0) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Ignoring torrent registration");
+            return;
+        }
         RegisteredUpdater ru = new RegisteredUpdater(updater, type, method, priority);
         if (_log.shouldLog(Log.INFO))
             _log.info("Registering " + ru);
-        _registeredUpdaters.add(ru);
+        if (!_registeredUpdaters.add(ru)) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Duplicate registration " + ru);
+        }
     }
 
     public void unregister(Updater updater, UpdateType type, UpdateMethod method) {
@@ -575,7 +587,10 @@ public class ConsoleUpdateManager implements UpdateManager {
         RegisteredChecker rc = new RegisteredChecker(updater, type, method, priority);
         if (_log.shouldLog(Log.INFO))
             _log.info("Registering " + rc);
-        _registeredCheckers.add(rc);
+        if (!_registeredCheckers.add(rc)) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Duplicate registration " + rc);
+        }
     }
 
     public void unregister(Checker updater, UpdateType type, UpdateMethod method) {