@@ -440,6 +440,8 @@ public static List<ACL> getNimbusAcls(Map<String, Object> conf) {
440440 private final TimeCacheMap <String , WritableByteChannel > uploaders ;
441441 private final BlobStore blobStore ;
442442 private final TopoCache topoCache ;
443+ //When a dependency blob was first seen unreferenced by any topology, only used by the cleanup pass.
444+ private final Map <String , Long > orphanedDependencyKeysDetectedMs = new HashMap <>();
443445 @ SuppressWarnings ("deprecation" )
444446 private final TimeCacheMap <String , BufferInputStream > blobDownloaders ;
445447 @ SuppressWarnings ("deprecation" )
@@ -775,8 +777,18 @@ static List<String> getKeyListFromId(Map<String, Object> conf, String id) {
775777
776778 public static int getVersionForKey (String key , NimbusInfo nimbusInfo ,
777779 CuratorFramework zkClient ) throws KeyNotFoundException {
780+ return getVersionForKey (key , nimbusInfo , zkClient , true );
781+ }
782+
783+ /**
784+ * Get the version to register the copy of a blob held by a nimbus under.
785+ *
786+ * @see KeySequenceNumber#getKeySequenceNumber(CuratorFramework, boolean)
787+ */
788+ public static int getVersionForKey (String key , NimbusInfo nimbusInfo ,
789+ CuratorFramework zkClient , boolean mayCreateKey ) throws KeyNotFoundException {
778790 KeySequenceNumber kseq = new KeySequenceNumber (key , nimbusInfo );
779- return kseq .getKeySequenceNumber (zkClient );
791+ return kseq .getKeySequenceNumber (zkClient , mayCreateKey );
780792 }
781793
782794 private static StormTopology readStormTopology (String topoId , TopoCache tc ) throws KeyNotFoundException , AuthorizationException ,
@@ -3095,6 +3107,45 @@ public void forceDeleteTopoDistDir(String topoId) throws IOException {
30953107 Utils .forceDelete (ServerConfigUtils .masterStormDistRoot (conf , topoId ));
30963108 }
30973109
3110+ /**
3111+ * Remove the dependency blobs that no topology has referred to for longer than the inbox jar expiration.
3112+ *
3113+ * <p>{@link #rmDependencyBlobsInTopology} only runs in the pass that cleans up the owning topology, and a dependency
3114+ * blob key carries no topology id, so a blob that outlives that pass can never be traced back to it. That happens
3115+ * when a submission fails after its dependencies were uploaded, or when another nimbus still holds a copy of the blob
3116+ * and it is downloaded back after it was removed here. This sweep reclaims those.
3117+ *
3118+ * <p>Only keys that are provably unique to one topology are considered: an older client that finds a shareable key
3119+ * in the store does not upload it again but refers to it, so such a key may be about to be used. A client uploads the
3120+ * dependencies before it submits the topology, so a key is only removed once it has been seen unreferenced for
3121+ * {@link DaemonConfig#NIMBUS_INBOX_JAR_EXPIRATION_SECS}, the time nimbus gives an uploaded topology jar to be
3122+ * submitted. When a key was first seen unreferenced is only kept in memory, so a new leader starts the wait over.
3123+ *
3124+ * @param referenced the dependency blob keys referenced by topologies that are not being cleaned up
3125+ */
3126+ @ VisibleForTesting
3127+ void sweepOrphanedDependencyBlobs (Set <String > referenced ) {
3128+ try {
3129+ long graceMs = TimeUnit .SECONDS .toMillis (
3130+ ObjectReader .getInt (conf .get (DaemonConfig .NIMBUS_INBOX_JAR_EXPIRATION_SECS ), 3600 ));
3131+ long nowMs = Time .currentTimeMillis ();
3132+ Set <String > orphaned = blobStore .filterAndListKeys (
3133+ key -> isProvablyUniqueDependencyKey (key ) && !referenced .contains (key ) ? key : null );
3134+ //Forget the keys that are gone or referenced again, so that being orphaned later waits the full time again.
3135+ orphanedDependencyKeysDetectedMs .keySet ().retainAll (orphaned );
3136+ for (String key : orphaned ) {
3137+ long unreferencedMs = nowMs - orphanedDependencyKeysDetectedMs .computeIfAbsent (key , k -> nowMs );
3138+ if (unreferencedMs >= graceMs ) {
3139+ LOG .info ("Removing dependency blob {}, no topology has referred to it for {} ms" , key , unreferencedMs );
3140+ rmBlobKey (blobStore , key , stormClusterState );
3141+ orphanedDependencyKeysDetectedMs .remove (key );
3142+ }
3143+ }
3144+ } catch (Exception e ) {
3145+ LOG .warn ("Could not sweep the dependency blobs that no topology refers to" , e );
3146+ }
3147+ }
3148+
30983149 /**
30993150 * Cleanup topologies and Jars.
31003151 */
@@ -3134,6 +3185,12 @@ public void doCleanup() {
31343185 idToExecutors .getAndUpdate (new Dissoc <>(topoId ));
31353186 }
31363187
3188+ //Catches the dependency blobs that outlived the pass that cleaned up their topology. Without knowing the
3189+ //references nothing can be told to be unused, so nothing is swept then.
3190+ if (stillReferenced != null ) {
3191+ sweepOrphanedDependencyBlobs (stillReferenced );
3192+ }
3193+
31373194 long cleanupDurationMs = Time .deltaMs (cleanupStartMs );
31383195 if (cleanupDurationMs > 10000 ) {
31393196 LOG .warn ("doCleanup is taking too long, topoIdSelectionDurationMs={}, cleanupDurationMs={}" ,
@@ -4378,7 +4435,9 @@ public void createStateInZookeeper(String key) throws TException {
43784435 BlobStore store = blobStore ;
43794436 NimbusInfo ni = nimbusHostPortInfo ;
43804437 if (store instanceof LocalFsBlobStore ) {
4381- state .setupBlob (key , ni , getVersionForKey (key , ni , zkClient ));
4438+ //A non-leader only registers its copy of a key the leader created. If zookeeper does not know the key
4439+ //any more it was deleted while the copy was downloaded, and registering it would bring it back.
4440+ state .setupBlob (key , ni , getVersionForKey (key , ni , zkClient , isLeader ()));
43824441 }
43834442 LOG .debug ("Created state in zookeeper {} {} {}" , state , store , ni );
43844443 } catch (KeyNotFoundException e ) {
0 commit comments