Здравствуйте, IT, Вы писали:
V>>Пример из жизни:
V>>- десятки девочек набивают накладные
V>>- накладные состоят в среднем из 100-300 строк (оптовая торговля бытовой химией), т.е. процедура набивки одной накладной довольно длинная
V>>- в системах типа 1С постоянно случаются накладки, когда несколько девочек пытаются одновременно продать остатки одной и той же позиции товара, ибо на момент набивки им всем система показывает наличие товара
IT>Действительно, такие задачи иногда встречаются в жизни. Но строить на основании этого всю систему как stateful — это нонсенс. Такую задачку надо выделить из общей массы, отвести её в сторонку (можно вместе с девочками) и там решить. Но только не надо обобщать подход к решению подобных задач на всё бизнес приложение в целом (если оно, конечно, не состоит из одной такой задачки).
Оно часто состоит из десятков подобных задач. Такие задачи я называю примерно так: "редактирование документов, требующее поддержки сервера". Складские и бухгалтерские задачи — как раз та область. Только по складу мы можем иметь до нескольких десятков операций (в наших реалиях, включая черно-бело-серое кино). И теоретически бесконечный набор черно-белых операций по бухгалтерии.
Большинство остальных задач учетно-аналитической бизнес-системы гораздо легче в реализации. Типа: справочники, отчетность (аналитика), ЗП и т.д. и действительно, не требуют statefull.
Все что далее просьба воспринимать не как размахивание флагом "statefull рулит всегда и везде", а как "
иногда он очень удобен". Именно эти "иногда" и буду обсуждать, считая, что с бенефитами stateless и так все ясно.
V>>В моей системе сервер приложений разруливал подобную ситуацию. Т.е. используя statefull модель накладных, он позволял оперировать не только остатками товаров на складах по результатам транзакций, но и мгновенными остатками, с учетом текущих редактируемых данных коллеги за соседним столом.
IT>Думаю, что можно было бы отделаться только кешированием твоих мгновенных остатков. Делать для этого statefull модель накладных скорее всего совсем не обязательно.
Ниже как раз был приведен пример такого решения, которое было первоначальным.
V>>Подобная задача может быть решена и через stateless, но весьма ненадежно.
IT>Не верю
По большому счёту stateful от stateless отличается только лишь тем, что state в stateless всегда гарантированно ложится в базу данных, а в statefull не всегда, иногда только в память апп-сервера. Остальное — это приёмы использования кеша и борьбы с ним.
Да все правильно. Но я не понимаю, для тебя принципиальная разница — где хранится промежуточное состояние — на сервере приложений, или на клиенте. Я помню, про off-line клиентов, но это совсем другая история, требующая персистентности уже прямо на клиенте для качественной реализации.
V>>(Именно таким был первый вариант решения задачи). Т.е. суть решения состояла в организации доп. состояния документа — черновик, и каждая строчка накладной отправлялась на сервак. Был общий регистр "набиваемых" данных. Вроде все работало, до тех пор, пока девочки корректно выходили из клиентских программ. Если же из программы вышли неккоректно в момент набивки накладных, то вся логика просто слетала. Приходилось наверчивать доп. операции, типа — сброс регистров набиваемых данных, эту операцию нужно было выполнять в тот момент, когда никто не редактировал накладные... И вся эта пляска вокруг однойтолько бизнес-задачи. В общем, наглядная демонстрация процедуры удаления гланд при неправильной начальной предпосылке расположения оных.
IT>И как ты удаляешь эти гланды теперь?
Удаляются сами. Удаленные (в смысле remote) объекты в .Net имеют время жизни. Корневым спонсором для удаленных объектов у нас является логическая сессия. Ее получает клиент при логине. Ее-то родимую мы и пингуем с некоторым интервалом. Если отвалились, можем заново восстановить соединение с собственной сессией и удерживаемыми объектами. Если компьютер девочки "отвалился" навсегда, то все созданные statefull объекты просто подбираются .Net remoting-ом через некоторое настраиваемое время (15 мин — оптимальный был для нас интервал). Соответственно, актуальные редактируемые остатки корректируются. Опять же — сами эти регистры теперь хранились в памяти сервера, поэтому задержки на обработку текущей редактируемой накладной стали просто незаметны на глаз. ("Хорошая" девочка гонит около 2 строк в секунду при набивке своей простыни, а таких девочек десятки, техника была — пень-III 800MHz, на stateless и триггерах работало, прямо скажем, не очень, зато стало весьма резво работать на statefull)
Кстати, именно для подобных накладных были задествованы все способы поддержки, включая полу-offline. На клиенте была предусмотрена персистентность, чтобы в случае обрыва связи не потерять сотни введенных строк. Допускалось продолжать редактировать эту накладную вне связи с сервером (список/иерархия товаров кешировался/синхронизировался на клиенте сразу при открытии первой же накладной. Синхронизировался — т.к. был тоже персистентным).
Были, разумеется, документы, которые не требовали поддержки сервером. В этом случае stateless получался сам-собой, даже при использовании нашего движка, заточенного под statefull. Просто когда данные передаются и сохраняются одной командой, то такое поведение аналогично stateless. Почему я и говорил, что реализация statefull более гибкая, т.к. позволяет получить любые модели поведения.
Т.е., я не напираю на statefull, я против того, чтобы от него отказываться. Особенно, если на нем неокторые вещи решаются легче.
V>>Статефулл решил все проблемы сразу, просто и элегантно.
IT>Неужели stateful штрафует девочек за некорректный выход из программы?
Он корректно "прибирает" за ними.
V>>Правильная тема для обсуждения. Свой енжин пишу как раз и для подобных задач. Дело в том, что мы, разработчики, обычно точно знаем характер взаимодействия со своими данными, а сервак SQL — нет. Справочники я обычно смело кеширую на апп-серваке, и уверяю, эти join-ы работают гораздо быстрее, чем на стороне SQL. Если решать задачу не каждый раз заново, а именно решить единожды — то все получается ok.
IT>Советую пользоваться индекстами
Хороший совет
Только все-равно быстрее без join-ов в БД.
IT>Впрочем, для монстрообразных справочников кеширование здорово помогает. Но, как я уже сказал выше, это с успехом и с теми же проблемами делается как в stateless, так и в stateful. Разницы никакой.
Вообще-то да. Просто в statefull используется единый механизм для синхронизации локальных кешей и состояний редактируемых объектов. Если посмотреть немного свысока — то это суть одно и то же.
V>>Кеширование бывает не только на апп-серваке, но и на клиенте. В статефул-модели мы можем поручить нашему движку самому разбираться, какие данные надо подгрузить на клиента, а какие не надо. То же самое при отправке их обратно на сервак. В случае медленного коннекта клиента — это сильное подспорье. Даже в случае быстрого, но тарифицируемого за каждый MB трафика — тоже.
IT>Могу ещё раз сказать про кешь — в обоих подходах с ним работа практически идентична.
Нет, не идентична, в случае, если ты редактируешь объект, который имеет как подчиненные так и связанные списки объектов.
Внимание, сейчас еще раз повторю, где именно получается выигрышь по трафику (уже обрисовывал как-то, видать не очень внятно и подробно).
Предположим, что мы редактируем объект, который содержит ассоциированные списки других объектов. Итак, суть редактирования объекта — задать как поля самого объекта, так и набить несколько ассоциированных списков (конкретно у нас — каждое задание включает список компьютеров, групп, ПО). Затем, ДО запуска задания необходимо проверить его на коректность. Соответственно, все данные объекта и подчиненные списки мы гоним на сервак. Сервак отвечает, что там-то и там нестыковка, — оператор подправляет нестыковку и опять пытается запустить задание. Так вот, все последующие разы мы посылаем задание не целиком, а лишь последние изменения — разрыв ассоциации, или добавление, или только лишь измененные поля.
Далее. Возвращаясь к тем же накладным. В случае stateless у нас были танцы с бубном насчет блокировок документов. Т.е., девочка набивала документ, время от времени она давит на [Save], текущие данные сохраняются в БД как черновик документа, НО, пока она не закрыла форму или не "отвалилась", мы знаем, что этот документ кто-то редактирует. (Кстати, при каждом [Save] на сервер посылаются... да-да, именно лишь последние изменения).
При первоначальной реализации stateless, в случае, если девочка "отвалилась", мы должны были как-то снимать блокировку с подобных документов. В нашем случае у админа был специальный тул, который позволял ему снимать блокировку с залоченного документа. К сожалению, это происходли довольно регулярно, и выглядело как элементарная недоработка (фирма-заказчик экономила на UPS-ах, а нам повезло внедряться, когда в соседнем корпусе этой фирмы шли строительно-сварочные работы

) .
У нас была еще одна вообще уникальная фича — совместное редактирование документа. Есть задача — внос остатков склада/торговой точки. Имеем более 5 тыс. позиций. Некоторые документы (конкретно — инвентаризация и отчет розничной точки) можно было открывать в режиме совместного редактирования. 5 девочек вполне нормально работали над одним документом. Как это адекватно разрулить в stateless, учитывая "отваливания", синхронизацию своих и чужих вносимых данных так, чтобы не гнать бесконечный документ целиком каждому из участников и т.д. — непонятно вообще.
IT>За исключением, пожалуй, одной вещи. В stateful кешь легко превращается в storage объектов в памяти, что при всей своей привлекательности несёт в себе массу других проблем.
Нет, кеш — это просто кеш. Как я говорил, у нас есть несколько политик, одна из которых — без кеша вообще. Задача кеша на апп-серваке — это именно оптимизация работы с хранилищем, и не более того. Если запрашиваемого объекта нет в кеше, то он загружается из БД (и оседает в кеше, если политика позволяет).
IT>>>Не так. Ты путаешь понятия cache и storage. Да, они оба как бы типа хранят состояние. Но наличие данных в первом совсем не обязательно и предназначено только для одной функции — снятие нагрузки с базы данных постредством реиспользования ранее произведённых запросов. Для второго — наличие данных это часть логики. Например, через кэш можно получить два разных экземпляра одной и той же записи БД. Для stateless в этом нет ничего страшного. Но, если твоё хранилище в stateful вернёт два экземпляра одного и того же объкта, то это уже не stateful, а либо глючный stateful, либо stateless
IT>Ну да, видишь, я об этом ещё два года назад говорил
V>>Правильное замечание. Для этих целей у нас отделены Entity от EntityView. Entity может быть только в 1-м экземпляре в кеше или не быть вовсе. Экземпляров EntityView может быть много. Всю потоко- и транзакционную безопасность они разруливают м/у собой сами.
IT>Ok. Пример из жизни. Загружаешь ты, например, накладную. Вместе с ней поднимается и товары. Товаров в базе данных мильёны. Грузить их все в кешь не имеет смысла. А может и имеет для каких-то случаев
В любом случае какие-то товары у тебя уже в памяти. Теперь в другой части приложения ты делаешь запрос по какому то критерию для получения списка товаров. В качестве результата приезжает список как закешированных, так и не закешированных товаров. Что ты делаешь в этом случае? Мне правда интересно. Я занималься в своё время такой фигнёй, задача решаемая, но как-то глядя на это всё плакать хочется.
Если интересуют подробности, то вот они:
— кеш товаров (и некоторых других "объемных") справочников на стороне клиента —
персистентный. (такая политика клиентского кеша, выполнен на MS Access, структура таблиц создается динамически, т.к. есть вся мета-инфа)
— каждый кешируемый тип объектов объект в системе имеет свой syncId, для кажой сущности он нарастает линейно при каждом сохранении в БД. Т.е. некоторые сущности (и их менеджеры) просто унаследованы от некоей базовой, поддерживающей синхронизацию. (можно не буду рассказывать, когда и как syncId сбрасывается).
— при открытии любого документа, содержащего справочные данные с выставленной политикой синхронизации (добавлю, у нас есть политики синхронизации/кеширования без персистентности, скажем, если объектов планируется до тысячи, то просто при первом обращении они грузятся в кеш, политику всегда можно поменять в run-time). Так, в при открытии документа производится синхронизация, т.е. просто клиент запрашивает данные с syncId большим, чем при последней синхронизации, последний syncId разумеется запоминается для последующих синхронизаций.
— в политике прописан интервал синхронизации. Обычно 0..30 сек (в зависимости от "критичности" актуальности), т.е. если с момента последней синхронизации прошло меньше времени, чем в политике, то синхронизация не нужна. Сама синхронизация выполняется не по таймеру, а именно по требованию, и запоминается время синхронизации.
— Если на сущность прописана политика подобной синхронизации, то сервак присылает вместе с ID так же и FriendlyName, т.е. пользователь открывает документ, обычно первые 1-2 сек окидывает его взглядом (а там все уже есть), или просто подводит мышку к нужному полю, за это время успевает происходить синхронизация, даже если этой рабочей станцией не пользовались несколько дней. При постоянной работе синхронизация занимает незаметное глазу время. Так вот, пока он подведет мышку к полю корреспондентов, эти корреспонденты уже синхронизированы на клиенте и их можно выбрать из dictionary.
— На клиенте кешируются, разумеется, не все поля объекта, а только лишь помеченные. В подавляющем большинстве { ID, FriendlyName }
повтор:
IT>В качестве результата приезжает список как закешированных, так и не закешированных товаров
Конкретно товары приезжали без FriendlyName (потому как, с одной стороны, довольно часто синхронизировались, с другой стороны — очень большие накладные бывали, по модему не очень оперативно получалось работать). Вместо отсутствующих имен ничего не отображалось менее секунды, потом отображалось.
Если в первый раз на данном десктопе запускали программу, то первая накладная открывалась заметное время (более 30 сек) при размере справочника товаров 5 тыс и все это по модему 33kb. Зато потом — весьма адекватная работа, незаметно, что по модему. По локалке — менее 2 сек.
-----
Да, еще один момент про statefull.
Было обнаружено, что уменьшение разрядности ID ВСЕХ справочников до 16 бит неплохо сказывается как на производительности БД (примерно
в 4 раза быстрее стали перепроводится складские документы и строится отчеты), так и на работе по медленным каналам. Вопрос вылета за диапазон, при постоянном добавлении/удалении решался вводом сервиса счетчиков, которые выделяли ID-шки, и дополнительного кеша, куда складывались ID-шки удаленных объектов. Т.е. я исходил из предположения, что вряд ли будет более 65536 клиентов или сотрудников или товаров. (идентификация объектов не сквозная, разумеется, у кажого типа объектов — свой счетчик)
ID-шки выделяются централизованно сервером приложений. Для уменьшения количества глупых запросов за каждой ID-шкой, можно запрашивать сразу несколько штук. Было бы неплохо гарантировать затем возврат всех неиспользованных ID-шек при неккоректном завершении программы. И опять, наличие сессии и знание об существующих statefull-объектах позволяет корректно их удалять, и возвращать все что нужно куда нужно.
V>>Конечно StateFull. Ибо любой statefull легко приводим к stateless.
IT>
Ты это серьёзно? Может мы используем разные термины? Ты под stateful понимаешь то, что я под statless, а я под stateful, то что ты под stateless?
Маловероятно. Я просто имел в виду режим "сквозной работы" statefull-объекта. У нас значения FirstName и LastName запрашиваются у EntityView, а не самого Entity. Конкретная имплементация конкретного EntityView может представлять из себя "сквозняк" и не иметь собственных состояний. У базового EntityView есть св-во Entity, так вот — оно виртуальное
Более того, сигнатура метода такова:
VariantT[] GetFieldValues(short[] fieldIds);
Если список fieldIds пуст — то просто получить значения всех полей.
Для отправки обратно значения полей есть 2 сигнатуры:
struct FieldValue { short FieldId; VariantT value; }
void SaveEntity(VariantT[] fieldValues);
void SaveEntityFields(FieldValue[] fieldValues);
Какую сигнатуру выбрать — решает движок с клиентской стороны в зависимости от того, какие поля редактировались и какие там сейчас данные, в общем — минимизирует траффик.
(Есть аналогичные методы SendEntity, SendEntityFields)
------
Иногда мы получаем stateless просто вот благодаря сценариям использования. Т.е. просто поднимаем объект (получаем ссылку сразу вместе с данными, см. ниже перегрузки методов), потом просто сохраняем одним движением. ВСЕ. Такой же stateless, только в профиль.
struct ObjectWithValues {
IEntityWrapper entity; // удаленный объект
VariantT[] fieldValues; // его поля
}
ObjectWithValues GetObjectWithValues(ObjectIdT objId);
IEntityWrapper GetObject(ObjectIdT objId);
В большинстве случаев используется первый вариант, т.е. мы получаем объект за одно действие, и за одно действие его сохраняем. Это один из типичных сценариев.
По сигнатурам методов видно, что они не предназначены для непосредственного использования (short fieldId). на стороне клиента есть EntityAdapter, к которому мы биндимся так же, как к DataView, и который скрывает все тонкости работы с апп-серваком, пользуясь мета-информацией. Которая, кстати, тоже кешируется
IT>Вот простой вопрос. Как ты превратишь stateful решение в масштабируемое?
Масштабирование — тот самый тонкий и весьма отдельный вопрос. Т.е. мне вполне понятно желание сделать масштабирование не напрягаясь особо. А что — элементарно поставил кластер БД, прилепил к нему несколько stateless App-серваков, и радуешься жизни. Предлагаю при масштабировании напрячься, но только чуть-чуть.

Приведу свой же ответ на подобный вопрос:
Да не бывает полностью statefull-приложений. Система управления предприятием может иметь сервисы внутри апп-сервака, которые представляют из себя stateless. Просто редактирование связанных документов — это именно та область, где очень неплохо работает statefull, и очень сложно добиться адекватного поведения от stateless. Я бы разбил логику сервера приложений на несколько составляющих:
— бизнес-сервисы, т.е. некие статические бизнес-методы или методы статических объектов, которые выполняют операцию за один вызов, тут у всех волей-неволей получается stateless
— поддержка редактирования документов, здесь statefull рулит, т.к. иногда может потребоваться прилично информации для поддержки процесса, и эту гору информации необязательно гонять каждый раз на клиента.
Соответственно, когда речь заход о масштабируемости, то учитываем свою специфику. Например, сервисная часть легко поддается "тупому" масштабированию. Statefull-часть может работать вообще на отдельном сервере приложений, если очень надо... В принципе, там большая скорость обычно не требуется, ибо люди вносят/изменяют информацию крайне медленно с т.з. выч. систем. А отчетность и аналитика сидит на сервисах, т.е. на других хостах в нашем случае. Если же мощности все-равно не хватает, то я бы разбил бизнес-задачи на несвязанные или слабосвязанные наборы документов, и распределял бы нагрузку именно таким образом — согласно уровню связанности.
V>> У нас в системе есть несколько сущностей, которые ведут себя как stateless, потому как на каждый чих скидывают свое сосстояние в БД (и в кеш соответственно). Владение разными экземплярами EntityView одним экземпляром Entity автоматичеки синхронизирует изменения в соседних клиентских сессиях.
IT>Какой же это stateless? Типичный stateful, только с гарантированным сохранением данных в БД.
И с гарантированным получением оттуда (либо из ОБЩЕГО кеша). Какой же это statefull?

Или я чего-то недопонимаю?
IT>>>>>То ли в память, то ли в процессор. И не надо говорить что сейчас памяти навалом. Возьмём хотя бы наш сайт, у нас данных в базе на гиг, но если это всё положить в память, в виде найс объектной модели, то сервер сразу ляжет, т.к. под это понадобится в разы больше памяти чем используется БД.
V>>Всю модель никто и не ложит в память. Обычно настраивают политику использования кеша для каждой сущности. Для справочных данных — это было бы оправданно почти всегда, для остальных — в зависимости от задачи.
IT>Для остальных вообще как правило ничего не имеет смысла кешировать.
Когда куча сущностей взаимодействует и ссылается друг на друга, то трудно определиться — какая из них справочная, а какая — нет. Практически каждая осмысленная сущность — справочная. Ввиду этого я вводил 2 уровня кеширования:
— { ID, FriendlyName}
— вся сущность.
Не пытался кешировать только движения.
В бухгалтерии, однако, полезно кешировать даже движения, если организовать разделение м/у актуальными данными текущего периода и прошлыми данными. При наличии кеша, такое разделение даже не требуется отображать в БД и иметь тонну гемора всвязи с этим (один из гемморов в 1C, Accent, R-base). Т.е. при старте app-сервака просто высчитываем (или загружаем) состояние регистров на начало периода и подгружаем движения от начала периода до текущей даты. Очень прекрасно начинает работать оперативная бухгалтерская отчетность. Для банка я бы установил период в 1 банковский день.
IT>>>Более того, оказывается в GUI очень неплохо смотрятся несложные веб-формы
V>>Скажем прямо — когда как. Стоит захотеть положить на форму полнофункциональный грид — и сразу прощай веб-форма в ГУИ. Да и вообще, при интенсивном взаимодействии на клиенте с этим ГУИ неудобно работать с embedded web-form, гораздо проще оперировать обычным Windows.Forms.
IT>Разве? А где ты видел нормальный грид для гуёв? Веб-грид хоть и простенький, но с ним можно практически всё делать. А что нельзя, можно на таблицах. Думаю, кода будет не больше, чем при кастомизации гуёвого грида.
Нормальный — это Infragistics, например. У меня накопились доработки к нему (благо, он представляет из себя практически конструктор, и очень мощный). В общем, мало кода обычно, учитывая богатство мета-информации. В 99% процентов мне было достаточно подать ему (моей версии этого грида) источник данных и забыть. Остальное происходит автоматически.
V>>Хотя, в своем редакторе запросов взяли именно WebForm, и прилично натрахались со сложным взаимодействием с ней... И все только из-за мощных ср-в управления layout-ом.
IT>Вы наверное стремились всё сделать pixel-perfect?
Да нет, просто сам характер взаимодействия специфичен. Идет текст по ресайзэбл-форме, участки текста выделены цветом и подчеркиванием при наведении. Рядом со скобками идет кнопочки. Все это должно лэйаутиться и переноситься на новую строчку и т.д.
IT>>>>>Масштабируемость в стэйтфул — это вообще занятие для мазахистов. Сложность приложения из-за синхронизации увеличивается в разы.
V>>Все ясно. Вопрос вообще о подходе к написанию многозвенных приложений. С тем, что statefull изначально проще в реализации простых операций, типа: прочитать, изменить, записать, никто и не спорил. Однако очевидно, что с помощью statefull становится сложновато строить приложения, где необходима мгновенная реакция на изменения в системе, вызванные "соседним" пользователем. Применяя stateless вообще сложновато координировать м/у собой действия различных пользователей в системе, если таковая задача вдруг встанет. И этот принцип я бы не стал делить по применимости на Web- или GUI-приложения. On-line GUI клиента к RSDN я бы тоже выполнил в виде stateless модели. Однако WEB-формы выписки накладных из приведенного выше примера — однозначено statefull.
IT>Я уже сказал, что такой класс задач гораздо реже встречается в природе чем "простые операции типа: прочитать, изменить, записать". Да и такие задачи можно и нужно раскладывать на простые операции и в результате там от stateful останется один простой список текущих остатков. Ты же, похоже, создав целый фреймворк для твоей задачи, пытаешься не только обобщить это всё на весь stateful, но и убедить всех, что это единственно правильное решение. Это не так. Уверен, что твою задачу можно было бы спокойно решить малой кровью без строительства завода для производства одного гвоздя.
Не знаю, не знаю. В приведенном примере перешли на statefull именно из-за побочных эффектов в случае неккоректного завершения клиентского приложения, ненадежных блокировок документов и т.д.
Т.е. пришли к этому не сразу, а после месяцев реальной эксплуатации.