Pull to refresh

Comments 30

А что происходит с транзакциями при таких параллельных вычислениях в 1С?
Каждое задание в отдельной транзакции или все в одной? какой DTS используется тогда?
Каждый исполнитель — это отдельная сессия и свои транзакции при работе с БД. Чтобы не было проблем с блокировками нужно свести к минимуму общие ресурсы между исполнителями.
Однако, тогда данные в приведенном Вами примере сотчетом будут неконсистентными. Таблицы могут произвольным образом изменится.
Консистентность данных, как правило, важна в алгоритмах с пишущими транзакциями. Отчеты же строятся используя запросы с хинтами NOLOCK иначе они будут сильно тормозить работу системы.
Я о другом. У Вас данные запрашиваются в 3-х разных транзакциях. Кроме того, время реального выполнения запроса неизвестно. Так что данные у вас могут быть рассогласованы, и консолидированный отчет будет содержать билиберду.
Просто стоить отметить, что такая реализация параллельной обработки имеет весьма опасные последствия.
Во-первых кажущаяся легкость распараллеливания. Однако, из-за того, что все задачи выполняются в разных транзакциях могут возникать ошибки подобные описанной выше. Во-вторых, такие ошибки крайне сложно искать — они недетерминированны и могут не воспроизводится под дебагером, да и вообще требовать весьма специфических условий для воспроизведения (например на одном серваке есть ошибка, на другом нет).
Кроме того, ошибка может проявлять себя не как исключение (exception), а как ошибка в рассчетах, тогда это совсем гиблый случай кто и когда заметит такое.
Разве качество отчета повысится за счет отказа от распараллеливания, при условии, что запросы будут выполнены с NOLOCK?
Я не знаю, что такое NOLOCK. Я знаю что такое транзакции :)
Речь не про конкретный пример, а про то, что при такой реализации есть подводные камни, про которые имеет смысл сказать. Привожу более конкретный пример (просто из головы).
Процесс заключается в том, что сразу после оприходования товара на складе его необходимо списать документами продажи.
Оригинальная обработка выглядит так (мне C# ближе):
...
// хеш товар-количество
var articles = income.getArticles();
//оприходовали товар
SaveIncome(income);
// так делать не надо, но сейчас не скорость важна
foreach(a in articles)
{
    var outcome = FindOutComeByArticle(a);
    // резервируем товар a
    SaveOutcome(outcome, a);
}
...


Очевидное распараллеливание:
...
// хеш товар-количество
var articles = income.getArticles();
//оприходовали товар
SaveIncome(income);
// ускоряем в 1000 раз !
RunParallel(articles, (a) => var outcome = FindOutComeByArticle(a);
    // резервируем товар a
    SaveOutcome(outcome, a) );
...


Проблема в том, что такая реализация может великолепно отработать на тестах.
Оставлю интригу и напишу в чем проблема чуть позже :) Если меня не опередят конечно.
При написании параллельных штук, все-таки, нужно учитывать, что фоновые задания 1С это отдельные сеансы и так же, как на C# в рамках одной транзакции пример со списанием сделать не получится. А вот если данные достаточно независимы в каждой из порций, то упрощение управление заданиями (за счет фреймворка) значительно облегчит жизнь.

С отчетом, наверное, тоже. Зависит от специфицики данных в отчете.
Ну какая разница отдельные или нет? Параметры передать можем, значит и на 1С переписать мое тоже можно.
А вот независимость данных штука очень мрачная — в приведенном примере ее не замечали месяц.
Проблема о которой Вы пишите понятна, она связана с изменением данных.
Так же совершенно справедливо, что не каждый алгоритм можно распараллелить.

Что же касается отчетов и NOLOCK (он же READUNCOMMITTED) — хинт используется для не блокирующего чтения. В момент пока выполняется SELECT тысячи других транзакций могут изменять вычитываемые данные. Если же отказаться от не блокирующего чтения, то получим огромные проблемы с производительностью базы данных.
Речь не о том, можно ли параллелить алгоритм, а в транзакциях. Приведенный пример отлично будет работать в MySQL без транзакций (точнее — никаких спецэффектов не появится). Его можно выполнить на MS и Oracle использованием распределенных транзакций, и его можно переписать так, что он будет выполнятся так же и без спецэффектов.
Во-вторых, такие ошибки крайне сложно искать — они недетерминированны и могут не воспроизводится под дебагером, да и вообще требовать весьма специфических условий для воспроизведения (например на одном серваке есть ошибка, на другом нет).


Ну так это в любом многопоточном софте )
Распараллеливание на фоновых заданиях — это круто. Я делал синхронизацию между потоками исполнения. Но как сделать адекватное ожидание данных в потоке от другого потока так и не придумал. =(

Код
#Region PublicInterface

// Parameters
//      size    - integer   - count of process
//		comm    - string    - module and procedure name
//		param   - structure - structure of parameters of the procedure
//
Procedure Init(size, comm, param = Undefined) Export

        CommParam = new Structure;
        CommParam.Insert("size", size);
        CommParam.Insert("comm", comm);

        JobParam = new Array;
        JobParam.Add(CommParam);
        JobParam.Add(param);

        For i = 1 to size Do
                commParam.Insert("rank", i);
                BackgroundJobs.Execute(comm, JobParam, i);
        EndDo

EndProcedure

// Parameters
//		comm    - string    - module and procedure name
//		rank    - integer   - key of the process if Undefined wait all process in comm
//
Procedure Wait(comm, rank = Undefined) Export 

        Selection = New Structure("MethodName, State", comm, BackgroundJobState.Active);
        If rank <> Undefined Then
                Selection.Insert("Key", rank);	
        EndIf;
        ArrayOfJobs = BackgroundJobs.GetBackgroundJobs(Selection);	
        BackgroundJobs.WaitForCompletion(ArrayOfJobs);

EndProcedure

// Parameters
//		buf     - any       - module and procedure name
//		dest    - integer   - key of the destination process
//      msgtag  - integer   - tag to devide messege from one sourse
//		comm    - structure - construct in Init
//
Procedure Send(buf, dest, msgtag, comm) Export

        Param = new Structure;
        Param.Insert("buf", buf);
        Param.Insert("dest", dest);
        Param.Insert("source", comm.rank);
        Param.Insert("msgtag", msgtag);
        Param.Insert("comm", comm.comm);

        Message = new UserMessage;
        Message.Text = ValueToStringInternal(param);
        Message.Message();

EndProcedure

// Parameters
//		source  - integer   - key of the source process 
//      msgtag  - integer   - tag to devide messege from one sourse
//		comm    - structure - construct in Init
//
// Return
// 		any
//
Function Recv(source, msgtag, comm) Export

        Selection = New Structure("MethodName, Key", comm.comm, source);
        ArrayOfJobs = BackgroundJobs.GetBackgroundJobs(Selection);	
        If ArrayOfJobs.Count() < 1 Then 
                Return Undefined;
        EndIf;
        Job = ArrayOfJobs[ArrayOfJobs.Count() - 1];

        While True Do
                Messages = Job.GetUserMessages();

                Count = Messages.Count() - 1;
                For i = 0 to Count Do
                        Value = ValueFromStringInternal(Messages[i].Text);
                        If  Value.dest = comm.rank
                                And Value.source = source
                                And Value.comm = comm.comm
                                And Value.msgtag = msgtag Then

                                Return Value.buf;
                        EndIf
                EndDo;
        EndDo;

EndFunction	

#EndRegion


Я правильно понимаю, что это 1С на английском? Очень непривычно.
Интересное у Вас решение по передаче информации между фоновыми. Я в таком направлении даже не думал.
Когда мне нужно между заданиями передать информацию, я использую общее хранилище значений. В качестве идентификатора в хранилище использую ключ задания.
С хранилищами тоже можно. Но там разные тонкости со сборщиком мусора. По сути хранилище считается помеченным к очистке в памяти после окончания существования объекта, с чьим идентификатором запустили задание. Например если хранилище создано с идентификатором формы, то оно будет помечено к очистке только после закрытия формы. Может получиться неприятная ситуация с потерей переданных данных. А сообщения, отправленные заданием можно прочитать даже после его выполнения.

Но с общим хранилищем может быть и не важно это. Надо почитать про него по подробнее.
Я с названием ошибся. Имелось в виду хранилище настроек, там можно свои произвольные данные хранить. Оно в БД хранится, нужно не забывать чистить.
Хорошее начинание. Когда увидим что-то из этих радостей в типовых?
И при пере-проведении документов или формировании какого-нибудь большого отчёта можно будет спокойно переключиться и заниматься работой дальше, а не ждать пару часов или запускать 2-ю копию.
В целом подход не новый, как и его проблемы. Но, попробовал такой подход на мобильной 1с, где он дал бы не малый прирост к скорости, а оказалось — что все эти процессы выполняются последовательно:)
смотря что делают процессы. Если вы на множители числа раскладываете, то на мобильном одноядерном процессоре эффекта не будет. Однако, если каждый поток большую часть времени ожидает чтения из сокета, то эффект будет значительный (по количествую потоков.
Еще раз и внимательно прочтите что я написал :)
Повторюсь — все фоновые задания выполняются последовательно. А это значит, что как бы вы не разбивали, а на что бы вы не разбивали, и даже на 100500 ядерном процессоре — прироста не будет.

Алгоритм простой — второе фоновое задание выполняется только тогда, когда выполнилось первое :)

Есть конечно вариант попробовать запускать их из уведомлений, но тут уже дело такое, надо пробовать.
Т.е. в Ваших приложениях отсутствовали внутренние блокировки, и приложение часто делало вызовы к ядру, и при этом потоки выполнялись исключительно последовательно?
Возможно, это особенность мобильной платформы 1С (например блокировка есть в интерпретаторе языка).
Но еще хочу уточнить — а как Вы определили, что потоки последовательно выполняются? Может, это только кажется?

Не специалист по 1С, и до сих пор думал, что в данном случае не надо им было так косячить :)
Ну это не косяк платформы. Ведь такое поведение документировано. По крайней мере для толстого, тонкого и веб-клиента в файловом варианте. А по сути мобильное приложение так же из себя представляет файловый вариант базы.

v8.1c.ru/o7/201305fi/index.htm

Естественно при использовании клиент-серверной архитектуры поведение будет другим, и там возможно выполнение параллельно нескольких фоновых заданий.
Мда. Жаль не написана мотивация такой реализации.
Ну, с другой стороны — фоновые задания выполняются с веб сервера, если память мне не изменяет.
А вот на мобильной платформе такого нет. Тем более — если делать вызов функций уведомлениями, тут прослеживается потенциальная возможность распараллеливания процессов.
Т.е. не все так однозначно :)
На файловой 1С фоновые задания также выполняются последовательно :)
Прекрасная штука, взял себе, спасибо.

Есть у меня одна обработка, которая с периодичностью в N часов производит изменения в ~30 000 товаров. Последовательно это занимает около 3 часов, поэтому хочу это распараллелить. Изменения независимы друг от друга, поэтому Ваш механизм подходит. Но вот вопрос — сколько одновременных исполнителей можно ставить? Можно ли поставить 100? 500? На что это влияет? Память будет адски отжирать?
При добавлении потоков больше чем фактически ядер в кластере, часть потоков будет выполняться последовательно вытесняя друг друга, а на синхронизации между такими потоками большая потеря времени.
Понятно, спасибо.
Опираться только на фактическое число ядер в кластере я бы не стал. Как правило, потоки простаивают на ожидании данных от СУБД, дисковой подсистемы, сети и т.п. При этом ядро вполне успешно может использоваться другим потоком.
Из практики, на сервере 1С с 8 ядрами постоянно работают 30 исполнителей, которые выполняют очень ресурсоемкие для задания. Значение было получено экспериментальным методом. Система в равновесном состоянии — уменьшение количества исполнителей приводит к увеличению времени обработки заданий, увеличение — не уменьшает время.
По количеству исполнителей, простого ответа нет. Ресурс, который станет узким местом при распараллеливании, зависит от прикладного кода внутри заданий и железа (сервер 1С и СУБД). Кроме ресурсов, узким местом могут стать блокировки.
Самый простой способ — провести тесты с разным количеством исполнителей. В качестве отправной точки выбрать значение, которое подсказывает опыт. При этом наблюдать за работой системы и посматривать в журнал регистрации. Заодно можно выявить места для оптимизации.
Sign up to leave a comment.

Articles