diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java index 57b0deda2..2c75c3654 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java @@ -17,7 +17,6 @@ import java.security.SecureRandom; import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.HexFormat; import java.util.List; @@ -76,7 +75,6 @@ public class BackupManager { private static final String NUM_OBJECTS_SUMMARY_NAME = MetricsUtil.name(BackupsDb.class, "numObjects"); private static final String BYTES_USED_SUMMARY_NAME = MetricsUtil.name(BackupsDb.class, "bytesUsed"); - private static final String BACKUPS_COUNTER_NAME = MetricsUtil.name(BackupsDb.class, "backups"); private static final String SUCCESS_TAG_NAME = "success"; private static final String FAILURE_REASON_TAG_NAME = "reason"; @@ -152,7 +150,6 @@ public class BackupManager { })); } - /** * Create a form that may be used to upload a backup file for the backupId encoded in the presentation. *

@@ -165,42 +162,12 @@ public class BackupManager { final AuthenticatedBackupUser backupUser) { checkBackupLevel(backupUser, BackupLevel.FREE); checkBackupCredentialType(backupUser, BackupCredentialType.MESSAGES); - final Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS); - final long maxTotalMediaSize = - dynamicConfigurationManager.getConfiguration().getBackupConfiguration().maxTotalMediaSize(); // this could race with concurrent updates, but the only effect would be last-writer-wins on the timestamp return backupsDb .addMessageBackup(backupUser) - .thenApply(storedBackupAttributes -> { - final Instant previousRefreshTime = storedBackupAttributes.lastRefresh(); - // Only publish a metric update once per day - if (previousRefreshTime.isBefore(today)) { - final Tags tags = Tags.of( - UserAgentTagUtil.getPlatformTag(backupUser.userAgent()), - Tag.of("tier", backupUser.backupLevel().name())); - - DistributionSummary.builder(NUM_OBJECTS_SUMMARY_NAME) - .tags(tags) - .publishPercentileHistogram() - .register(Metrics.globalRegistry) - .record(storedBackupAttributes.numObjects()); - DistributionSummary.builder(BYTES_USED_SUMMARY_NAME) - .tags(tags) - .publishPercentileHistogram() - .register(Metrics.globalRegistry) - .record(storedBackupAttributes.bytesUsed()); - - // Report that the backup is out of quota if it cannot store a max size media object - final boolean quotaExhausted = storedBackupAttributes.bytesUsed() >= - (maxTotalMediaSize - BackupManager.MAX_MEDIA_OBJECT_SIZE); - - Metrics.counter(BACKUPS_COUNTER_NAME, - tags.and("quotaExhausted", String.valueOf(quotaExhausted))) - .increment(); - } - return cdn3BackupCredentialGenerator.generateUpload(cdnMessageBackupName(backupUser)); - }); + .thenApply(_ -> + cdn3BackupCredentialGenerator.generateUpload(cdnMessageBackupName(backupUser))); } public CompletableFuture createTemporaryAttachmentUploadDescriptor( @@ -226,7 +193,33 @@ public class BackupManager { public CompletableFuture ttlRefresh(final AuthenticatedBackupUser backupUser) { checkBackupLevel(backupUser, BackupLevel.FREE); // update message backup TTL - return backupsDb.ttlRefresh(backupUser); + return backupsDb.ttlRefresh(backupUser).thenAccept(storedBackupAttributes -> { + if (backupUser.credentialType() == BackupCredentialType.MEDIA) { + final long maxTotalMediaSize = + dynamicConfigurationManager.getConfiguration().getBackupConfiguration().maxTotalMediaSize(); + + // Report that the backup is out of quota if it cannot store a max size media object + final boolean quotaExhausted = storedBackupAttributes.bytesUsed() >= + (maxTotalMediaSize - BackupManager.MAX_MEDIA_OBJECT_SIZE); + + final Tags tags = Tags.of( + UserAgentTagUtil.getPlatformTag(backupUser.userAgent()), + Tag.of("type", backupUser.credentialType().name()), + Tag.of("tier", backupUser.backupLevel().name()), + Tag.of("quotaExhausted", String.valueOf(quotaExhausted))); + + DistributionSummary.builder(NUM_OBJECTS_SUMMARY_NAME) + .tags(tags) + .publishPercentileHistogram() + .register(Metrics.globalRegistry) + .record(storedBackupAttributes.numObjects()); + DistributionSummary.builder(BYTES_USED_SUMMARY_NAME) + .tags(tags) + .publishPercentileHistogram() + .register(Metrics.globalRegistry) + .record(storedBackupAttributes.bytesUsed()); + } + }); } public record BackupInfo(int cdn, String backupSubdir, String mediaSubdir, String messageBackupKey, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupsDb.java b/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupsDb.java index 888d77c94..3fbf7347b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupsDb.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupsDb.java @@ -242,7 +242,7 @@ public class BackupsDb { * * @param backupUser an already authorized backup user */ - CompletableFuture ttlRefresh(final AuthenticatedBackupUser backupUser) { + CompletableFuture ttlRefresh(final AuthenticatedBackupUser backupUser) { final Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS); // update message backup TTL return dynamoClient.updateItem(UpdateBuilder.forUser(backupTableName, backupUser) @@ -250,7 +250,7 @@ public class BackupsDb { .updateItemBuilder() .returnValues(ReturnValue.ALL_OLD) .build()) - .thenRun(Util.NOOP); + .thenApply(updateItemResponse -> fromItem(updateItemResponse.attributes())); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/backup/BackupsDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/backup/BackupsDbTest.java index ced11f7aa..caa2e7a3f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/backup/BackupsDbTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/backup/BackupsDbTest.java @@ -54,22 +54,30 @@ public class BackupsDbTest { @Test public void trackMediaStats() { final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupCredentialType.MEDIA, BackupLevel.PAID); - // add at least one message backup so we can describe it - backupsDb.addMessageBackup(backupUser).join(); int total = 0; for (int i = 0; i < 5; i++) { this.backupsDb.trackMedia(backupUser, 1, i).join(); total += i; final BackupsDb.BackupDescription description = this.backupsDb.describeBackup(backupUser).join(); - assertThat(description.mediaUsedSpace().get()).isEqualTo(total); + final StoredBackupAttributes storedAttrs = backupsDb.ttlRefresh(backupUser).join(); + assertThat(description.mediaUsedSpace().orElseThrow()) + .isEqualTo(total) + .isEqualTo(storedAttrs.bytesUsed()); + assertThat(storedAttrs.numObjects()).isEqualTo(i + 1); } + for (int i = 0; i < 5; i++) { this.backupsDb.trackMedia(backupUser, -1, -i).join(); total -= i; final BackupsDb.BackupDescription description = this.backupsDb.describeBackup(backupUser).join(); - assertThat(description.mediaUsedSpace().get()).isEqualTo(total); + final StoredBackupAttributes storedAttrs = backupsDb.ttlRefresh(backupUser).join(); + assertThat(description.mediaUsedSpace().orElseThrow()) + .isEqualTo(total) + .isEqualTo(storedAttrs.bytesUsed()); + assertThat(storedAttrs.numObjects()).isEqualTo(5 - i - 1); } + } @ParameterizedTest