Агрегация и движение данных между объектами: проблемы и подходы
Одна из основный задач бизнес приложений - это агрегация, калькуляция, сведения данных.
Главным инструментом для сложных случаев таких вычислений является стандартный функционал по созданию Репортов.
в тоже время очень часто требуется выполнять агрегация, калькуляция, сведения данных не в режиме Репорта, а непосредственно от одного объекта к другому в реал-тайме. Для этого есть стандартные средства как 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% случаев я предпочту простое в реализации и поддержке решение, в угоду более сложному и "оптимизированному".
прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое.
что это за решение? брутально в цикле пересчитывать все требуемые поля?
а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями
[quote="ilya leshchuk"]"оптимизацию" на частичный пересчёт[/quote] прикольная идея, но это нужно быть очень рисковым человеком, чтоб положиться на такое. [quote="ilya leshchuk"]простое в реализации и поддержке решение[/quote] что это за решение? брутально в цикле пересчитывать все требуемые поля? [quote="ilya leshchuk"]откинуть подсчёты в Roll-Up полях[/quote] а чем они плохи? Единственно, что их неприлично мало (допустимое количество) и нужно попариться чтоб заставить их работать с условиями
You're looking at him right now. Во-первых это сработает в 90% случаев, во-вторых его гораздо легче использовать например из батча, в третьих - (частично вытекает из во-вторых) - удобно нормализовать данные если они покорёжены, или для уже существующих.
Да.
Тем что я написал - "это точно так же отжирает 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 которым можно забороть данную проблему, хотя например идея о диком сплаве синхрона и асинхрона есть :)
здесь важно отметить, что нет ИЛИ оператора между критериями, но есть "ИЛИ" функционал между вариантами значений в пределах одного критерия.
Например, если тебе нужно использовать как критерий РекордТайп "А1" или "А2" - то это решается просто добавлением нескольких выбранных вариантов в значение критерия. А вот если тебе нужно сделать условие как РекордТайп "А1" ИЛИ Статус "Новый", то так уже не получится
[quote="Den Brown"]Стандартные RoolUp fields... внезапно имеют минимальные возможности для настройки критерий: - нет ИЛИ оператора между критериями вообще; [/quote] здесь важно отметить, что нет ИЛИ оператора между критериями, но есть "ИЛИ" функционал между вариантами значений в пределах одного критерия. Например, если тебе нужно использовать как критерий РекордТайп "А1" или "А2" - то это решается просто добавлением нескольких выбранных вариантов в значение критерия. А вот если тебе нужно сделать условие как РекордТайп "А1" ИЛИ Статус "Новый", то так уже не получится