Re[8]: EJB3 vs SQL
От: jonny_gold  
Дата: 15.12.06 13:01
Оценка:
Здравствуйте, aka50, Вы писали:

A>Т.е. в данном случае либо надо следовать спецификации и предполагать, что блокировка может быть где угодно,

A>либо идти путем "оптимизаций", т.е.

A>
A>if (em instanceof HibernateEntityManager) {
A>   MyEntity entity = ((HibernateEntityManger)em).getSession().get(... LockModeType.WRITE)
A>} else {
A>   // standard find + lock and pray :)
A>}

A>


Если следовать спецификации, то не стоит вообще заморачиваться с проблемой отложенной блокировки, потому что блокировка — это всего-лишь одна из возможных реализации: спека только гарантирует REPEATABLE READ после вызова lock(READ).
Как вариант лок может быть реализован на основе version-поля, вот почему спека требует наличия версии у блокируемых сущностей в portable applications.
То есть в текущей спеке гарантированной блокировки строк просто нету.

Оверквотинг вырезан. Автору желтая карточка. Blazkowicz
Re[6]: Simultaneous Access
От: jonny_gold  
Дата: 15.12.06 13:17
Оценка:
Здравствуйте, C0s, Вы писали:

C0s>Здравствуйте, jonny_gold, Вы писали:


C0s>>>>>множественные задачи конкурентного доступа под нагрузкой к одним и тем же объектам БД

C0s>>>обсуждаем конкретный пример (топикстартера), а не балк-операции

_>>Во-первых, я никогда не встречался с задачей, когда нужно обеспечивать защиту от потери обновлений при такой безумной конкуренции, поэтому не посчитал пример, приведенный в начальном письме, описанием реально требуемых условий. Если это так, то я вообще не понимаю, зачем использовать ORM в таком случае.


C0s>ну, в этом топике не ты первый, кто пытается обсудить что-то еще кроме изначального вопроса

C0s>кстати, я не согласен, что требуемые условия в том примере — нереальны. задача счетчика (к примеру, остатков продукции при параллельном приходе-расходе) — довольно распространенная! и почти во всех таких задачах преимущества ORM очевидны (сложные структуры данных описателей и проч)
Да, только там не такая конкуренция.

C0s>и на самом деле, в большинстве таких задач транзакции не настолько просты, чтобы можно было с легкостью повторять их по факту пойманного исключения об откате из-за нарушения оптимистичного лока

Лок ведь тоже не бесплатно дается. Увеличиваются лаги, возможны таймауты и т.д. Особенно в нетривиальных транзакциях?

C0s>пессимистичный лок, особенно в виде единственного прямого инкрементирующего или декрементирующего update — способ избежать откатов в таких случаях (когда транзакции все равно выстраиваются одна за другой из-за того, что оперируют одним и тем же счетчиком).

Читай ниже...

C0s>в-общем, я к тому, что надо уметь владеть обоими техниками...


_>>Во-вторых, не могли бы вы мне кратенько объяснить, что такое update-query в hibernate, если не "update ... set ... where ..."?


C0s>да, именно update, ничего особенного. просто для этого объект загружать не обязательно


Что значит не обязательно? Мы говорим о потерянных обновлениях? Обновлениях с учетом предыдущего состояния записи. Если мы не накладываем лок, значит мы должны всегда ожидать, что наша версия записи к моменту комита уже устарела. В варианте с update ты должен будешь забить в "where" все старые значения обновляемых полей, чтобы обнаружить колизию (собственно, так реализовано в dataset ADO.NET, там поля с версией не используются). А значит возможен вариант с "0 rows affected". Чем этот способ лучше использования версий? Результат тот же, только что велосипед свой.

Собственно, повторю, оперция update в ejbql есть. И подумай почему она описывается в разделе с названием "Bulk operations", так смутившем тебя.
Re[5]: Simultaneous Access
От: jonny_gold  
Дата: 15.12.06 13:32
Оценка: 2 (1)
Здравствуйте, SLad83, Вы писали:

SL>Здравствуйте, Аноним, Вы писали:


SL>>>ну вместо вызова метода 100 раз можно внутри его поставить слип — вот и тест с паузой...

А>>Ну, да, типа того.

SL>при попытке делать


SL>
SL>    try {
SL>            f.setSomefield(f.getSomefield()+1);
SL>            Thread.sleep(2000);
SL>            injectedEm.flush();
SL>        } catch(OptimisticLockException ole) {
SL>            injectedEm.refresh(f); // здесь
SL>            f.setSomefield(f.getSomefield()+1);
SL>            injectedEm.flush();   
SL>            return "OptimisticLockException caught";
SL>        }
SL>   return "field updated";
SL>

Повторное обновление в catch это конечно круто. А почему ты решил, что оно всегда будет завершаться удачно? Ведь там тоже может быть конфликт. Внутри метода ты можешь ловить OptimisticLockException только чтобы обернуть его в другое исключение. А реальной обработкой исключения должен заниматься клиентский код: сдаться, повтроить операцию (в новой транзакции), подготовиться к операции merge'а и т.д.

SL>это значит, что при Exception-е транзакция закрывается? или при injectedEm.flush()?

Да, это исключение всегда откатывает транзакцию.


SL>как тогда реализовать повторную попытку обновления? решение


SL>
SL> do {
SL>                sTmp = sess.fieldInc(entityId);
SL>            } while (!sTmp.equals("field updated"));
SL>


SL>корявое какое-то...

А что смущает? Только зачем подавлять исключение? Еще один try ... catch. Только учти, что если ты сам не обернул OptimisticLockException, то контейнер обернет ее в EJBException или RemoteException.
Re[7]: Simultaneous Access
От: C0s Россия  
Дата: 15.12.06 13:39
Оценка:
Здравствуйте, jonny_gold, Вы писали:

C0s>>ну, в этом топике не ты первый, кто пытается обсудить что-то еще кроме изначального вопроса

C0s>>кстати, я не согласен, что требуемые условия в том примере — нереальны. задача счетчика (к примеру, остатков продукции при параллельном приходе-расходе) — довольно распространенная! и почти во всех таких задачах преимущества ORM очевидны (сложные структуры данных описателей и проч)
_>Да, только там не такая конкуренция.

какая такая "не такая"?

_>Лок ведь тоже не бесплатно дается. Увеличиваются лаги, возможны таймауты и т.д. Особенно в нетривиальных транзакциях?


в большинстве модифицирующих транзакций есть какие-то update (собственно, везде, кроме чистых вставок). так что локи накладываются, и избавиться от них нельзя.
"оптимистичные" локи просто откладывают реальные БД-локи на конец транзакции, но не отменяют их совсем.

C0s>>да, именно update, ничего особенного. просто для этого объект загружать не обязательно


_>Что значит не обязательно? Мы говорим о потерянных обновлениях?


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

_>Обновлениях с учетом предыдущего состояния записи. Если мы не накладываем лок, значит мы должны всегда ожидать, что наша версия записи к моменту комита уже устарела. В варианте с update ты должен будешь забить в "where" все старые значения обновляемых полей, чтобы обнаружить колизию (собственно, так реализовано в dataset ADO.NET, там поля с версией не используются). А значит возможен вариант с "0 rows affected". Чем этот способ лучше использования версий? Результат тот же, только что велосипед свой.


ты о чем?
достаточно выполнить
update counters set counter = counter + 1 where id = %1;
commit;

на update все параллельные транзакции для одного и того же id будут выстраиваться одна за другой, ибо налагается лок. откаты возможны только по таймауту ожидания снятия лока на уровне БД

собственно, основной вопрос — можно ли именно в контексте ejb3 этого добиться? ибо проблемы автора были именно в том, что в его примере был предваряющий select. а его здесь быть не должно!

_>Собственно, повторю, оперция update в ejbql есть. И подумай почему она описывается в разделе с названием "Bulk operations", так смутившем тебя.


ну, ты пытаешься напомнить о том, что update бывает множественный я же это тоже понимаю
просто его использование при наличии в where условия по первичному ключу гарантирует его применение к максимум одной записи. я меня язык не поворачивается в этом контексте называть это bulk-операцией
Re[9]: EJB3 vs SQL
От: aka50 Россия  
Дата: 15.12.06 13:48
Оценка:
Здравствуйте, jonny_gold, Вы писали:

_>Если следовать спецификации, то не стоит вообще заморачиваться с проблемой отложенной блокировки, потому что блокировка — это всего-лишь одна из возможных реализации: спека только гарантирует REPEATABLE READ после вызова lock(READ).

_>Как вариант лок может быть реализован на основе version-поля, вот почему спека требует наличия версии у блокируемых сущностей в portable applications.
_>То есть в текущей спеке гарантированной блокировки строк просто нету.

Спека основывается на стандарте sql, который в свою очередь основывается на блокировочниках (в большей степени),
чем на мультиверсионниках, и по этому если в случае с блокировочниками

REPEATABLE READ. The goal of REPEATABLE READ is to provide an isolation level that gives consistent, correct answers and prevents lost updates. I'll show examples of what you must do in Oracle Database to achieve these goals and examine what happens in other systems. If you have REPEATABLE READ isolation, the results from a given query must be consistent with respect to some point in time. Most databases (not Oracle) achieve repeatable reads through the use of row-level, shared read locks. A shared read lock prevents other sessions from modifying data that you've read.



По этому стоит заморачиваться, ибо без оглядки (как я писал раньше) на БД писать крайне чревато,
вот как раз один из таких примеров... т.е. в моем случае (я преимущественно на Oracle работаю)
для меня REPEATABLE и даже SERIALIZABLE уровень — не решение, только явный lock из-за особенностей
моей бд.

Oracle Database takes an optimistic approach to serialization: it gambles on the fact that the data your transaction wants to update won't be updated by any other transaction. This is typically the way it happens, and usually the gamble pays off, especially in quick transaction, OLTP-type systems. If no one else updates your data during your transaction, this isolation level, which will generally decrease concurrency in other systems, will provide the same degree of concurrency as it would without SERIALIZABLE transactions. The downside is that you may get the ORA-08177 error if the gamble doesn't pay off. If you think about it, however, it's worth the risk. If you're using SERIALIZABLE transaction, you shouldn't expect to update the same information as other transactions.


If you do, you should use the SELECT ... FOR UPDATE as described in Chapter 1 [of Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions]. This will serialize the access. So, you can effectively use an isolation level of SERIALIZABLE if you:

* Have a high probability of no one else modifying the same data
* Need transaction-level read consistency
* Will be doing short transactions (to help make the first bullet point a reality)


по этому и возникло небольшое противоречие из-за "неполного" соответствия некоторых бд

http://www.oracle.com/technology/oramag/oracle/05-nov/o65asktom.html
Re[3]: Simultaneous Access
От: aka50 Россия  
Дата: 27.12.06 09:53
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, jonny_gold, Вы писали:


_>>В сущностях, где требуется ловить потерянные апдейты, добавь поле с @Version. После апдейтов таких сущностей вызывай flush() и лови OptimisticLockException. Твой пример не совсем подходит для тестов такого подхода, нужно что-нить пореальнее, с паузами. А лок редко кодга нужен.


B>Какая должна быть логика в обработке OptimisticLockException?


Не совсем в тему EJB , а даже как бы вбок, но тем не менее тут должно быть уместно.

Одно из решений лежит в плоскости AOP. Можно наплодить разных
аспектных методов (с)Alex Reyst для различных логик, и разруливать
например аннотациями (типа описанного в примере Idempotent).

Для AspectJ(по идее должно спокойно цеплятся и к EJB3) или Spring 2.0

Вот код из мануала по 2.0
http://static.springframework.org/spring/docs/2.0.x/reference/aop.html

@Aspect
public class ConcurrentOperationExecutor implements Ordered {
   
   private static final int DEFAULT_MAX_RETRIES = 2;

   private int maxRetries = DEFAULT_MAX_RETRIES;
   private int order = 1;

   public void setMaxRetries(int maxRetries) {
      this.maxRetries = maxRetries;
   }
   
   public int getOrder() {
      return this.order;
   }
   
   public void setOrder(int order) {
      this.order = order;
   }
   
   @Around("com.xyz.myapp.SystemArchitecture.businessService()")
   public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 
      int numAttempts = 0;
      PessimisticLockingFailureException lockFailureException;
      do {
         numAttempts++;
         try { 
            return pjp.proceed();
         }
         catch(PessimisticLockingFailureException ex) {
            lockFailureException = ex;
         }
      }
      while(numAttempts <= this.maxRetries);
      throw lockFailureException;
   }

}

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
  // marker annotation
}

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + 
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 
  ...    
}
Re[4]: Simultaneous Access
От: Blazkowicz Россия  
Дата: 27.12.06 10:09
Оценка:
Здравствуйте, aka50, Вы писали:

B>>Какая должна быть логика в обработке OptimisticLockException?


A>
A>@Aspect
A>public class ConcurrentOperationExecutor implements Ordered {
A>   @Around("com.xyz.myapp.SystemArchitecture.businessService()")
A>   public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 
A>      int numAttempts = 0;
A>      PessimisticLockingFailureException lockFailureException;
A>      do {
A>         numAttempts++;
A>         try { 
A>            return pjp.proceed();
A>         }
A>         catch(PessimisticLockingFailureException ex) {
A>            lockFailureException = ex;
A>         }
A>      }
A>      while(numAttempts <= this.maxRetries);
A>      throw lockFailureException;
A>   }

A>}
A>


Ну, так это вроде все тот же пессимистичный лок. Просто он долбится в лок, пока тот не освободится. В случае с оптимиситчным локом ведь надо перечитать значение, и перезапустить бизнес логику.
Re[5]: Simultaneous Access
От: aka50 Россия  
Дата: 27.12.06 10:34
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, aka50, Вы писали:


B>>>Какая должна быть логика в обработке OptimisticLockException?


B>Ну, так это вроде все тот же пессимистичный лок. Просто он долбится в лок, пока тот не освободится. В случае с оптимиситчным локом ведь надо перечитать значение, и перезапустить бизнес логику.


Пессимистическим он будет для клиента. С точки зрения бд (например блокировочников) это будет оптимистический лок.

А вообще эта метода просто позволяет очистить код. Т.е. вместо описания действий на клиенте,
мы это реализуем в конкретном методе, и если рассматривать конкретно idempotent случай,
то бизнес метод уже знает, что он обладает такой семантикой и следовательно будет написан
соотвествующим образом.

Это метод может быть idempotent. Т.е. метод в нашем случае _сам_ перечитает entity
(другой вопрос что делать с умершей сессией, но это кривость реализации, а не
самого понятия optimistic lock).
public void inc(int value);


А этот уже не может, т.к. сдесь уже идет работа с измененным entity
и следовательно проблемы конкурентного доступа должны решаться уровнем выше.
public void storeIncremented(SomeHugeEntity entity)

Хотя тоже возможен вариант с допустим окошком пользователю открываемому
из АМ и этот АМ знает, что нужно загрузить некий entity.getClass(), сравнить
почленно и показать пользователю, если он согласен, повторить метод с новым
entity. Т.е. вместо того, чтобы однотипный код плодить в клиентском коде,
можно часть этих операций выкинуть в аспекты.

(пост действительно был не совсем в тему, т.к. больше касается того как
_реализовать_ обработку "оптимизма", а не то, что делать с точки зрения бизнес логики.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.