2222import org .elasticsearch .cluster .ClusterStateAckListener ;
2323import org .elasticsearch .cluster .ClusterStateListener ;
2424import org .elasticsearch .cluster .SimpleBatchedAckListenerTaskExecutor ;
25+ import org .elasticsearch .cluster .metadata .IndexMetadata ;
2526import org .elasticsearch .cluster .metadata .Metadata ;
2627import org .elasticsearch .cluster .metadata .ProjectId ;
2728import org .elasticsearch .cluster .metadata .ProjectMetadata ;
@@ -424,7 +425,11 @@ public void clusterChanged(ClusterChangedEvent event) {
424425 cancelJob ();
425426 }
426427 }
427- if (samples .isEmpty ()) {
428+ if (isMaster == false && samples .isEmpty ()) {
429+ /*
430+ * The remaining code potentially removes entries from samples, and delete configurations if this is the master. So if this is
431+ * not the master and has no sampling configurations, we can just bail out here.
432+ */
428433 return ;
429434 }
430435 // We want to remove any samples if their sampling configuration has been deleted or modified.
@@ -438,46 +443,75 @@ public void clusterChanged(ClusterChangedEvent event) {
438443 event .previousState ().metadata ().projects ().keySet ()
439444 );
440445 for (ProjectId projectId : allProjectIds ) {
441- if (event .customMetadataChanged (projectId , SamplingMetadata .TYPE )) {
442- Map <String , SamplingConfiguration > oldSampleConfigsMap = Optional .ofNullable (
443- event .previousState ().metadata ().projects ().get (projectId )
444- )
445- .map (p -> (SamplingMetadata ) p .custom (SamplingMetadata .TYPE ))
446- .map (SamplingMetadata ::getIndexToSamplingConfigMap )
447- .orElse (Map .of ());
448- Map <String , SamplingConfiguration > newSampleConfigsMap = Optional .ofNullable (
449- event .state ().metadata ().projects ().get (projectId )
450- )
451- .map (p -> (SamplingMetadata ) p .custom (SamplingMetadata .TYPE ))
452- .map (SamplingMetadata ::getIndexToSamplingConfigMap )
453- .orElse (Map .of ());
454- Set <String > indicesWithRemovedConfigs = new HashSet <>(oldSampleConfigsMap .keySet ());
455- indicesWithRemovedConfigs .removeAll (newSampleConfigsMap .keySet ());
456- /*
457- * These index names no longer have sampling configurations associated with them. So we remove their samples. We are OK
458- * with the fact that we have a race condition here -- it is possible that in maybeSample() the configuration still
459- * exists but before the sample is read from samples it is deleted by this method and gets recreated. In the worst case
460- * we'll have a small amount of memory being used until the sampling configuration is recreated or the TTL checker
461- * reclaims it. The advantage is that we can avoid locking here, which could slow down ingestion.
462- */
463- for (String indexName : indicesWithRemovedConfigs ) {
464- logger .debug ("Removing sample info for {} because its configuration has been removed" , indexName );
465- samples .remove (new ProjectIndex (projectId , indexName ));
466- }
467- /*
468- * Now we check if any of the sampling configurations have changed. If they have, we remove the existing sample. Same as
469- * above, we have a race condition here that we can live with.
470- */
471- for (Map .Entry <String , SamplingConfiguration > entry : newSampleConfigsMap .entrySet ()) {
472- String indexName = entry .getKey ();
473- if (entry .getValue ().equals (oldSampleConfigsMap .get (indexName )) == false ) {
474- logger .debug ("Removing sample info for {} because its configuration has changed" , indexName );
475- samples .remove (new ProjectIndex (projectId , indexName ));
476- }
477- }
446+ maybeRemoveStaleSamples (event , projectId );
447+ // Now delete configurations for any indices that have been deleted:
448+ if (isMaster ) {
449+ maybeDeleteSamplingConfigurations (event , projectId );
450+ }
451+ }
452+ }
453+ }
454+
455+ /*
456+ * This method removes any samples from the samples Map that have had their sampling configuration removed or changed in this event.
457+ */
458+ private void maybeRemoveStaleSamples (ClusterChangedEvent event , ProjectId projectId ) {
459+ if (samples .isEmpty () == false && event .customMetadataChanged (projectId , SamplingMetadata .TYPE )) {
460+ Map <String , SamplingConfiguration > oldSampleConfigsMap = Optional .ofNullable (
461+ event .previousState ().metadata ().projects ().get (projectId )
462+ )
463+ .map (p -> (SamplingMetadata ) p .custom (SamplingMetadata .TYPE ))
464+ .map (SamplingMetadata ::getIndexToSamplingConfigMap )
465+ .orElse (Map .of ());
466+ Map <String , SamplingConfiguration > newSampleConfigsMap = Optional .ofNullable (event .state ().metadata ().projects ().get (projectId ))
467+ .map (p -> (SamplingMetadata ) p .custom (SamplingMetadata .TYPE ))
468+ .map (SamplingMetadata ::getIndexToSamplingConfigMap )
469+ .orElse (Map .of ());
470+ Set <String > indicesWithRemovedConfigs = new HashSet <>(oldSampleConfigsMap .keySet ());
471+ indicesWithRemovedConfigs .removeAll (newSampleConfigsMap .keySet ());
472+ /*
473+ * These index names no longer have sampling configurations associated with them. So we remove their samples. We are OK
474+ * with the fact that we have a race condition here -- it is possible that in maybeSample() the configuration still
475+ * exists but before the sample is read from samples it is deleted by this method and gets recreated. In the worst case
476+ * we'll have a small amount of memory being used until the sampling configuration is recreated or the TTL checker
477+ * reclaims it. The advantage is that we can avoid locking here, which could slow down ingestion.
478+ */
479+ for (String indexName : indicesWithRemovedConfigs ) {
480+ logger .debug ("Removing sample info for {} because its configuration has been removed" , indexName );
481+ samples .remove (new ProjectIndex (projectId , indexName ));
482+ }
483+ /*
484+ * Now we check if any of the sampling configurations have changed. If they have, we remove the existing sample. Same as
485+ * above, we have a race condition here that we can live with.
486+ */
487+ for (Map .Entry <String , SamplingConfiguration > entry : newSampleConfigsMap .entrySet ()) {
488+ String indexName = entry .getKey ();
489+ if (oldSampleConfigsMap .containsKey (indexName ) && entry .getValue ().equals (oldSampleConfigsMap .get (indexName )) == false ) {
490+ logger .debug ("Removing sample info for {} because its configuration has changed" , indexName );
491+ samples .remove (new ProjectIndex (projectId , indexName ));
492+ }
493+ }
494+ }
495+ }
496+
497+ /*
498+ * This method deletes the sampling configuration for any index that has been deleted in this event.
499+ */
500+ private void maybeDeleteSamplingConfigurations (ClusterChangedEvent event , ProjectId projectId ) {
501+ ProjectMetadata currentProject = event .state ().metadata ().projects ().get (projectId );
502+ ProjectMetadata previousProject = event .previousState ().metadata ().projects ().get (projectId );
503+ if (currentProject == null || previousProject == null ) {
504+ return ;
505+ }
506+ if (currentProject .indices () != previousProject .indices ()) {
507+ for (IndexMetadata index : previousProject .indices ().values ()) {
508+ IndexMetadata current = currentProject .index (index .getIndex ());
509+ if (current == null ) {
510+ String indexName = index .getIndex ().getName ();
511+ logger .debug ("Deleting sample configuration for {} because the index has been deleted" , indexName );
512+ deleteSampleConfiguration (projectId , indexName );
478513 }
479514 }
480- // TODO: If an index has been deleted, we want to remove its sampling configuration
481515 }
482516 }
483517
0 commit comments