code: public void cleanupProjectArtifacts(final int projectId) {
      if (!isCleanupPermitted()) {
        return;
      }
      final Set<String> allResourceIds = findResourceIdsToDelete(projectId);
      if (allResourceIds.size() == 0) {
        return;
      }
  
      log.warn(String.format("Deleting project artifacts [id: %d]: %s", projectId, allResourceIds));
      allResourceIds.forEach(this::delete);
    }
docs: Remove all but last N artifacts as configured by AZKABAN_STORAGE_ARTIFACT_MAX_RETENTION
  
  Since multiple versions can share the same filename, the algo is to collect all filenames and
  from them, remove the latest ones. The remaining ones are deleted by the respective storage.
  
  From the storage perspective, cleanup just needs the {@link Storage#delete(String)} API to
  work.
  
  Failure cases: - If the storage cleanup fails, the cleanup will be attempted again on the next
  upload - If the storage cleanup succeeds and the DB cleanup fails, the DB will be cleaned up in
  the next attempt.
  
  @param projectId project ID