Re[8]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: Shmj Ниоткуда  
Дата: 03.12.13 15:27
Оценка:
Здравствуйте, ili, Вы писали:

ili>Выполняем первый до селекта

ili>Выполняем второй до селекта (второй висит)

Почему два разных скрипта? Ведь операция одна и та же? Как знать когда вызвать первый когда второй?
Re[9]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: ili Россия  
Дата: 03.12.13 15:31
Оценка:
Здравствуйте, Shmj, Вы писали:

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


ili>>Выполняем первый до селекта

ili>>Выполняем второй до селекта (второй висит)

S>Почему два разных скрипта? Ведь операция одна и та же? Как знать когда вызвать первый когда второй?


скрипт таки один, в двух разных соединениях
тест эмулирует конкурентные запросы
суть в том, что один апдейт лочит другой апдейт, т.е. селект, выполняемяй после апдейта, не может быть выполнен, пока не будет выполнен апдейт
Re: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: TK Лес кывт.рф
Дата: 03.12.13 18:30
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Что делать? Есть ли что лучшее, чем использование ручной блокировки "EXEC sp_getapplock" и исполнения в одном потоке?


можно и через отдельную таблицу:

using (var scope = new TransactionScope("ReadCommited"))
{
   using (var context = new DbContext())
   {
      var lock = context.SystemLock.Attach(new SystemLock() { Id = "AccountX" });
      lock.LockTime = DateTime.UtcNow;
      context.SaveChanges();

      var a1 = context.Account.FirstOrDefualt(...);
      a1.Total -= 1;
      
      var a2 = context.Account.FirstOrDefualt(...);
      a2.Total += 1;

      context.SaveChanges()
   }
   scope.Complete();
}
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[5]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: TK Лес кывт.рф
Дата: 03.12.13 18:43
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Блокировать с помощью BEGIN TRAN? Это не помогает избежать необходимости перезапусков транзакции (в случае неудачи).


Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.12.13 19:18
Оценка:
Здравствуйте, artelk, Вы писали:

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


G>>Можно поставить serializable уровень изоляции, тогда фактически каждое обращение к записи будет вызывать эксклюзивную блокировку.

A>Неверно. При чтении (в ms sql) будет intent lock, если явно не сказать, что нужен эксклюзивный.

Да, я тупанул. Не так понял задачу.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.12.13 19:59
Оценка:
Здравствуйте, Аноним, Вы писали:

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


G>>Можно поставить serializable уровень изоляции, тогда фактически каждое обращение к записи будет вызывать эксклюзивную блокировку.


А>Ошибаетесь. Serializable гарантирует:


А>

А>1. Инструкции не могут считывать данные, которые были изменены другими транзакциями, но еще не были зафиксированы.
А>2. Другие транзакции не могут изменять данные, считываемые текущей транзакцией, до ее завершения.
А>3. Другие транзакции не могут вставлять новые строки со значениями ключа, которые входят в диапазон ключей, считываемых инструкциями текущей транзакции, до ее завершения.


А>Отсюда.


А>Превое не запрещает ЧИТАТЬ данные, которые были уже ПРОЧИТАНЫ в другой транзакции.

А>Второе на запрещает ИЗМЕНЯТЬ данные, которые уже были ИЗМЕНЕНЫ в другой транзакции. Читать нельзя, а вот изменять -- пожалуйста.

А>Получается, если мы сначала прочитаем -- то и другой сможет прочитать. Если сначала изменим (без чтения) -- то и другой сможет изменить.


Сорри, неверно прочитал исходную задачу, думал код вида

update accounts set Balance = Balance+x where Id=toId
update accounts set Balance = Balance-x where Id=fromId


Кстати в ef такое возможно написать с использованием http://efe.codeplex.com/
Проверку можно и в check constraint сделать.
Хотя в таком случае serializable — оверкилл.


G>>Но лучше сразу хранить в виде двойной записи, а остатки получать через индексированные view.

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

Я думаю для высокой скорости и конкурентности имеет смысл вычисления перенести в СУБД — тригерры и check constraints

Вот примерно так у меня получилось:

CREATE TABLE [dbo].[Op] (
    [Id]     INT      IDENTITY (1, 1) NOT NULL,
    [Cr]     INT      NULL,
    [Db]     INT      NULL,
    [Amount] MONEY    NOT NULL,
    [Date]   DATETIME DEFAULT (getdate()) NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


CREATE VIEW [dbo].Balance AS 
select isnull(CrT.Cr, DbT.Db) as Account, 
       isnull(Crt.Sum,0) as Cr, 
       isnull(DbT.Sum,0) as Db, 
       (isnull(DbT.Sum,0) - isnull(Crt.Sum,0)) as Balance
from
    (select t.Cr as Cr , SUM(t.Amount) as [Sum]  from [dbo].[Op] t group by t.Cr) as CrT
    full join 
    (select t.Db as Db, SUM(t.Amount)as [Sum] from [dbo].[Op] t group by t.Db) as DbT
on CrT.Cr = DbT.Db
where DbT.Db is not null or CrT.Cr is not null


CREATE FUNCTION [dbo].[GetBalanceByAccountId]
(
    @AccountId int
)
RETURNS money
AS
BEGIN
    RETURN (select top 1 b.Balance from Balance b where b.Account = @AccountId)    
END


ALTER TABLE [dbo].[Op] WITH NOCHECK
    ADD CONSTRAINT [PositiveBalance] CHECK (Cr is null or [dbo].[GetBalanceByAccountId]([Cr]) >= 0);


Далее в коде приложения ловить эксепшены и понимать что пошло не так по имени constraint.
Re[6]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.12.13 20:15
Оценка:
Здравствуйте, TK, Вы писали:

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


А>>Блокировать с помощью BEGIN TRAN? Это не помогает избежать необходимости перезапусков транзакции (в случае неудачи).


TK>Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).


Пффф, а если между 1 и 2 процесс умрет?
Re[3]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.12.13 20:19
Оценка:
Здравствуйте, agat50, Вы писали:

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


_>>P.S. — воистину, классика никогда не стареет! Пройдут годы, может быть, столетия — а наши правнуки по-прежнему будут на собеседованиях спрашивать друг друга, как перечислить деньги со счета на счет


A>А если данные по счетам разнесены по 2+ серверам (типа шардинг), без собственного велосипеда не обойтись, или тоже есть паттерны?


Зачем вам два сервера то? Вы вряд ли в один даже упретесь.
Re[4]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: agat50  
Дата: 03.12.13 20:49
Оценка:
Здравствуйте, gandjustas, Вы писали:

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


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


_>>>P.S. — воистину, классика никогда не стареет! Пройдут годы, может быть, столетия — а наши правнуки по-прежнему будут на собеседованиях спрашивать друг друга, как перечислить деньги со счета на счет


A>>А если данные по счетам разнесены по 2+ серверам (типа шардинг), без собственного велосипеда не обойтись, или тоже есть паттерны?


G>Зачем вам два сервера то? Вы вряд ли в один даже упретесь.


Вопрос не в скорости в основном, в общем естественно проще и быстрее мощный сервер, любая сеть будет сливать 60гб\с RAM. Вопрос в фейлах. Если такой сервер вырубится — во-первых, нужно иметь такой же по мощности резервный, что дорого. Во-вторых, восстанавливать из бекапов проще и быстрее меньший объём данных. Для 10 лёгких серверов меньше избыточность резерва (достаточно чтобы хотя бы эти сервера не работали на максимуме), данные по счетам хорошо параллелятся, сеть звездой можно сделать => сложнее нагнуть сеть. Так как объём бэкапов меньше — можно поставить для них дешёвые медленные диски, зазеркалить по 3. Ну и т.п.

Но это всё теория, опыта мало с отказоустойчивой инфраструкторой. Просто тема интересует, а тут активная дискуссия, решил спросить
Re[5]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.12.13 21:20
Оценка: +1
Здравствуйте, agat50, Вы писали:

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


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


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


_>>>>P.S. — воистину, классика никогда не стареет! Пройдут годы, может быть, столетия — а наши правнуки по-прежнему будут на собеседованиях спрашивать друг друга, как перечислить деньги со счета на счет


A>>>А если данные по счетам разнесены по 2+ серверам (типа шардинг), без собственного велосипеда не обойтись, или тоже есть паттерны?


G>>Зачем вам два сервера то? Вы вряд ли в один даже упретесь.


A>Вопрос не в скорости в основном, в общем естественно проще и быстрее мощный сервер, любая сеть будет сливать 60гб\с RAM. Вопрос в фейлах. Если такой сервер вырубится — во-первых, нужно иметь такой же по мощности резервный, что дорого. Во-вторых, восстанавливать из бекапов проще и быстрее меньший объём данных. Для 10 лёгких серверов меньше избыточность резерва (достаточно чтобы хотя бы эти сервера не работали на максимуме), данные по счетам хорошо параллелятся, сеть звездой можно сделать => сложнее нагнуть сеть. Так как объём бэкапов меньше — можно поставить для них дешёвые медленные диски, зазеркалить по 3. Ну и т.п.


A>Но это всё теория, опыта мало с отказоустойчивой инфраструкторой. Просто тема интересует, а тут активная дискуссия, решил спросить


Шардинг отказоустойчивость не увеличивает. Вот тут как раз прекрасная задача — счета и проводки. Один счет лежит в одном шарде, другой в другом. Один шард вырубается и все, система недоступна, только четверть переводов смогут пройти. А еще интереснее если проводки хранятся, тогда при выпадении одного сервера полностью теряется консистентность (невозможно посчитать баланс в общем случае). Нужно или несколько реплик каждого шарда (дорого) или отказоустойчивость самих шардов (приходим к исходной проблеме).

Вообще умирание сервака — нечастое явление.
Первое место в хит-параде простоев занимает установка обновлений. Второе — проблемы с дисками.

Первое лечится сервисными окнами, час-два в месяц (99,5% доступности)
Второе — зеркалированием (raid)

Если вы не делаете процессинг в банке, то можно одним серваком обходиться долго, обеспечивая достаточно высокую доступность. Если всетаки процессинг, то проблем с ресурсами не будет в принципе
Re[5]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 03.12.13 22:15
Оценка:
Здравствуйте, agat50, Вы писали:

A>Вопрос не в скорости в основном, в общем естественно проще и быстрее мощный сервер, любая сеть будет сливать 60гб\с RAM. Вопрос в фейлах. Если такой сервер вырубится — во-первых, нужно иметь такой же по мощности резервный, что дорого. Во-вторых, восстанавливать из бекапов проще и быстрее меньший объём данных. Для 10 лёгких серверов меньше избыточность резерва (достаточно чтобы хотя бы эти сервера не работали на максимуме), данные по счетам хорошо параллелятся, сеть звездой можно сделать => сложнее нагнуть сеть. Так как объём бэкапов меньше — можно поставить для них дешёвые медленные диски, зазеркалить по 3. Ну и т.п.


Как уже сказали, шардингом отказоустойчивость никто не повышает. Один "нормальный" сервер Вам будет стоить однозначно дешевле 10-ти "легких". При этом, если Ваш бизнес-оунер разрешает Вашей системе такую роскошь, как простой на время поднятия из бэкапа — это означает, что иметь такой же по мощности резервный сервер Вам уж совсем ни к чему. Достаточно персоналки у Вас под столом или виртуалки в облаке.

Вариант 10-ти "легких" серверов используют, когда нужно обрабатывать поток однотипных независимых задач (batch job processing, веб-сервера и т.д.). Тогда эти задачи балансировщиком распределяются между машинами и тогда действительно выгоднее иметь много слабых, хилых, но взаимозаменяемых железок (или виртуалок), чем одну большую и супернадежную, но одну. Собственно, такая архитектура является идеальной для любой нагруженной системы и к ней нужно стремиться. Но все еще не всегда получается.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: TK Лес кывт.рф
Дата: 04.12.13 04:11
Оценка:
Здравствуйте, gandjustas, Вы писали:

TK>>Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).


G>Пффф, а если между 1 и 2 процесс умрет?


А если процесс умрет во время 1 или 2? Транзакция откатится и ничего не будет.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: ili Россия  
Дата: 04.12.13 07:41
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А если сначала изменить, потом прочитать -- то нет гарантии, что кто-то не изменил кроме нас. Ведь транзакция не блокирует на изменение записи, которые не были прочитаны.


да всё прекрасно блокируется
простейший тест это наглядно показывает
Автор: ili
Дата: 03.12.13
Re[5]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: Аноним  
Дата: 04.12.13 11:45
Оценка:
Здравствуйте, ili, Вы писали:

ili>да всё прекрасно блокируется

ili>простейший тест это наглядно показывает
Автор: ili
Дата: 03.12.13


Напишите ваш скрипт одним куском, а то не понятно что вы хотите сказать.

Хотите сказать что если в транзакции изменили запись, то другие ее не смогут изменять до завершения транзакции?
Re[6]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: ili Россия  
Дата: 04.12.13 12:03
Оценка: 4 (1)
Здравствуйте, Аноним, Вы писали:

А>Напишите ваш скрипт одним куском, а то не понятно что вы хотите сказать.


он таки и написан одним куском.

А>Хотите сказать что если в транзакции изменили запись, то другие ее не смогут изменять до завершения транзакции?


именно, по крайней мене в конструкции set amount = amount + 1, в конструкции set amount = 100 не проверял

вообще первая конструкция выглядит как изменение со чтением, это по крайней мере объясняет почему они блокируются не смотря на пространные объяснения MSDNa про "чтения" и "изменения" (кстати, чего там понимается под "чтением" это вопрос... т.к. вот Id в WHERE тоже надо прочитать... так что вообще не факт что имеется ввиду SELECT, а не любая операция "чтения")
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: Shmj Ниоткуда  
Дата: 04.12.13 14:18
Оценка:
Здравствуйте, ili, Вы писали:

А>>Хотите сказать что если в транзакции изменили запись, то другие ее не смогут изменять до завершения транзакции?

ili>именно, по крайней мене в конструкции set amount = amount + 1, в конструкции set amount = 100 не проверял

ili>вообще первая конструкция выглядит как изменение со чтением, это по крайней мере объясняет почему они блокируются не смотря на пространные объяснения MSDNa про "чтения" и "изменения" (кстати, чего там понимается под "чтением" это вопрос... т.к. вот Id в WHERE тоже надо прочитать... так что вообще не факт что имеется ввиду SELECT, а не любая операция "чтения")


Да, действительно. ПРоверил еще раз -- то что изменили в транзакции другие не смогут ни изменить ни прочитаь. Как же я раньше проверял
Re[8]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
От: Sinclair Россия https://github.com/evilguest/
Дата: 05.12.13 10:30
Оценка:
Здравствуйте, Shmj, Вы писали:
S>Да, действительно. ПРоверил еще раз -- то что изменили в транзакции другие не смогут ни изменить ни прочитаь. Как же я раньше проверял
Раньше вы проверяли оптимистичные блокировки. А они — фикция. В отличие от настоящих.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.