Currently it is tricky to backfill new custom modules. The easiest path is to tear everything down and start again. This is inefficient, particularly when the custom module is completely independent of the 'core' derived tables meaning these tables could be left untouched in theory.
The issue is that the manifest system used for incrementalisation has no insight into what modules have consumed what events, only that the event has been processed at some point.
Solution
It is hard to dynamically back-fill data in the model but we could assist the process by populating the events_staged table with all the events that have been processed up until the current point in time. This could then be consumed by the new custom module as part of a one-off job, then revert back to the standard job (including the newly filled custom module).
How this looks in practice using BQ as an example:
Before running the backfill we should ensure all _staged tables are empty i.e. all data has been consumed by the standard modules. Then:
- Calc limits for the run. We can skip steps 2-4 in the base module. In
05-batch-limits.sql:
CREATE OR REPLACE TABLE {{.scratch_schema}}.base_run_limits{{.entropy}}
AS(
SELECT
MIN(collector_tstamp) AS lower_limit,
MAX(collector_tstamp) AS upper_limit
FROM
{{.output_schema}}.base_event_id_manifest{{.entropy}}
);
- Generate
events_this_run. In 06-events-this-run join in event manifest rather than session manifest to get all events previously processed while using the limits calculated in the last step:
CREATE OR REPLACE TABLE {{.scratch_schema}}.events_this_run{{.entropy}}
AS(
-- Without downstream joins, it's safe to dedupe by picking the first event_id found.
SELECT
ARRAY_AGG(e ORDER BY e.collector_tstamp LIMIT 1)[OFFSET(0)].*
FROM (
SELECT
a.contexts_com_snowplowanalytics_snowplow_web_page_1_0_0[SAFE_OFFSET(0)].id AS page_view_id,
a.* EXCEPT(contexts_com_snowplowanalytics_snowplow_web_page_1_0_0)
FROM
{{.input_schema}}.events a
INNER JOIN
{{.scratch_schema}}.base_event_id_manifest{{.entropy}} b
ON a.event_id = b.event_id
WHERE
a.collector_tstamp >= LOWER_LIMIT
AND a.collector_tstamp <= UPPER_LIMIT
{{if eq (or .derived_tstamp_partitioned false) true}}
AND a.derived_tstamp >= LOWER_LIMIT
AND a.derived_tstamp <= UPPER_LIMIT
{{end}}
) e
GROUP BY
e.event_id
);
The reason for inner joining the events table with the base_event_id_manifest, rather than just processing all events between the lower_limit and upper_limit, is to ensure we don't process previously unseen late arriving events into the new custom module that havent previously been consumed by the standard modules. This could result in modules potentially becoming out of sync.
- Commit this to
events_staged using standard step 8.
- For the cleanup steps of the base module, ignore the manifest step.
- Let the custom module consume
events_staged
- Run
98-truncate-base-staged in the page views module to truncate events staged.
- Revert to standard job
This alternative base module logic could be toggled on/off using a backfill boolean in the playbook.
One potential problem might be if the backfill is particularly large it may not be possible to process all data in one go. In which case you would have to chunk the backfill into say n month batches. This adds complication due to sessions that straddle batches and therefore need to be reprocessed in the subsequent batch. This could be solved but would require slightly more complex logic.
Currently it is tricky to backfill new custom modules. The easiest path is to tear everything down and start again. This is inefficient, particularly when the custom module is completely independent of the 'core' derived tables meaning these tables could be left untouched in theory.
The issue is that the manifest system used for incrementalisation has no insight into what modules have consumed what events, only that the event has been processed at some point.
Solution
It is hard to dynamically back-fill data in the model but we could assist the process by populating the
events_stagedtable with all the events that have been processed up until the current point in time. This could then be consumed by the new custom module as part of a one-off job, then revert back to the standard job (including the newly filled custom module).How this looks in practice using BQ as an example:
Before running the backfill we should ensure all
_stagedtables are empty i.e. all data has been consumed by the standard modules. Then:05-batch-limits.sql:events_this_run. In06-events-this-runjoin in event manifest rather than session manifest to get all events previously processed while using the limits calculated in the last step:The reason for inner joining the
eventstable with thebase_event_id_manifest, rather than just processing all events between thelower_limitandupper_limit, is to ensure we don't process previously unseen late arriving events into the new custom module that havent previously been consumed by the standard modules. This could result in modules potentially becoming out of sync.events_stagedusing standard step 8.events_staged98-truncate-base-stagedin the page views module to truncate events staged.This alternative base module logic could be toggled on/off using a
backfillboolean in the playbook.One potential problem might be if the backfill is particularly large it may not be possible to process all data in one go. In which case you would have to chunk the backfill into say n month batches. This adds complication due to sessions that straddle batches and therefore need to be reprocessed in the subsequent batch. This could be solved but would require slightly more complex logic.