Регистрация  |  Вход

Агрегация и движение данных между объектами: проблемы и подходы

Агрегация и движение данных между объектами: проблемы и подходы

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

Главным инструментом для сложных случаев таких вычислений является стандартный функционал по созданию Репортов.

в тоже время очень часто требуется выполнять агрегация, калькуляция, сведения данных не в режиме Репорта, а непосредственно от одного объекта к другому в реал-тайме. Для этого есть стандартные средства как RoolUp fields c четырьмя типами калькуляции. В тоже время в реальных, то есть сложных ситуациях этих RoolUp fields и их стандартного функционала катастрофически не хватает.

По-этому, предлагаю обсудить это вопрос: как двигать данные между объектами, и так чтобы по возможности это было гибкое, легко настраиваемое решение, и в тоже время не стукнуть лимиты, а их много, они все такие разные и только и ждут своего часа, чтоб напомнить о себе.

И так варианты:

Стандартные RoolUp fields. Всем хороши, кроме своих серьезных ограничений:
(1) всего 25 на объект;
(2) работают только по М-Д связи;
(3) внезапно имеют минимальные возможности для настройки критерий:
- нет ИЛИ оператора между критериями вообще;
- нет возможности создать формульный критерий;
- нельзя использовать формульное поле как критерий (но, хорошая попытка, сынок);
- нельзя использовать формульное поле как калькулирумое поле (да, ты вообще смышленный). Точнее говоря, пока формульное поле очень простое - оно доступно в списке возможных калькулируемых полей. Но я сделал поле на один IF сложнее, и оно больше не доступно.

Воркэраунд для решения проблемы с критерием: сделать два поле: рабочее формульное и простое, которое использовать в RoolUp как критерий. Триггером двигать данные между полями.

Установить специальный тул для агрегации данных меду объектами: Declarative Lookup Rollup Summaries (DLRS)
Его суть в том, чтобы с помощью point-and-click интерфейса создавать и настравать апекс тригер между объектами, который и выполняют всю работу. Плюсы:
(1) удобно и просто
(2) можно настраивать РолАпы между объекта по простой связи, а не только между М-Д
(3) самые разные критерии
(4) можно иметь больше чем 25 ролапов на объект
недостаток:
калькуляция каждого поля выполняется посредством агрегатного SOQL, что технически верно, но, но вы уже догадались, что это значит что не больше чем 100 вовлеченных в цепочку калькуляции полей в течение одного ран контекста, а в сложных случаях этого может быть не достаточно.

Калькулировать все в тригере
Но как именно?
через агрегатный SOQL? тогда это ничем не лучше чем DLRS.
Просто брутально выполнять калькуляцию в коде? а вы знаете сколько там тысяч записей придет на калькуляцию? вполне возможно стукнуть таймаут или хипсайз лимит

Использовать WFR & field update or Process Builder (когда нужно просто двигать данные, без изменений)
в реальных сложных случаях множественных вычислений неприменимы, так как провоцируют ненужные апдейты, работают в пойми каком порядке, такую работу лучше делать и контролировать в тригере.

И да, если вы скажите клиенту, что пора выводить логику в асинхрон, сразу начнут делать лица, чтоб мол, зачем вы так с нами, что мы вам плохого сделали, мы так не договаривались...

Во-общем, куда не кинь всюду клин.
Но, это не должно удивлять, облачные вычисление - это мир лимитов.
Вопрос в том, как все организовать чтоб в них уложиться.
Это и предлагаю обсудить

Агрегация и движение данных между объектами: проблемы и подходы

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

Главным инструментом для сложных случаев таких вычислений является стандартный функционал по созданию Репортов.

в тоже время очень часто требуется выполнять агрегация, калькуляция, сведения данных не в режиме Репорта, а непосредственно от одного объекта к другому в реал-тайме. Для этого есть стандартные средства как RoolUp fields  c четырьмя типами калькуляции. В тоже время в реальных, то есть сложных ситуациях этих  RoolUp fields и их стандартного функционала катастрофически не хватает.

По-этому, предлагаю обсудить это вопрос: как двигать данные между объектами, и так чтобы по возможности это было гибкое, легко настраиваемое решение, и в тоже время не стукнуть лимиты, а их много, они все такие разные и только и ждут своего часа, чтоб напомнить о себе.

И так варианты:

[b]Стандартные RoolUp fields.[/b] Всем хороши, кроме своих серьезных ограничений:
(1) всего 25 на объект;
(2) работают только по М-Д связи;
(3) внезапно имеют минимальные возможности для настройки критерий: 
- нет ИЛИ оператора между критериями вообще;
- нет возможности создать формульный критерий;
- нельзя использовать формульное поле как критерий (но, хорошая попытка, сынок);
- нельзя использовать формульное поле как калькулирумое поле (да, ты вообще смышленный). Точнее говоря, пока формульное поле очень простое - оно доступно в списке возможных калькулируемых полей. Но я сделал поле на один IF сложнее, и оно больше не доступно.

Воркэраунд для решения проблемы с критерием: сделать два поле: рабочее формульное и простое, которое использовать в RoolUp как критерий. Триггером двигать данные между полями.

[b]Установить специальный тул для агрегации данных меду объектами: Declarative Lookup Rollup Summaries (DLRS)[/b]
Его суть в том, чтобы с помощью point-and-click интерфейса создавать и настравать апекс тригер между объектами, который и выполняют всю работу. Плюсы:
(1) удобно и просто
(2) можно настраивать РолАпы между объекта по простой связи, а не только между М-Д
(3) самые разные критерии
(4) можно иметь больше чем 25 ролапов на объект
недостаток:
калькуляция каждого поля выполняется посредством агрегатного SOQL, что технически верно, но, но вы уже догадались, что это значит что не больше чем 100 вовлеченных в цепочку калькуляции полей в течение одного ран контекста, а в сложных случаях этого может быть не достаточно.

[b]Калькулировать все в тригере[/b]
Но как именно? 
через агрегатный SOQL? тогда это ничем не лучше чем DLRS.
Просто брутально выполнять калькуляцию в коде? а вы знаете сколько там тысяч записей придет на калькуляцию? вполне возможно стукнуть таймаут или хипсайз лимит

[b]Использовать  WFR & field update or Process Builder [i](когда нужно просто двигать данные, без изменений)[/i][/b]
в реальных сложных случаях  множественных вычислений неприменимы, так как провоцируют ненужные апдейты, работают в пойми каком порядке, такую работу лучше делать и контролировать в тригере.

И да, если вы скажите клиенту, что пора [b]выводить логику в асинхрон[/b], сразу начнут делать лица, чтоб мол, зачем вы так с нами, что мы вам плохого сделали, мы так не договаривались...

Во-общем, куда не кинь всюду клин.
Но, это не должно удивлять, облачные вычисление - это мир лимитов.
Вопрос в том, как все организовать чтоб в них уложиться.
Это и предлагаю обсудить






Сложный ты вопрос поднял.
Мне кажется что в каждом отдельном случае придется колхозить свое решение.
Если по простому:
- укладываешься в лимиты - стандартные средства SF (триггеры/ролапы)
- выходишь за пределы лимитов асинхронные батчи.
что то посложнее
- выносим расчеты за пределы СФ. Находим платформу для работы с BigData и подходящим функционалом для таких расчетов и сливаем в нее данные для расчетов (хотя с теми объемами данных которые принято держать в SF справится любая дохлая база данных)
- ломаем расчеты на промежуточные уровни - синхронными средствами SF (триггеры/ролапы) калькулируем данные над небольшими порциями данных (к примеру запускать подсчеты над записями в пределах текущего месяца) и сохранять их. Конечный просчет проводить над этими промежуточными данными (по месяцам).

Опять же все эти решения сугубо кастомные и зависят от задачи. Поверь, если бы было универсальное решение мы бы уже давно пользовались им.

Сложный ты вопрос поднял. 
Мне кажется что в каждом отдельном случае придется колхозить свое решение.
Если по простому:
- укладываешься в лимиты - стандартные средства SF (триггеры/ролапы)
- выходишь за пределы лимитов асинхронные батчи.
что то посложнее 
- выносим расчеты за пределы СФ. Находим платформу для работы с BigData и подходящим функционалом для таких расчетов и сливаем в нее данные для расчетов (хотя с теми объемами данных которые принято держать в SF справится любая дохлая база данных)
- ломаем расчеты на промежуточные уровни - синхронными средствами SF (триггеры/ролапы) калькулируем данные над небольшими порциями данных (к примеру запускать подсчеты над записями в пределах текущего месяца) и сохранять их. Конечный просчет проводить над этими промежуточными данными (по месяцам).

Опять же все эти решения сугубо кастомные и зависят от задачи. Поверь, если бы было универсальное решение мы бы уже давно пользовались им.

OK,

продолжу тему, итак давайте перечислим процесс агрегации и апдатирования данных между объектами во временной плоскости:

(1) истинный реал-тайм процесс - все необходимые апдейты и вычисления на всех вовлеченных объектах происходят в процессе единого ран-контекста вовремя обновления source данных;

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

(3) по-вызову - юзер нажимает на кнопку - начинаются вычисления (вкл. асинхронные);

(4) по-вызову- schedulable (здесь все понятно);

и вот еще один вариант, фактически это микс "по-вызову" и "реал-тайм", где калькуляция фактически выполняется "по-вызову", но очень похожа на "реал-тайм" так как юзеру ничего нажимать не приходится:

(5) по-обращению к записи через стандартный лейаут или ВФ страницу.

вот пример:
есть объект Отчет, стоящий на вершине из пирамиды других записей. В норме обновление внизу пирамиды приводит к восходящему каскаду обновлений, заканчивающимся на Отчете. Реал-тайм процесс. В результате, когда юзер открывает запись Отчет, он сразу видит текущую информацию.
Но часть вычислений (особенно с более сложной чем просто линейной логикой)можно вынести в процесс по-вызову, но юзерам это может не понравится (еще кнопку нажимать надо). Тогда делаем так: в стандартный лейаут Отчета вставляем ВФ страницу, в Инит методе контроллера кверим дату, делаем вычисления, апдатируем если нужно и немедленно рендерим текущие данные на ВФ странице. То есть для юзера, это вполне себе как реал-тайм процесс.
Недостаток, в том, что вычисление происходят только при непосредственном обращении к записи через стандартный лейаут или ВФ страницу, но текущие вычисляемые данные не доступны например в лист-вью или при обращении к записи через код. Но вполне себе вариант, когда юзеру требуется посмотреть самому на данные в Отчет записи или последующая работа с ними начинается только после открытия записи на стандартном лейуате, например если после этого юзер жмет на лейауте на какую-то кнопку и начинается какой то процесс работы с данными.

Что думаете?

OK,

продолжу тему, итак давайте перечислим процесс агрегации и апдатирования данных между объектами во временной плоскости:

(1) [b]истинный реал-тайм[/b] процесс - все необходимые апдейты и вычисления на всех вовлеченных объектах происходят в процессе единого ран-контекста вовремя обновления source данных;

(2) [b]асинхронный[/b] (полностью или частично) реал-тайм процесс - тяжелые вычисления отстреливаются в асинхрон, но тем менее данные обновляются так быстро как это позволяют  сделать текущие асинхронные процессы.

(3) [b]по-вызову[/b] - юзер нажимает на кнопку - начинаются вычисления (вкл. асинхронные);

(4) [b]по-вызову- schedulable[/b] (здесь все понятно);

и вот еще один вариант, фактически это микс "по-вызову" и "реал-тайм", где калькуляция фактически выполняется "по-вызову", но очень похожа на "реал-тайм" так как юзеру ничего нажимать не приходится:

(5)[b] по-обращению к записи[/b] через стандартный лейаут или ВФ страницу.

вот пример:
есть объект Отчет, стоящий на вершине из пирамиды других записей. В норме обновление внизу пирамиды приводит к восходящему каскаду обновлений, заканчивающимся на Отчете. Реал-тайм процесс. В результате, когда юзер открывает запись Отчет, он сразу видит текущую информацию.
Но часть вычислений (особенно с более сложной чем просто линейной логикой)можно вынести в процесс [b]по-вызову[/b], но юзерам это может не понравится (еще кнопку нажимать надо). Тогда делаем так: в стандартный лейаут Отчета вставляем ВФ страницу, в Инит методе контроллера кверим дату, делаем вычисления, апдатируем если нужно и немедленно рендерим текущие данные на ВФ странице. То есть для юзера, это вполне себе как реал-тайм процесс.
Недостаток, в том, что вычисление происходят только при непосредственном обращении к записи через стандартный лейаут или ВФ страницу, но текущие вычисляемые данные не доступны например в лист-вью или при обращении к записи через код. Но вполне себе вариант, когда юзеру требуется  посмотреть самому на данные в Отчет записи или последующая работа с ними начинается только после открытия записи на стандартном лейуате, например если после этого юзер жмет на лейауте на какую-то кнопку и начинается какой то процесс работы с данными.

Что думаете?

Мне нравится этот подход!
Только, минус в том что агрегированных данных не существует в природе, пока пользователь не откроет страницу.
И это очень тонкая грань - через какое-то время клиент может поставить вполне логическую задачу по выгрузке агрегированных данных или попросит организовать какой-то постпроцессинг над агрегированными данными. Клиент же не знает как оно все внутри работает.
К тому же кнопка есть кнопка. Лучше уж с ней чем на onInit вешать. Может пользователь зашел на страницу совсем не за отчетом, а поле поменять какое. Но в итоге запуститься сложная процедура выборки и просчета данных (и где-то умрет дерево или исчезнет ведро нефти просто так).

Мне нравится этот подход! 
Только, минус в том что агрегированных данных не существует в природе, пока пользователь не откроет страницу.
И это очень тонкая грань - через какое-то время клиент может поставить вполне логическую задачу по выгрузке агрегированных данных или попросит организовать какой-то постпроцессинг над агрегированными данными. Клиент же не знает как оно все внутри работает. 
К тому же кнопка есть кнопка. Лучше уж с ней чем на onInit вешать. Может пользователь зашел на страницу совсем не за отчетом, а поле поменять какое. Но в итоге запуститься сложная процедура выборки и просчета данных (и где-то умрет дерево или исчезнет ведро нефти просто так).

По сути есть только два подхода:
1)реал тайм;
2)асинхронный;
остальное вариации, причем вариации асинхронного по итогу.

Если клиент хочет оставаться в реалтайм, то можно для избегания агрегатных SOQL, делать агрегацию снизу вверх.
То есть при каждом изменении значения на потомке менять агрегированное значение на родителе и так вверх по иерархии.Изменились значения на 200 потомках, максимум у них будет 200 родителей и даже SOQL не нужен для апдейта родителя.
Да много кода, да нужно учитывать инсерт, апдейт, делит и анделит, но зато реал тайм. Подойдет конечно не для всех вариантов, зависит от схемы БД или если много математики и т.д. но во многих случаях поможет избежать асинхрона.

По сути есть только два подхода:
1)[b]реал тайм[/b];
2)[b]асинхронный[/b];
остальное вариации, причем вариации асинхронного по итогу.

 Если клиент хочет оставаться в реалтайм, то можно для избегания агрегатных SOQL, делать агрегацию снизу вверх.
То есть при каждом изменении значения на потомке менять агрегированное значение на родителе и так вверх по иерархии.Изменились значения на 200 потомках, максимум у них будет 200 родителей и даже SOQL не нужен для апдейта родителя. 
 Да много кода, да нужно учитывать инсерт, апдейт, делит и анделит, но зато реал тайм. Подойдет конечно не для всех вариантов, зависит от схемы БД или если много математики и т.д. но во многих случаях поможет избежать асинхрона.

Как по мне - можно сразу откинуть подсчёты в Roll-Up полях, WF, Process Builder'ах и т.д. потому что всё это точно так же отжирает CPU time, при этом имея ограничения, которых нет у Apex'а. Второе - лично я бы сразу откинул "оптимизацию" на частичный пересчёт - в духе если мы суммируем например какое-то поле, то на update мы отнимаем от агреггированного значения старую величину и приплюсовываем новую, и прочее прочее, потому что в граничных ситуациях всё равно необходимо будет делать полный пересчёт всей иерархии, поэтому в угоду простоты кода и реализации я бы пожертвовал такой "оптимизацией" и делал полный пересчёт на каждый чих.
PS: чтобы не быть голословным по-поводу примера граничной ситуации представьте что наверху иерархии перерасчётов у вас Account и у него есть два поля которые влияют на все нижестоящие агрегации, к примеру Discount Rate (какую скидку предоставить) и Special Pricing Categories (на какие группы записей предоставить скидку) и попробуйте реализовать соптимизированную схему когда на Account'е меняются оба эти значения одновременно.
PPS: Да, понимаю что надо рассматривать каждую конкретную задачу отдельно, но в 99% случаев я предпочту простое в реализации и поддержке решение, в угоду более сложному и "оптимизированному".

Как по мне - можно сразу откинуть подсчёты в Roll-Up полях, WF, Process Builder'ах и т.д. потому что всё это точно так же отжирает CPU time, при этом имея ограничения, которых нет у Apex'а. Второе - лично я бы сразу откинул "оптимизацию" на частичный пересчёт - в духе если мы суммируем например какое-то поле, то на update мы отнимаем от агреггированного значения старую величину и приплюсовываем новую, и прочее прочее, потому что в граничных ситуациях всё равно необходимо будет делать полный пересчёт всей иерархии, поэтому в угоду простоты кода и реализации я бы пожертвовал такой "оптимизацией" и делал полный пересчёт на каждый чих.
PS: чтобы не быть голословным по-поводу примера граничной ситуации представьте что наверху иерархии перерасчётов у вас Account и у него есть два поля которые влияют на все нижестоящие агрегации, к примеру Discount Rate (какую скидку предоставить) и Special Pricing Categories (на какие группы записей предоставить скидку) и попробуйте реализовать соптимизированную схему когда на Account'е меняются оба эти значения одновременно.
PPS: Да, понимаю что надо рассматривать каждую конкретную задачу отдельно, но в 99% случаев я предпочту простое в реализации и поддержке решение, в угоду более сложному и "оптимизированному".

ilya leshchuk
"оптимизацию" на частичный пересчёт

прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое.

ilya leshchuk
простое в реализации и поддержке решение

что это за решение? брутально в цикле пересчитывать все требуемые поля?

ilya leshchuk
откинуть подсчёты в Roll-Up полях

а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями

[quote="ilya leshchuk"]"оптимизацию" на частичный пересчёт[/quote]

прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое.

[quote="ilya leshchuk"]простое в реализации и поддержке решение[/quote]

что это за решение? брутально в цикле пересчитывать все требуемые поля?

[quote="ilya leshchuk"]откинуть подсчёты в Roll-Up полях[/quote]
а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями

Den Brown
прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое.

You're looking at him right now. Во-первых это сработает в 90% случаев, во-вторых его гораздо легче использовать например из батча, в третьих - (частично вытекает из во-вторых) - удобно нормализовать данные если они покорёжены, или для уже существующих.

Den Brown
что это за решение? брутально в цикле пересчитывать все требуемые поля?

Да.

Den Brown
а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями

Тем что я написал - "это точно так же отжирает CPU time, при этом имея ограничения" о которых вы, кстати, сами и упоминали.

PS: опять же - каждый случай надо рассматривать отдельно, пока в голове не сложился как говорят silver bullet которым можно забороть данную проблему, хотя например идея о диком сплаве синхрона и асинхрона есть :)

[quote="Den Brown"]прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое.[/quote]
You're looking at him right now. Во-первых это сработает в 90% случаев, во-вторых его гораздо легче использовать например из батча, в третьих - (частично вытекает из во-вторых) - удобно нормализовать данные если они покорёжены, или для уже существующих.

[quote="Den Brown"]что это за решение? брутально в цикле пересчитывать все требуемые поля?[/quote]
Да.

[quote="Den Brown"]а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями[/quote]

Тем что я написал - "это точно так же отжирает CPU time, при этом имея ограничения" о которых вы, кстати, сами и упоминали.

PS: опять же - каждый случай надо рассматривать отдельно, пока в голове не сложился как говорят silver bullet которым можно забороть данную проблему, хотя например идея о диком сплаве синхрона и асинхрона есть :)

Den Brown
Стандартные RoolUp fields... внезапно имеют минимальные возможности для настройки критерий:
- нет ИЛИ оператора между критериями вообще;

здесь важно отметить, что нет ИЛИ оператора между критериями, но есть "ИЛИ" функционал между вариантами значений в пределах одного критерия.

Например, если тебе нужно использовать как критерий РекордТайп "А1" или "А2" - то это решается просто добавлением нескольких выбранных вариантов в значение критерия. А вот если тебе нужно сделать условие как РекордТайп "А1" ИЛИ Статус "Новый", то так уже не получится

[quote="Den Brown"]Стандартные RoolUp fields... внезапно имеют минимальные возможности для настройки критерий: 
- нет ИЛИ оператора между критериями вообще; [/quote]

здесь важно отметить, что нет ИЛИ оператора между критериями, но есть "ИЛИ" функционал между вариантами значений в пределах одного критерия. 

Например, если тебе нужно использовать как критерий РекордТайп "А1" или "А2" - то это решается просто добавлением нескольких выбранных вариантов в значение критерия. А вот если тебе нужно сделать условие как РекордТайп "А1" ИЛИ Статус "Новый", то так уже не получится