Java Google Appengine закрывает счетчики без транзакций

Я просматриваю пример Sharded Counters в Java: http://code.google.com/appengine/articles/sharding_counters.html

У меня возник вопрос о реализации метода приращения. В python он явно переносит get() и приращение транзакции. В примере Java он просто извлекает его и устанавливает его. Я не уверен, что полностью понимаю хранилище данных и транзакции, но, похоже, критический раздел обновления должен быть завернут в транзакцию хранилища данных. Я что-то упускаю?

Исходный код:

  public void increment() {
    PersistenceManager pm = PMF.get().getPersistenceManager();

    Random generator = new Random();
    int shardNum = generator.nextInt(NUM_SHARDS);

    try {
      Query shardQuery = pm.newQuery(SimpleCounterShard.class);
      shardQuery.setFilter("shardNumber == numParam");
      shardQuery.declareParameters("int numParam");

      List<SimpleCounterShard> shards =
          (List<SimpleCounterShard>) shardQuery.execute(shardNum);
      SimpleCounterShard shard;

      // If the shard with the passed shard number exists, increment its count
      // by 1. Otherwise, create a new shard object, set its count to 1, and
      // persist it.
      if (shards != null && !shards.isEmpty()) {
        shard = shards.get(0);
        shard.setCount(shard.getCount() + 1);
      } else {
        shard = new SimpleCounterShard();
        shard.setShardNumber(shardNum);
        shard.setCount(1);
      }

      pm.makePersistent(shard);
    } finally {
      pm.close();
    }
  }
}

Код транзакции (я полагаю, вам нужно запустить это в транзакции для обеспечения правильности соответствия при параллельных транзакциях?):

public void increment() { 
    PersistenceManager pm = PMF.get().getPersistenceManager(); 
    Random generator = new Random(); 
    int shardNum = generator.nextInt(NUM_SHARDS); 
    try { 
      Query shardQuery = pm.newQuery(SimpleCounterShard.class); 
      shardQuery.setFilter("shardNumber == numParam"); 
      shardQuery.declareParameters("int numParam"); 
      List<SimpleCounterShard> shards = 
          (List<SimpleCounterShard>) shardQuery.execute(shardNum); 
      SimpleCounterShard shard; 
      // If the shard with the passed shard number exists, increment its count 
      // by 1. Otherwise, create a new shard object, set its count to 1, and 
      // persist it. 
      if (shards != null && !shards.isEmpty()) { 
            Transaction tx = pm.currentTransaction(); 
        try { 
            tx.begin(); 
            //I believe in a transaction objects need to be loaded by ID (can't use the outside queried entity) 
             Key shardKey = KeyFactory.Builder(SimpleCounterShard.class.getSimpleName(), shards.get(0).getID()) 
            shard =  pm.getObjectById(SimpleCounterShard.class, shardKey); 
            shard.setCount(shard.getCount() + 1); 
            tx.commit(); 
        } finally { 
            if (tx.isActive()) { 
                tx.rollback(); 
            } 
        } 
      } else { 
        shard = new SimpleCounterShard(); 
        shard.setShardNumber(shardNum); 
        shard.setCount(1); 
      } 
      pm.makePersistent(shard); 
    } finally { 
      pm.close(); 
    } 
  } 

Ответ 1

В этом разделе прямо из документов показано, что вы совершенно точно нуждаетесь в транзакции:

http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_For_Transactions

В этом примере показано одно использование транзакций: обновление объекта с новым значением свойства относительно его текущего значения.

    Key k = KeyFactory.createKey("Employee", "k12345");
    Employee e = pm.getObjectById(Employee.class, k);
    e.counter += 1;
    pm.makePersistent(e);

Для этого требуется транзакция, потому что значение может быть обновлено другим пользователем после того, как этот код извлечет объект, но прежде чем он сохранит измененный объект. Без транзакции пользовательский запрос будет использовать значение счетчик перед другим обновлением пользователя, и сохранение перезапишет новое значение. При транзакции приложение сообщается о другом обновлении пользователя. Если объект обновляется во время транзакции, транзакция завершается с исключением. Приложение может повторить транзакцию для использования новых данных.

Он очень близок к тому, что делает этот пример, и, как и вы, мне не удалось найти причину, по которой разбросанные счетчики будут отличаться.