Skip to content

Commit 4ebad2c

Browse files
authored
Add a framework for running experiments to improve push notification reliability
1 parent 1fe6dac commit 4ebad2c

16 files changed

+1489
-8
lines changed

service/config/sample.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ dynamoDbTables:
116116
tableName: Example_Profiles
117117
pushChallenge:
118118
tableName: Example_PushChallenge
119+
pushNotificationExperimentSamples:
120+
tableName: Example_PushNotificationExperimentSamples
119121
redeemedReceipts:
120122
tableName: Example_RedeemedReceipts
121123
expiration: P30D # Duration of time until rows expire

service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbTables.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public Duration getExpiration() {
6363
private final Table phoneNumberIdentifiers;
6464
private final Table profiles;
6565
private final Table pushChallenge;
66+
private final Table pushNotificationExperimentSamples;
6667
private final TableWithExpiration redeemedReceipts;
6768
private final TableWithExpiration registrationRecovery;
6869
private final Table remoteConfig;
@@ -88,6 +89,7 @@ public DynamoDbTables(
8889
@JsonProperty("phoneNumberIdentifiers") final Table phoneNumberIdentifiers,
8990
@JsonProperty("profiles") final Table profiles,
9091
@JsonProperty("pushChallenge") final Table pushChallenge,
92+
@JsonProperty("pushNotificationExperimentSamples") final Table pushNotificationExperimentSamples,
9193
@JsonProperty("redeemedReceipts") final TableWithExpiration redeemedReceipts,
9294
@JsonProperty("registrationRecovery") final TableWithExpiration registrationRecovery,
9395
@JsonProperty("remoteConfig") final Table remoteConfig,
@@ -112,6 +114,7 @@ public DynamoDbTables(
112114
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
113115
this.profiles = profiles;
114116
this.pushChallenge = pushChallenge;
117+
this.pushNotificationExperimentSamples = pushNotificationExperimentSamples;
115118
this.redeemedReceipts = redeemedReceipts;
116119
this.registrationRecovery = registrationRecovery;
117120
this.remoteConfig = remoteConfig;
@@ -217,6 +220,12 @@ public Table getPushChallenge() {
217220
return pushChallenge;
218221
}
219222

223+
@NotNull
224+
@Valid
225+
public Table getPushNotificationExperimentSamples() {
226+
return pushNotificationExperimentSamples;
227+
}
228+
220229
@NotNull
221230
@Valid
222231
public TableWithExpiration getRedeemedReceipts() {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.whispersystems.textsecuregcm.experiment;
2+
3+
import org.whispersystems.textsecuregcm.storage.Account;
4+
import org.whispersystems.textsecuregcm.storage.Device;
5+
import javax.annotation.Nullable;
6+
import java.util.concurrent.CompletableFuture;
7+
8+
/**
9+
* A push notification selects for eligible devices, applies a control or experimental treatment, and provides a
10+
* mechanism for comparing device states before and after receiving the treatment.
11+
*
12+
* @param <T> the type of state object stored for this experiment
13+
*/
14+
public interface PushNotificationExperiment<T> {
15+
16+
/**
17+
* Returns the unique name of this experiment.
18+
*
19+
* @return the unique name of this experiment
20+
*/
21+
String getExperimentName();
22+
23+
/**
24+
* Tests whether a device is eligible for this experiment. An eligible device may be assigned to either the control
25+
* or experiment group within an experiment. Ineligible devices will not participate in the experiment in any way.
26+
*
27+
* @param account the account to which the device belongs
28+
* @param device the device to test for eligibility in this experiment
29+
*
30+
* @return a future that yields a boolean value indicating whether the target device is eligible for this experiment
31+
*/
32+
CompletableFuture<Boolean> isDeviceEligible(Account account, Device device);
33+
34+
/**
35+
* Generates an experiment specific state "snapshot" of the given device. Experiment results are generally evaluated
36+
* by comparing a device's state before a treatment is applied and its state after the treatment is applied.
37+
*
38+
* @param account the account to which the device belongs
39+
* @param device the device for which to generate a state "snapshot"
40+
*
41+
* @return an experiment-specific state "snapshot" of the given device
42+
*/
43+
T getState(@Nullable Account account, @Nullable Device device);
44+
45+
/**
46+
* Applies a control treatment to the given device. In many cases (and by default) no action is taken for devices in
47+
* the control group.
48+
*
49+
* @param account the account to which the device belongs
50+
* @param device the device to which to apply the control treatment for this experiment
51+
*
52+
* @return a future that completes when the control treatment has been applied for the given device
53+
*/
54+
default CompletableFuture<Void> applyControlTreatment(Account account, Device device) {
55+
return CompletableFuture.completedFuture(null);
56+
};
57+
58+
/**
59+
* Applies an experimental treatment to the given device. This generally involves sending or scheduling a specific
60+
* type of push notification for the given device.
61+
*
62+
* @param account the account to which the device belongs
63+
* @param device the device to which to apply the experimental treatment for this experiment
64+
*
65+
* @return a future that completes when the experimental treatment has been applied for the given device
66+
*/
67+
CompletableFuture<Void> applyExperimentTreatment(Account account, Device device);
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.whispersystems.textsecuregcm.experiment;
2+
3+
public record PushNotificationExperimentSample<T>(boolean inExperimentGroup, T initialState, T finalState) {
4+
}

0 commit comments

Comments
 (0)