influxdb(seed): drop update-domain rows + resume via staging sentinel
Job has been running 2h+ on per-day chunking with one new failure
mode: HA's `update` domain entities carry `release_summary_str` fields
containing multi-line markdown release notes. `influx write --format
csv` rejects the line:
Error: failed to write data: 400 Bad Request: unable to parse
'state,domain=update,entity_id=studio_code_server_update,...,
release_summary_str="## What's changed\n...
(InfluxDB's annotated-CSV nominally handles RFC4180 quoted multi-line
strings; the v2.7 `influx write --format csv` parser doesn't.) Per-day
chunking surfaces this as a one-day failure rather than corrupting a
larger window.
The script then dies via pipefail; kubelet OnFailure restarts the
container; loop replays from 2018 (no checkpointing); same bad day
fails again; backoffLimit gets eaten by retracing empty days that
already-succeeded.
Two fixes:
1. Filter out `domain == "update"` before pivot — these entries are
metadata toggles (update-available y/n, version strings, release
notes) not real time-series, safe to drop wholesale. `_field ==
"source"` filter from 614533c stays.
2. Sentinel file `/staging/last-completed-day` holds the last `$NEXT`
that succeeded. Script reads it on startup and resumes from there;
writes it after each successful chunk. A kubelet restart now picks
up where the previous attempt left off rather than retracing from
2018. The PVC survives pod restarts, so this works across the
container's full life — sentinel only resets when the Job itself
is deleted (which is also when we want to re-seed from scratch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,30 +124,49 @@ spec:
|
||||
# `pipefail` (bash-only, hence the `command: bash` above):
|
||||
# an `influx query` EOF inside the pipe propagates a non-
|
||||
# zero exit, the loop aborts, the Job retries.
|
||||
# Resume from the last completed day if a sentinel exists in
|
||||
# the staging PVC. Without this, kubelet's OnFailure restart
|
||||
# would replay from 2018 every time — 30+ min of retraced
|
||||
# empty days per retry — and the same bad chunk would burn
|
||||
# the backoffLimit before reaching new ground.
|
||||
SENTINEL=/staging/last-completed-day
|
||||
START="2018-01-01T00:00:00Z"
|
||||
# Stop at tomorrow → covers today inclusively.
|
||||
END=$(date -u -d "tomorrow" +%Y-%m-%dT00:00:00Z)
|
||||
CUR="$START"
|
||||
if [[ -f "$SENTINEL" ]]; then
|
||||
CUR=$(cat "$SENTINEL")
|
||||
log "Resuming from $CUR (sentinel)"
|
||||
else
|
||||
CUR="$START"
|
||||
log "Starting fresh from $CUR"
|
||||
fi
|
||||
|
||||
while [[ "$CUR" < "$END" ]]; do
|
||||
NEXT=$(date -u -d "$CUR +1 day" +%Y-%m-%dT00:00:00Z)
|
||||
log "Streaming $CUR → $NEXT ..."
|
||||
# Filter out the `_field == "source"` rows: HA writes
|
||||
# `source` as both a tag (which integration emitted the
|
||||
# point) AND a field (some entity attributes) on the
|
||||
# same measurement. pivot() cannot materialise a field-
|
||||
# derived column whose name collides with an existing
|
||||
# tag column, so it errors out at the first row that
|
||||
# has _field=source. Dropping that one field keeps the
|
||||
# `source` tag intact (the more useful of the two) at
|
||||
# the cost of losing the entity-attribute field, which
|
||||
# is rarely populated and unique per entity.
|
||||
# Filters in front of pivot, ordered by issue:
|
||||
#
|
||||
# 1. `_field == "source"`: HA writes `source` as both a
|
||||
# tag (integration emitter, e.g. "Home Assistant") AND
|
||||
# a field (some entity attributes). pivot() can't
|
||||
# materialise a field-derived column with the same
|
||||
# name as an existing tag column. Drop the field, keep
|
||||
# the tag (more useful — drives Grafana datasource
|
||||
# filters).
|
||||
#
|
||||
# 2. `domain == "update"`: HA's update domain entities
|
||||
# carry markdown release notes in `release_summary_str`
|
||||
# with embedded newlines. `influx write --format csv`
|
||||
# can't parse multi-line quoted strings and rejects
|
||||
# the entire line. Update entries are state-toggle
|
||||
# metadata (update available y/n + version strings),
|
||||
# not useful time-series — safe to drop wholesale.
|
||||
influx query --raw \
|
||||
--host "$LEGACY_HOST" \
|
||||
--token "$LEGACY_TOKEN" \
|
||||
--org "$LEGACY_ORG" \
|
||||
"from(bucket: \"$LEGACY_BUCKET\")
|
||||
|> range(start: $CUR, stop: $NEXT)
|
||||
|> filter(fn: (r) => r._field != \"source\")
|
||||
|> filter(fn: (r) => r._field != \"source\" and r.domain != \"update\")
|
||||
|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")
|
||||
|> drop(columns: [\"_start\", \"_stop\"])" \
|
||||
| influx write \
|
||||
@@ -156,6 +175,9 @@ spec:
|
||||
--org "$INFLUX_ORG" \
|
||||
--bucket "$INFLUX_BUCKET" \
|
||||
--format csv
|
||||
# Day complete — checkpoint so a kubelet OnFailure restart
|
||||
# picks up here instead of replaying from 2018.
|
||||
echo "$NEXT" > "$SENTINEL"
|
||||
CUR="$NEXT"
|
||||
done
|
||||
|
||||
|
||||
Reference in New Issue
Block a user