Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, ili, Вы писали:
ili>>Выполняем первый до селекта ili>>Выполняем второй до селекта (второй висит)
S>Почему два разных скрипта? Ведь операция одна и та же? Как знать когда вызвать первый когда второй?
скрипт таки один, в двух разных соединениях
тест эмулирует конкурентные запросы
суть в том, что один апдейт лочит другой апдейт, т.е. селект, выполняемяй после апдейта, не может быть выполнен, пока не будет выполнен апдейт
Re: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, Аноним, Вы писали:
А>Что делать? Есть ли что лучшее, чем использование ручной блокировки "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 раз. Что делать?
Здравствуйте, Аноним, Вы писали:
А>Блокировать с помощью BEGIN TRAN? Это не помогает избежать необходимости перезапусков транзакции (в случае неудачи).
Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, artelk, Вы писали:
A>Здравствуйте, gandjustas, Вы писали:
G>>Можно поставить serializable уровень изоляции, тогда фактически каждое обращение к записи будет вызывать эксклюзивную блокировку. A>Неверно. При чтении (в ms sql) будет intent lock, если явно не сказать, что нужен эксклюзивный.
Да, я тупанул. Не так понял задачу.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, 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 раз. Что делать?
Здравствуйте, TK, Вы писали:
TK>Здравствуйте, Аноним, Вы писали:
А>>Блокировать с помощью BEGIN TRAN? Это не помогает избежать необходимости перезапусков транзакции (в случае неудачи).
TK>Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).
Пффф, а если между 1 и 2 процесс умрет?
Re[3]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, agat50, Вы писали:
A>Здравствуйте, scale_tone, Вы писали:
_>>P.S. — воистину, классика никогда не стареет! Пройдут годы, может быть, столетия — а наши правнуки по-прежнему будут на собеседованиях спрашивать друг друга, как перечислить деньги со счета на счет
A>А если данные по счетам разнесены по 2+ серверам (типа шардинг), без собственного велосипеда не обойтись, или тоже есть паттерны?
Зачем вам два сервера то? Вы вряд ли в один даже упретесь.
Re[4]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, agat50, Вы писали:
A>>Здравствуйте, scale_tone, Вы писали:
_>>>P.S. — воистину, классика никогда не стареет! Пройдут годы, может быть, столетия — а наши правнуки по-прежнему будут на собеседованиях спрашивать друг друга, как перечислить деньги со счета на счет
A>>А если данные по счетам разнесены по 2+ серверам (типа шардинг), без собственного велосипеда не обойтись, или тоже есть паттерны?
G>Зачем вам два сервера то? Вы вряд ли в один даже упретесь.
Вопрос не в скорости в основном, в общем естественно проще и быстрее мощный сервер, любая сеть будет сливать 60гб\с RAM. Вопрос в фейлах. Если такой сервер вырубится — во-первых, нужно иметь такой же по мощности резервный, что дорого. Во-вторых, восстанавливать из бекапов проще и быстрее меньший объём данных. Для 10 лёгких серверов меньше избыточность резерва (достаточно чтобы хотя бы эти сервера не работали на максимуме), данные по счетам хорошо параллелятся, сеть звездой можно сделать => сложнее нагнуть сеть. Так как объём бэкапов меньше — можно поставить для них дешёвые медленные диски, зазеркалить по 3. Ну и т.п.
Но это всё теория, опыта мало с отказоустойчивой инфраструкторой. Просто тема интересует, а тут активная дискуссия, решил спросить
Re[5]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, 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 раз. Что делать?
Здравствуйте, agat50, Вы писали:
A>Вопрос не в скорости в основном, в общем естественно проще и быстрее мощный сервер, любая сеть будет сливать 60гб\с RAM. Вопрос в фейлах. Если такой сервер вырубится — во-первых, нужно иметь такой же по мощности резервный, что дорого. Во-вторых, восстанавливать из бекапов проще и быстрее меньший объём данных. Для 10 лёгких серверов меньше избыточность резерва (достаточно чтобы хотя бы эти сервера не работали на максимуме), данные по счетам хорошо параллелятся, сеть звездой можно сделать => сложнее нагнуть сеть. Так как объём бэкапов меньше — можно поставить для них дешёвые медленные диски, зазеркалить по 3. Ну и т.п.
Как уже сказали, шардингом отказоустойчивость никто не повышает. Один "нормальный" сервер Вам будет стоить однозначно дешевле 10-ти "легких". При этом, если Ваш бизнес-оунер разрешает Вашей системе такую роскошь, как простой на время поднятия из бэкапа — это означает, что иметь такой же по мощности резервный сервер Вам уж совсем ни к чему. Достаточно персоналки у Вас под столом или виртуалки в облаке.
Вариант 10-ти "легких" серверов используют, когда нужно обрабатывать поток однотипных независимых задач (batch job processing, веб-сервера и т.д.). Тогда эти задачи балансировщиком распределяются между машинами и тогда действительно выгоднее иметь много слабых, хилых, но взаимозаменяемых железок (или виртуалок), чем одну большую и супернадежную, но одну. Собственно, такая архитектура является идеальной для любой нагруженной системы и к ней нужно стремиться. Но все еще не всегда получается.
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, gandjustas, Вы писали:
TK>>Не обязательно перезапускать в всю транзакцию — можно повторять только ту часть, что не удалась. т.е. обновили счет 1, сделали SaveChanges, обновили счет 2 сделали SaveChanges (если не прошло то, повторили начиная со второго шага).
G>Пффф, а если между 1 и 2 процесс умрет?
А если процесс умрет во время 1 или 2? Транзакция откатится и ничего не будет.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, Аноним, Вы писали:
А>А если сначала изменить, потом прочитать -- то нет гарантии, что кто-то не изменил кроме нас. Ведь транзакция не блокирует на изменение записи, которые не были прочитаны.
Здравствуйте, Аноним, Вы писали:
А>Напишите ваш скрипт одним куском, а то не понятно что вы хотите сказать.
он таки и написан одним куском.
А>Хотите сказать что если в транзакции изменили запись, то другие ее не смогут изменять до завершения транзакции?
именно, по крайней мене в конструкции set amount = amount + 1, в конструкции set amount = 100 не проверял
вообще первая конструкция выглядит как изменение со чтением, это по крайней мере объясняет почему они блокируются не смотря на пространные объяснения MSDNa про "чтения" и "изменения" (кстати, чего там понимается под "чтением" это вопрос... т.к. вот Id в WHERE тоже надо прочитать... так что вообще не факт что имеется ввиду SELECT, а не любая операция "чтения")
Re[7]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, ili, Вы писали:
А>>Хотите сказать что если в транзакции изменили запись, то другие ее не смогут изменять до завершения транзакции? ili>именно, по крайней мене в конструкции set amount = amount + 1, в конструкции set amount = 100 не проверял
ili>вообще первая конструкция выглядит как изменение со чтением, это по крайней мере объясняет почему они блокируются не смотря на пространные объяснения MSDNa про "чтения" и "изменения" (кстати, чего там понимается под "чтением" это вопрос... т.к. вот Id в WHERE тоже надо прочитать... так что вообще не факт что имеется ввиду SELECT, а не любая операция "чтения")
Да, действительно. ПРоверил еще раз -- то что изменили в транзакции другие не смогут ни изменить ни прочитаь. Как же я раньше проверял
Re[8]: ConcurrencyException'ы замедляют работу в 20 раз. Что делать?
Здравствуйте, Shmj, Вы писали: S>Да, действительно. ПРоверил еще раз -- то что изменили в транзакции другие не смогут ни изменить ни прочитаь. Как же я раньше проверял
Раньше вы проверяли оптимистичные блокировки. А они — фикция. В отличие от настоящих.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.