aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From wfar...@apache.org
Subject incubator-aurora git commit: Uniquely identify job updates in the database by JobUpdateKey.
Date Wed, 18 Feb 2015 01:35:20 GMT
Repository: incubator-aurora
Updated Branches:
  refs/heads/master 4b43305b3 -> ec66a5ec6


Uniquely identify job updates in the database by JobUpdateKey.

Bugs closed: AURORA-1093

Reviewed at https://reviews.apache.org/r/31136/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/ec66a5ec
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/ec66a5ec
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/ec66a5ec

Branch: refs/heads/master
Commit: ec66a5ec68a825235e672843c3c6aaeeba2e1cdd
Parents: 4b43305
Author: Bill Farner <wfarner@apache.org>
Authored: Tue Feb 17 17:33:10 2015 -0800
Committer: Bill Farner <wfarner@apache.org>
Committed: Tue Feb 17 17:33:10 2015 -0800

----------------------------------------------------------------------
 .../thrift/org/apache/aurora/gen/api.thrift     |   4 +
 .../scheduler/storage/db/DBJobUpdateStore.java  | 141 +++++++++++-----
 .../db/JobInstanceUpdateEventMapper.java        |   5 +-
 .../storage/db/JobUpdateDetailsMapper.java      |  74 +++++----
 .../storage/db/JobUpdateEventMapper.java        |   5 +-
 .../scheduler/storage/db/PruneVictim.java       |  40 +++++
 .../storage/db/JobInstanceUpdateEventMapper.xml |   8 +-
 .../storage/db/JobUpdateDetailsMapper.xml       | 160 +++++++++++++------
 .../storage/db/JobUpdateEventMapper.xml         |   8 +-
 .../aurora/scheduler/storage/db/schema.sql      |  19 +--
 .../storage/db/DBJobUpdateStoreTest.java        |  27 ++++
 11 files changed, 343 insertions(+), 148 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/api/src/main/thrift/org/apache/aurora/gen/api.thrift
----------------------------------------------------------------------
diff --git a/api/src/main/thrift/org/apache/aurora/gen/api.thrift b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
index 2a77f28..11116f6 100644
--- a/api/src/main/thrift/org/apache/aurora/gen/api.thrift
+++ b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
@@ -718,6 +718,7 @@ struct JobUpdateState {
 
 /** Summary of the job update including job key, user and current state. */
 struct JobUpdateSummary {
+  // TODO(wfarner): As part of AURORA-1093, add a JobUpdateKey field, deprecate updateId
and jobKey.
   /** Update ID. */
   1: string updateId
 
@@ -786,6 +787,9 @@ struct JobUpdateQuery {
   /** Job role. */
   2: string role
 
+  /** Unique identifier for a job update. */
+  8: JobUpdateKey key
+
   /** Job key. */
   3: JobKey jobKey
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java b/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
index 39f72cc..34c4ab5 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
@@ -21,19 +21,23 @@ import javax.inject.Inject;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.twitter.common.base.MorePreconditions;
 
 import org.apache.aurora.gen.JobUpdate;
 import org.apache.aurora.gen.JobUpdateInstructions;
+import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.storage.StoredJobUpdateDetails;
 import org.apache.aurora.scheduler.storage.JobUpdateStore;
+import org.apache.aurora.scheduler.storage.Storage.StorageException;
 import org.apache.aurora.scheduler.storage.entities.IInstanceTaskConfig;
 import org.apache.aurora.scheduler.storage.entities.IJobInstanceUpdateEvent;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateDetails;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateEvent;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateInstructions;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateQuery;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
 import org.apache.aurora.scheduler.storage.entities.IRange;
@@ -75,12 +79,14 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
           "Missing both initial and desired states. At least one is required.");
     }
 
-    jobKeyMapper.merge(update.getSummary().getJobKey().newBuilder());
+    IJobUpdateSummary summary = update.getSummary();
+    jobKeyMapper.merge(summary.getJobKey().newBuilder());
     detailsMapper.insert(update.newBuilder());
 
-    String updateId = update.getSummary().getUpdateId();
+    IJobUpdateKey key = IJobUpdateKey.build(
+        new JobUpdateKey(summary.getJobKey().newBuilder(), summary.getUpdateId()));
     if (lockToken.isPresent()) {
-      detailsMapper.insertLockToken(updateId, lockToken.get());
+      detailsMapper.insertLockToken(key, lockToken.get());
     }
 
     // Insert optional instance update overrides.
@@ -88,20 +94,20 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
         update.getInstructions().getSettings().getUpdateOnlyTheseInstances();
 
     if (!instanceOverrides.isEmpty()) {
-      detailsMapper.insertInstanceOverrides(updateId, IRange.toBuildersSet(instanceOverrides));
+      detailsMapper.insertInstanceOverrides(key, IRange.toBuildersSet(instanceOverrides));
     }
 
     // Insert desired state task config and instance mappings.
     if (update.getInstructions().isSetDesiredState()) {
       IInstanceTaskConfig desired = update.getInstructions().getDesiredState();
       detailsMapper.insertTaskConfig(
-          updateId,
+          key,
           desired.getTask().newBuilder(),
           true,
           new InsertResult());
 
       detailsMapper.insertDesiredInstances(
-          updateId,
+          key,
           IRange.toBuildersSet(MorePreconditions.checkNotBlank(desired.getInstances())));
     }
 
@@ -109,7 +115,7 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
     if (!update.getInstructions().getInitialState().isEmpty()) {
       for (IInstanceTaskConfig config : update.getInstructions().getInitialState()) {
         InsertResult result = new InsertResult();
-        detailsMapper.insertTaskConfig(updateId, config.getTask().newBuilder(), false, result);
+        detailsMapper.insertTaskConfig(key, config.getTask().newBuilder(), false, result);
 
         detailsMapper.insertTaskConfigInstances(
             result.getId(),
@@ -121,13 +127,23 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
   @Timed("job_update_store_save_event")
   @Override
   public void saveJobUpdateEvent(IJobUpdateEvent event, String updateId) {
-    jobEventMapper.insert(updateId, event.newBuilder());
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      jobEventMapper.insert(key.get(), event.newBuilder());
+    } else {
+      throw new StorageException("No update to associate with update ID " + updateId);
+    }
   }
 
   @Timed("job_update_store_save_instance_event")
   @Override
   public void saveJobInstanceUpdateEvent(IJobInstanceUpdateEvent event, String updateId)
{
-    instanceEventMapper.insert(event.newBuilder(), updateId);
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      instanceEventMapper.insert(event.newBuilder(), key.get());
+    } else {
+      throw new StorageException("No update to associate with update ID " + updateId);
+    }
   }
 
   @Timed("job_update_store_delete_all")
@@ -136,6 +152,21 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
     detailsMapper.truncate();
   }
 
+  private static final Function<PruneVictim, Long> GET_ROW_ID = new Function<PruneVictim,
Long>() {
+    @Override
+    public Long apply(PruneVictim victim) {
+      return victim.getRowId();
+    }
+  };
+
+  private static final Function<PruneVictim, String> GET_UPDATE_ID =
+      new Function<PruneVictim, String>() {
+        @Override
+        public String apply(PruneVictim victim) {
+          return victim.getUpdate().getId();
+        }
+      };
+
   @Timed("job_update_store_prune_history")
   @Override
   public Set<String> pruneHistory(int perJobRetainCount, long historyPruneThresholdMs)
{
@@ -146,14 +177,16 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
         historyPruneThresholdMs);
 
     for (Long jobKeyId : jobKeyIdsToPrune) {
-      Set<String> pruneVictims = detailsMapper.selectPruneVictims(
+      Set<PruneVictim> pruneVictims = detailsMapper.selectPruneVictims(
           jobKeyId,
           perJobRetainCount,
           historyPruneThresholdMs);
 
-      detailsMapper.deleteCompletedUpdates(pruneVictims);
-      pruned.addAll(pruneVictims);
+      detailsMapper.deleteCompletedUpdates(
+          FluentIterable.from(pruneVictims).transform(GET_ROW_ID).toSet());
+      pruned.addAll(FluentIterable.from(pruneVictims).transform(GET_UPDATE_ID));
     }
+
     return pruned.build();
   }
 
@@ -179,37 +212,52 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
   @Timed("job_update_store_fetch_details")
   @Override
   public Optional<IJobUpdateDetails> fetchJobUpdateDetails(final String updateId) {
-    return Optional.fromNullable(detailsMapper.selectDetails(updateId))
-        .transform(new Function<StoredJobUpdateDetails, IJobUpdateDetails>() {
-          @Override
-          public IJobUpdateDetails apply(StoredJobUpdateDetails input) {
-            return IJobUpdateDetails.build(input.getDetails());
-          }
-        });
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      return Optional.fromNullable(detailsMapper.selectDetails(key.get()))
+          .transform(new Function<StoredJobUpdateDetails, IJobUpdateDetails>() {
+            @Override
+            public IJobUpdateDetails apply(StoredJobUpdateDetails input) {
+              return IJobUpdateDetails.build(input.getDetails());
+            }
+          });
+    } else {
+      return Optional.absent();
+    }
   }
 
   @Timed("job_update_store_fetch_update")
   @Override
   public Optional<IJobUpdate> fetchJobUpdate(String updateId) {
-    return Optional.fromNullable(detailsMapper.selectUpdate(updateId))
-        .transform(new Function<JobUpdate, IJobUpdate>() {
-          @Override
-          public IJobUpdate apply(JobUpdate input) {
-            return IJobUpdate.build(input);
-          }
-        });
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      return Optional.fromNullable(detailsMapper.selectUpdate(key.get()))
+          .transform(new Function<JobUpdate, IJobUpdate>() {
+            @Override
+            public IJobUpdate apply(JobUpdate input) {
+              return IJobUpdate.build(input);
+            }
+          });
+    } else {
+      return Optional.absent();
+    }
   }
 
   @Timed("job_update_store_fetch_instructions")
   @Override
   public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(String updateId)
{
-    return Optional.fromNullable(detailsMapper.selectInstructions(updateId))
-        .transform(new Function<JobUpdateInstructions, IJobUpdateInstructions>() {
-          @Override
-          public IJobUpdateInstructions apply(JobUpdateInstructions input) {
-            return IJobUpdateInstructions.build(input);
-          }
-        });
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      return Optional.fromNullable(detailsMapper.selectInstructions(key.get()))
+          .transform(new Function<JobUpdateInstructions, IJobUpdateInstructions>()
{
+            @Override
+            public IJobUpdateInstructions apply(JobUpdateInstructions input) {
+              return IJobUpdateInstructions.build(input);
+            }
+          });
+    } else {
+      return Optional.absent();
+    }
   }
 
   @Timed("job_update_store_fetch_all_details")
@@ -218,19 +266,34 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
     return ImmutableSet.copyOf(detailsMapper.selectAllDetails());
   }
 
+  private Optional<IJobUpdateKey> fetchUpdateKey(String updateId) {
+    return Optional.fromNullable(detailsMapper.selectUpdateKey(updateId))
+        .transform(IJobUpdateKey.FROM_BUILDER);
+  }
+
   @Timed("job_update_store_get_lock_token")
   @Override
   public Optional<String> getLockToken(String updateId) {
-    // We assume here that cascading deletes will cause a lock-update associative row to
disappear
-    // when the lock is invalidated.  This further assumes that a lock row is deleted when
a lock
-    // is no longer valid.
-    return Optional.fromNullable(detailsMapper.selectLockToken(updateId));
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      // We assume here that cascading deletes will cause a lock-update associative row to
disappear
+      // when the lock is invalidated.  This further assumes that a lock row is deleted when
a lock
+      // is no longer valid.
+      return Optional.fromNullable(detailsMapper.selectLockToken(key.get()));
+    } else {
+      return Optional.absent();
+    }
   }
 
   @Timed("job_update_store_fetch_instance_events")
   @Override
   public List<IJobInstanceUpdateEvent> fetchInstanceEvents(String updateId, int instanceId)
{
-    return IJobInstanceUpdateEvent.listFromBuilders(
-        detailsMapper.selectInstanceUpdateEvents(updateId, instanceId));
+    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
+    if (key.isPresent()) {
+      return IJobInstanceUpdateEvent.listFromBuilders(
+          detailsMapper.selectInstanceUpdateEvents(key.get(), instanceId));
+    } else {
+      return ImmutableList.of();
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/java/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.java
b/src/main/java/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.java
index d5dd5a5..4fb33bd 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.java
@@ -14,6 +14,7 @@
 package org.apache.aurora.scheduler.storage.db;
 
 import org.apache.aurora.gen.JobInstanceUpdateEvent;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -27,7 +28,7 @@ interface JobInstanceUpdateEventMapper {
    * Inserts a new job instance update event into the database.
    *
    * @param event Event to insert.
-   * @param updateId Update ID of the event.
+   * @param key Update key of the event.
    */
-  void insert(@Param("event") JobInstanceUpdateEvent event, @Param("updateId") String updateId);
+  void insert(@Param("event") JobInstanceUpdateEvent event, @Param("key") IJobUpdateKey key);
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.java
b/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.java
index 028eb7c..b1b6f11 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.java
@@ -21,11 +21,13 @@ import javax.annotation.Nullable;
 import org.apache.aurora.gen.JobInstanceUpdateEvent;
 import org.apache.aurora.gen.JobUpdate;
 import org.apache.aurora.gen.JobUpdateInstructions;
+import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.JobUpdateQuery;
 import org.apache.aurora.gen.JobUpdateSummary;
 import org.apache.aurora.gen.Range;
 import org.apache.aurora.gen.TaskConfig;
 import org.apache.aurora.gen.storage.StoredJobUpdateDetails;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -45,23 +47,23 @@ interface JobUpdateDetailsMapper {
   /**
    * Inserts an association between an update and a lock.
    *
-   * @param updateId Unique update identifier.
+   * @param key Unique update identifier.
    * @param lockToken Unique lock identifier, resulting from
    *        {@link org.apache.aurora.scheduler.storage.entities.ILock#getToken()}.
    */
-  void insertLockToken(@Param("updateId") String updateId, @Param("lockToken") String lockToken);
+  void insertLockToken(@Param("key") IJobUpdateKey key, @Param("lockToken") String lockToken);
 
   /**
    * Inserts a task configuration entry for an update.
    *
-   * @param updateId Update ID to insert task configs for.
+   * @param key Update to insert task configs for.
    * @param taskConfig task configuration to insert.
    * @param isNew Flag to identify if the task config is existing {@code false} or
    *              desired {@code true}.
    * @param result Container for auto-generated ID of the inserted job update row.
    */
   void insertTaskConfig(
-      @Param("updateId") String updateId,
+      @Param("key") IJobUpdateKey key,
       @Param("config") TaskConfig taskConfig,
       @Param("isNew") boolean isNew,
       @Param("result") InsertResult result);
@@ -80,23 +82,19 @@ interface JobUpdateDetailsMapper {
    * Maps update with an optional set of
    * {@link org.apache.aurora.gen.JobUpdateSettings#updateOnlyTheseInstances}.
    *
-   * @param updateId Update ID to store overrides for.
+   * @param key Update to store overrides for.
    * @param ranges Instance ID ranges to associate with an update.
    */
-  void insertInstanceOverrides(
-      @Param("updateId") String updateId,
-      @Param("ranges") Set<Range> ranges);
+  void insertInstanceOverrides(@Param("key") IJobUpdateKey key, @Param("ranges") Set<Range>
ranges);
 
   /**
    * Maps update with a set of instance IDs in
    * {@link org.apache.aurora.gen.JobUpdateInstructions#desiredState}.
    *
-   * @param updateId Update ID to store desired instances for.
+   * @param key Update to store desired instances for.
    * @param ranges Desired instance ID ranges to associate with an update.
    */
-  void insertDesiredInstances(
-      @Param("updateId") String updateId,
-      @Param("ranges") Set<Range> ranges);
+  void insertDesiredInstances(@Param("key") IJobUpdateKey key, @Param("ranges") Set<Range>
ranges);
 
   /**
    * Deletes all updates and events from the database.
@@ -104,11 +102,11 @@ interface JobUpdateDetailsMapper {
   void truncate();
 
   /**
-   * Deletes all updates and events with update ID in {@code updateIds}.
+   * Deletes all updates and events with update ID in {@code updates}.
    *
-   * @param updateIds Update IDs to delete.
+   * @param rowIds Row IDs of updates to delete.
    */
-  void deleteCompletedUpdates(@Param("updateIds") Set<String> updateIds);
+  void deleteCompletedUpdates(@Param("rowIds") Set<Long> rowIds);
 
   /**
    * Selects all distinct job key IDs associated with at least {@code perJobRetainCount}
completed
@@ -116,7 +114,7 @@ interface JobUpdateDetailsMapper {
    *
    * @param perJobRetainCount Number of updates to keep per job.
    * @param historyPruneThresholdMs History pruning timestamp threshold.
-   * @return Job key IDs.
+   * @return Job key database row IDs.
    */
   Set<Long> selectJobKeysForPruning(
       @Param("retainCount") int perJobRetainCount,
@@ -130,9 +128,9 @@ interface JobUpdateDetailsMapper {
    * @param jobKeyId Job key ID to select pruning victims for.
    * @param perJobRetainCount Number of updates to keep per job.
    * @param historyPruneThresholdMs History pruning timestamp threshold.
-   * @return Update IDs to prune.
+   * @return Victims to prune.
    */
-  Set<String> selectPruneVictims(
+  Set<PruneVictim> selectPruneVictims(
       @Param("keyId") long jobKeyId,
       @Param("retainCount") int perJobRetainCount,
       @Param("pruneThresholdMs") long historyPruneThresholdMs);
@@ -147,13 +145,25 @@ interface JobUpdateDetailsMapper {
   List<JobUpdateSummary> selectSummaries(JobUpdateQuery query);
 
   /**
-   * Gets details for the provided {@code updateId}.
+   * Selects an update key by the update ID field contained within it.
+   *
+   * <p>
+   * TODO(wfarner): Remove this in the final phase of AURORA-1093.
+   *
+   * @param updateId Update to search by.
+   * @return The update key that {@code updateId} represents.
+   */
+  @Nullable
+  JobUpdateKey selectUpdateKey(String updateId);
+
+  /**
+   * Gets details for the provided {@code key}.
    *
-   * @param updateId Update ID to get.
+   * @param key Update to get.
    * @return Job update details for the provided update ID, if it exists.
    */
   @Nullable
-  StoredJobUpdateDetails selectDetails(String updateId);
+  StoredJobUpdateDetails selectDetails(@Param("key") IJobUpdateKey key);
 
   /**
    * Gets all job update details matching the provided {@code query}.
@@ -165,22 +175,22 @@ interface JobUpdateDetailsMapper {
   List<StoredJobUpdateDetails> selectDetailsList(JobUpdateQuery query);
 
   /**
-   * Gets job update for the provided {@code updateId}.
+   * Gets job update for the provided {@code update}.
    *
-   * @param updateId Update ID to select by.
+   * @param key Update to select by.
    * @return Job update for the provided update ID, if it exists.
    */
   @Nullable
-  JobUpdate selectUpdate(String updateId);
+  JobUpdate selectUpdate(@Param("key") IJobUpdateKey key);
 
   /**
-   * Gets job update instructions for the provided {@code updateId}.
+   * Gets job update instructions for the provided {@code update}.
    *
-   * @param updateId Update ID to select by.
+   * @param key Update to select by.
    * @return Job update instructions for the provided update ID, if it exists.
    */
   @Nullable
-  JobUpdateInstructions selectInstructions(String updateId);
+  JobUpdateInstructions selectInstructions(@Param("key") IJobUpdateKey key);
 
   /**
    * Gets all stored job update details.
@@ -192,20 +202,20 @@ interface JobUpdateDetailsMapper {
   /**
    * Gets the token associated with an update.
    *
-   * @param updateId Update identifier.
+   * @param key Update identifier.
    * @return The associated lock token, or {@code null} if no association exists.
    */
   @Nullable
-  String selectLockToken(String updateId);
+  String selectLockToken(@Param("key") IJobUpdateKey key);
 
   /**
    * Gets job instance update events for a specific instance within an update.
    *
-   * @param updateId Update identifier.
+   * @param key Update identifier.
    * @param instanceId Instance to fetch events for.
-   * @return Instance events affecting {@code instanceId} within {@code updateId}.
+   * @return Instance events affecting {@code instanceId} within {@code key}.
    */
   List<JobInstanceUpdateEvent> selectInstanceUpdateEvents(
-      @Param("updateId") String updateId,
+      @Param("key") IJobUpdateKey key,
       @Param("instanceId") int instanceId);
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.java
b/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.java
index bbd2f46..d1a3c3f 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.java
@@ -14,6 +14,7 @@
 package org.apache.aurora.scheduler.storage.db;
 
 import org.apache.aurora.gen.JobUpdateEvent;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -26,8 +27,8 @@ interface JobUpdateEventMapper {
   /**
    * Inserts a new job update event into the database.
    *
-   * @param updateId Update ID of the event.
+   * @param key ID of the update associated with the event.
    * @param event Event to insert.
    */
-  void insert(@Param("updateId") String updateId, @Param("event") JobUpdateEvent event);
+  void insert(@Param("key") IJobUpdateKey key, @Param("event") JobUpdateEvent event);
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/java/org/apache/aurora/scheduler/storage/db/PruneVictim.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/PruneVictim.java b/src/main/java/org/apache/aurora/scheduler/storage/db/PruneVictim.java
new file mode 100644
index 0000000..144f5a3
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/PruneVictim.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.db;
+
+import org.apache.aurora.gen.JobUpdateKey;
+
+/**
+ * A job update that should be pruned.
+ */
+public class PruneVictim {
+  private long rowId;
+  private JobUpdateKey update;
+
+  public long getRowId() {
+    return rowId;
+  }
+
+  public JobUpdateKey getUpdate() {
+    return update;
+  }
+
+  public void setRowId(long rowId) {
+    this.rowId = rowId;
+  }
+
+  public void setUpdate(JobUpdateKey update) {
+    this.update = update;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/resources/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.xml
b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.xml
index 1bc2a62..1b58406 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.xml
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobInstanceUpdateEventMapper.xml
@@ -19,16 +19,12 @@
 <mapper namespace="org.apache.aurora.scheduler.storage.db.JobInstanceUpdateEventMapper">
   <insert id="insert">
     INSERT INTO job_instance_update_events (
-      update_id,
+      update_row_id,
       action,
       instance_id,
       timestamp_ms
     ) VALUES (
-      (
-        SELECT ID
-        FROM job_updates
-        WHERE update_id = #{updateId}
-      ),
+      <include refid="org.apache.aurora.scheduler.storage.db.JobUpdateDetailsMapper.select_update_row_id"/>,
       #{event.action, typeHandler=org.apache.aurora.scheduler.storage.db.typehandlers.JobUpdateActionTypeHandler},
       #{event.instanceId},
       #{event.timestampMs}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
index 7fd3a86..7685597 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
@@ -17,11 +17,27 @@
         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.apache.aurora.scheduler.storage.db.JobUpdateDetailsMapper">
-  <sql id="selectUpdateIdentity">
+  <sql id="job_key_inner_join">
+    INNER JOIN job_keys AS j ON j.id = u.job_key_id
+  </sql>
+
+  <sql id="filter_by_update_key">
+    u.update_id = #{key.id}
+    AND j.role = #{key.job.role}
+    AND j.environment = #{key.job.environment}
+    AND j.name = #{key.job.name}
+  </sql>
+
+  <sql id="select_update_row_id">
       (
-        SELECT id
-        FROM job_updates
-        WHERE update_id = #{updateId}
+        SELECT u.id
+        FROM job_updates AS u
+        <!-- Full qualification is needed since this fragment is accessed from outside
of this
+             mapper.  Without full qualification, mybatis looks for job_key_inner_join in
the
+             caller's namespace.  It's unclear if this is a feature or bug in mybatis.
+         -->
+        <include refid="org.apache.aurora.scheduler.storage.db.JobUpdateDetailsMapper.job_key_inner_join"/>
+        WHERE <include refid="org.apache.aurora.scheduler.storage.db.JobUpdateDetailsMapper.filter_by_update_key"/>
       )
   </sql>
 
@@ -61,29 +77,29 @@
 
   <insert id="insertLockToken">
     INSERT INTO job_update_locks (
-      update_id,
+      update_row_id,
       lock_token
     ) VALUES (
-      <include refid="selectUpdateIdentity"/>,
+      <include refid="select_update_row_id"/>,
       #{lockToken}
     )
   </insert>
 
   <insert id="insertTaskConfig" useGeneratedKeys="true" keyColumn="id" keyProperty="result.id">
     INSERT INTO job_update_configs (
-      update_id,
+      update_row_id,
       task_config,
       is_new
     ) VALUES (
-      <include refid="selectUpdateIdentity"/>,
+      <include refid="select_update_row_id"/>,
       #{config, typeHandler=org.apache.aurora.scheduler.storage.db.typehandlers.TaskConfigTypeHandler},
       #{isNew}
     )
   </insert>
 
-  <sql id="insertInstanceRanges">
+  <sql id="insert_instance_ranges">
     <foreach item="element" collection="ranges" open="(" separator="),(" close=")">
-      <include refid="selectUpdateIdentity"/>,
+      <include refid="select_update_row_id"/>,
       #{element.first},
       #{element.last}
     </foreach>
@@ -104,20 +120,20 @@
 
   <insert id="insertInstanceOverrides">
     INSERT INTO job_updates_to_instance_overrides (
-      update_id,
+      update_row_id,
       first,
       last
     ) VALUES
-  <include refid="insertInstanceRanges" />
+  <include refid="insert_instance_ranges" />
   </insert>
 
   <insert id="insertDesiredInstances">
     INSERT INTO job_updates_to_desired_instances (
-      update_id,
+      update_row_id,
       first,
       last
     ) VALUES
-    <include refid="insertInstanceRanges" />
+    <include refid="insert_instance_ranges" />
   </insert>
 
   <resultMap id="jobUpdateStateMap" type="org.apache.aurora.gen.JobUpdateState">
@@ -205,51 +221,51 @@
     INNER JOIN
     (
       SELECT
-        e_s.update_id,
+        e_s.update_row_id,
         e_s.status
       FROM job_update_events AS e_s
       INNER JOIN
       (
         SELECT
-          update_id,
+          update_row_id,
           MAX(timestamp_ms) AS timestamp_ms
         FROM job_update_events
-        GROUP BY update_id
-      ) AS e_t ON e_t.update_id = e_s.update_id AND e_t.timestamp_ms = e_s.timestamp_ms
-    ) AS max_status ON max_status.update_id = u.id
+        GROUP BY update_row_id
+      ) AS e_t ON e_t.update_row_id = e_s.update_row_id AND e_t.timestamp_ms = e_s.timestamp_ms
+    ) AS max_status ON max_status.update_row_id = u.id
   </sql>
 
   <sql id="created_timestamp_inner_join">
     INNER JOIN
     (
       SELECT
-        update_id,
+        update_row_id,
         MIN(timestamp_ms) AS timestamp_ms
       FROM job_update_events
-      GROUP BY update_id
-    ) AS min_ts ON min_ts.update_id = u.id
+      GROUP BY update_row_id
+    ) AS min_ts ON min_ts.update_row_id = u.id
   </sql>
 
   <sql id="last_updated_timestamp_inner_join">
     INNER JOIN
     (
       SELECT
-        update_id,
+        update_row_id,
         MAX(timestamp_ms) AS timestamp_ms
       FROM
       (
         SELECT
-          update_id,
+          update_row_id,
           timestamp_ms
         FROM job_update_events
         UNION ALL
         SELECT
-          update_id,
+          update_row_id,
           timestamp_ms
         FROM job_instance_update_events
       )
-      GROUP BY update_id
-    ) AS max_ts ON max_ts.update_id = u.id
+      GROUP BY update_row_id
+    ) AS max_ts ON max_ts.update_row_id = u.id
   </sql>
 
   <sql id="timestamps_inner_joins">
@@ -258,14 +274,13 @@
     <include refid="last_updated_timestamp_inner_join" />
   </sql>
 
-  <sql id="job_key_inner_join">
-    INNER JOIN job_keys AS j ON j.id = u.job_key_id
-  </sql>
-
   <sql id="query_filter">
-    <if test="updateId != null || role != null || user != null || jobKey != null || updateStatuses
!= null || limit != 0 || offset != 0">
+    <if test="updateId != null || key != null || role != null || user != null || jobKey
!= null || updateStatuses != null || limit != 0 || offset != 0">
       WHERE TRUE
       <if test="updateId != null">AND u.update_id = #{updateId}</if>
+      <if test="key != null">
+        AND <include refid="filter_by_update_key"/>
+      </if>
       <if test="user != null">AND u.user = #{user}</if>
       <if test="role != null">AND j.role = #{role}</if>
       <if test="jobKey != null">
@@ -304,6 +319,17 @@
     <include refid="query_filter" />
   </select>
 
+  <select id="selectUpdateKey" resultMap="jobUpdateKeyMap">
+    SELECT
+      u.update_id AS id,
+      j.role AS jk_role,
+      j.environment AS jk_environment,
+      j.name AS jk_name
+    FROM job_updates AS u
+    <include refid="job_key_inner_join" />
+    WHERE u.update_id = #{id}
+  </select>
+
   <!--Column naming convention below follows the thrift object hierarchy and columnPrefix
     attributes used in associations.
     For example: jusm_just_status maps to JobUpdateSummary/JobUpdateState/status field.-->
@@ -344,15 +370,15 @@
   </sql>
 
   <sql id="job_update_outer_joins">
-    LEFT OUTER JOIN job_update_configs AS cn ON cn.update_id = u.id AND cn.is_new = TRUE
-    LEFT OUTER JOIN job_update_configs AS co ON co.update_id = u.id AND co.is_new = FALSE
+    LEFT OUTER JOIN job_update_configs AS cn ON cn.update_row_id = u.id AND cn.is_new = TRUE
+    LEFT OUTER JOIN job_update_configs AS co ON co.update_row_id = u.id AND co.is_new = FALSE
     LEFT OUTER JOIN job_update_configs_to_instances AS ci ON ci.config_id = co.id
-    LEFT OUTER JOIN job_updates_to_desired_instances AS di ON di.update_id = u.id
-    LEFT OUTER JOIN job_updates_to_instance_overrides AS io ON io.update_id = u.id
+    LEFT OUTER JOIN job_updates_to_desired_instances AS di ON di.update_row_id = u.id
+    LEFT OUTER JOIN job_updates_to_instance_overrides AS io ON io.update_row_id = u.id
   </sql>
 
   <sql id="lock_outer_join">
-    LEFT OUTER JOIN job_update_locks AS l on l.update_id = u.id
+    LEFT OUTER JOIN job_update_locks AS l on l.update_row_id = u.id
   </sql>
 
   <sql id="unscoped_details_select">
@@ -371,8 +397,8 @@
     <include refid="job_key_inner_join" />
     <include refid="timestamps_inner_joins" />
     <include refid="job_update_outer_joins" />
-    LEFT OUTER JOIN job_update_events AS e ON e.update_id = u.id
-    LEFT OUTER JOIN job_instance_update_events AS i ON i.update_id = u.id
+    LEFT OUTER JOIN job_update_events AS e ON e.update_row_id = u.id
+    LEFT OUTER JOIN job_instance_update_events AS i ON i.update_row_id = u.id
     <include refid="lock_outer_join" />
   </sql>
 
@@ -407,7 +433,7 @@
     FROM job_updates AS u
     <include refid="job_key_inner_join" />
     <include refid="job_update_outer_joins" />
-    WHERE u.update_id = #{id}
+    WHERE <include refid="filter_by_update_key"/>
   </select>
 
   <select id="selectUpdate" resultMap="jobUpdateMap">
@@ -417,12 +443,12 @@
     <include refid="job_key_inner_join" />
     <include refid="timestamps_inner_joins" />
     <include refid="job_update_outer_joins" />
-    WHERE u.update_id = #{id}
+    WHERE <include refid="filter_by_update_key"/>
   </select>
 
   <select id="selectDetails" resultMap="jobUpdateDetailsMap">
     <include refid="unscoped_details_select"/>
-    WHERE u.update_id = #{id}
+    WHERE <include refid="filter_by_update_key"/>
     ORDER BY e_timestamp_ms, i_timestamp_ms
   </select>
 
@@ -439,8 +465,9 @@
     SELECT
       lock_token
     FROM job_update_locks AS l
-    INNER JOIN job_updates u ON l.update_id = u.id
-    WHERE u.update_id = #{id}
+    INNER JOIN job_updates u ON l.update_row_id = u.id
+    <include refid="job_key_inner_join" />
+    WHERE <include refid="filter_by_update_key"/>
   </select>
 
   <select id="selectInstanceUpdateEvents" resultMap="jobInstanceUpdateMap">
@@ -450,8 +477,10 @@
       timestamp_ms,
       action
     FROM job_instance_update_events as e
-    INNER JOIN job_updates as j ON j.id = e.update_id
-    WHERE j.update_id = #{updateId} AND e.instance_id = #{instanceId}
+    INNER JOIN job_updates as u ON u.id = e.update_row_id
+    <include refid="job_key_inner_join" />
+    WHERE <include refid="filter_by_update_key"/>
+      AND e.instance_id = #{instanceId}
     ORDER BY e.timestamp_ms
   </select>
 
@@ -477,12 +506,34 @@
     WHERE min_ts.timestamp_ms &lt; #{pruneThresholdMs} AND l.id IS NULL
   </select>
 
-  <select id="selectPruneVictims" resultType="String">
-    SELECT id FROM
+  <resultMap id="jobUpdateKeyMap" type="org.apache.aurora.gen.JobUpdateKey">
+    <association property="job"
+                 resultMap="org.apache.aurora.scheduler.storage.db.JobKeyMapper.jobKeyMap"
+                 columnPrefix="jk_"/>
+  </resultMap>
+
+  <resultMap id="pruneVictimMap" type="org.apache.aurora.scheduler.storage.db.PruneVictim">
+    <id column="row_id" property="rowId"/>
+    <association property="update" resultMap="jobUpdateKeyMap" columnPrefix="u_" />
+  </resultMap>
+
+  <select id="selectPruneVictims" resultMap="pruneVictimMap">
+    SELECT
+      row_id,
+      u_id,
+      u_jk_role,
+      u_jk_environment,
+      u_jk_name
+    FROM
     (
       SELECT
-        u.update_id as id
+        u.id as row_id,
+        u.update_id AS u_id,
+        j.role AS u_jk_role,
+        j.environment AS u_jk_environment,
+        j.name AS u_jk_name
       FROM job_updates as u
+      <include refid="job_key_inner_join" />
       <include refid="created_timestamp_inner_join" />
       <include refid="lock_outer_join" />
       WHERE u.job_key_id = #{keyId}
@@ -493,8 +544,13 @@
     )
     UNION
     SELECT
-      u.update_id as id
+      u.id,
+      u.update_id AS u_id,
+      j.role AS u_jk_role,
+      j.environment AS u_jk_environment,
+      j.name AS u_jk_name
     FROM job_updates as u
+    <include refid="job_key_inner_join" />
     <include refid="created_timestamp_inner_join" />
     <include refid="lock_outer_join" />
     WHERE u.job_key_id = #{keyId}
@@ -504,8 +560,8 @@
 
   <delete id="deleteCompletedUpdates">
     DELETE FROM job_updates
-    WHERE update_id IN
-    <foreach item="element" collection="updateIds" open="(" separator="," close=")">
+    WHERE id IN
+    <foreach item="element" collection="rowIds" open="(" separator="," close=")">
       #{element}
     </foreach>
   </delete>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.xml
b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.xml
index 9f23c4b..d813b19 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.xml
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateEventMapper.xml
@@ -19,16 +19,12 @@
 <mapper namespace="org.apache.aurora.scheduler.storage.db.JobUpdateEventMapper">
   <insert id="insert">
     INSERT INTO job_update_events (
-      update_id,
+      update_row_id,
       status,
       user,
       timestamp_ms
     ) VALUES (
-      (
-        SELECT id
-        FROM job_updates
-        WHERE update_id = #{updateId}
-      ),
+      <include refid="org.apache.aurora.scheduler.storage.db.JobUpdateDetailsMapper.select_update_row_id"/>,
       #{event.status, typeHandler=org.apache.aurora.scheduler.storage.db.typehandlers.JobUpdateStatusTypeHandler},
       #{event.user},
       #{event.timestampMs}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql b/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
index 987596f..e6184dd 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
@@ -108,41 +108,42 @@ CREATE TABLE job_updates(
   wait_for_batch_completion BOOLEAN NOT NULL,
   block_if_no_pulses_after_ms INT NULL,
 
+  -- TODO(wfarner): Convert this to UNIQUE(job_key_id, update_id) to complete AURORA-1093.
   UNIQUE(update_id)
 );
 
 CREATE TABLE job_update_locks(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   lock_token VARCHAR NOT NULL REFERENCES locks(token) ON DELETE CASCADE,
 
-  UNIQUE(update_id),
+  UNIQUE(update_row_id),
   UNIQUE(lock_token)
 );
 
 CREATE TABLE job_update_configs(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   task_config BINARY NOT NULL,
   is_new BOOLEAN NOT NULL
 );
 
 CREATE TABLE job_updates_to_instance_overrides(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   first INT NOT NULL,
   last INT NOT NULL,
 
-  UNIQUE(update_id, first, last)
+  UNIQUE(update_row_id, first, last)
 );
 
 CREATE TABLE job_updates_to_desired_instances(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   first INT NOT NULL,
   last INT NOT NULL,
 
-  UNIQUE(update_id, first, last)
+  UNIQUE(update_row_id, first, last)
 );
 
 CREATE TABLE job_update_configs_to_instances(
@@ -156,7 +157,7 @@ CREATE TABLE job_update_configs_to_instances(
 
 CREATE TABLE job_update_events(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   status INT NOT NULL REFERENCES job_update_statuses(id),
   timestamp_ms BIGINT NOT NULL,
   user VARCHAR
@@ -164,7 +165,7 @@ CREATE TABLE job_update_events(
 
 CREATE TABLE job_instance_update_events(
   id IDENTITY,
-  update_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
+  update_row_id BIGINT NOT NULL REFERENCES job_updates(id) ON DELETE CASCADE,
   action INT NOT NULL REFERENCES job_instance_update_actions(id),
   instance_id INT NOT NULL,
   timestamp_ms BIGINT NOT NULL

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/ec66a5ec/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
b/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
index be5e772..156cbc4 100644
--- a/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
@@ -30,6 +30,7 @@ import org.apache.aurora.gen.JobUpdateAction;
 import org.apache.aurora.gen.JobUpdateDetails;
 import org.apache.aurora.gen.JobUpdateEvent;
 import org.apache.aurora.gen.JobUpdateInstructions;
+import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.JobUpdateQuery;
 import org.apache.aurora.gen.JobUpdateSettings;
 import org.apache.aurora.gen.JobUpdateState;
@@ -73,6 +74,7 @@ import static org.apache.aurora.gen.JobUpdateStatus.ROLLING_FORWARD;
 import static org.apache.aurora.gen.JobUpdateStatus.ROLL_BACK_PAUSED;
 import static org.apache.aurora.gen.JobUpdateStatus.ROLL_FORWARD_PAUSED;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 public class DBJobUpdateStoreTest {
 
@@ -109,6 +111,15 @@ public class DBJobUpdateStoreTest {
 
     saveUpdate(update2, Optional.<String>absent());
     assertUpdate(update2);
+
+    // Colliding update IDs should be forbidden.
+    IJobUpdate update3 = makeJobUpdate(JobKeys.from("role", "env", "name3"), updateId2);
+    try {
+      saveUpdate(update3, Optional.<String>absent());
+      fail("Update ID collision should not be allowed");
+    } catch (StorageException e) {
+      // Expected.
+    }
   }
 
   @Test
@@ -622,6 +633,22 @@ public class DBJobUpdateStoreTest {
         ImmutableList.of(s5),
         getSummaries(new JobUpdateQuery().setJobKey(job5.newBuilder())));
 
+    // Test querying by update key.
+    assertEquals(
+        ImmutableList.of(s5),
+        getSummaries(
+            new JobUpdateQuery().setKey(new JobUpdateKey(job5.newBuilder(), s5.getUpdateId()))));
+
+    // Test querying by incorrect update keys.
+    assertEquals(
+        ImmutableList.<IJobUpdateSummary>of(),
+        getSummaries(
+            new JobUpdateQuery().setKey(new JobUpdateKey(job5.newBuilder(), s4.getUpdateId()))));
+    assertEquals(
+        ImmutableList.<IJobUpdateSummary>of(),
+        getSummaries(
+            new JobUpdateQuery().setKey(new JobUpdateKey(job4.newBuilder(), s5.getUpdateId()))));
+
     // Test query by user.
     assertEquals(ImmutableList.of(s2, s1), getSummaries(new JobUpdateQuery().setUser("user")));
 


Mime
View raw message