Commit 88c9f06
fix(webapp): recover from ClickHouse JSON parse failures on out-of-range integers
Second class of poisoned-row failure in the runs replication path. PR
#3708 handled lone UTF-16 surrogates; this one handles bare JSON integer
literals that exceed ClickHouse's Int64/UInt64 range.
ClickHouse's `JSON(max_dynamic_paths=...)` column fits each bare integer
token into Int64 (signed) or UInt64 (unsigned). Bare integers strictly
outside `[-2^63, 2^64 - 1]` are rejected with `INCORRECT_DATA` (no
silent fallback to Float64). JS Numbers that are integer-valued but
above `Number.MAX_SAFE_INTEGER` still serialise via JSON.stringify as
bare integer tokens (no exponent) while `|value| < 1e21`, so any such
Number lands on the wire as a token CH cannot accept.
Customer-facing symptom: `scan-social-profiles` runs continued to be
stranded in `EXECUTING` on the Tasks page even after the surrogate fix
landed. CloudWatch showed `Dropped batch — ClickHouse JSON parse error
but sanitizer found nothing to fix` firing 8/8 times since the previous
deploy. Root cause: upstream JS Number precision loss on a 21-digit
Google Plus ID (`117039831458782873093` → `117039831458782870000`) —
the precision-lossy value still serialises as a bare integer that
exceeds UInt64.MAX, which CH rejects. Reproduced end-to-end against
ClickHouse 25.12.11.4 in Docker with the exact `Cannot parse JSON
object here` error from prod.
`apps/webapp/app/v3/eventRepository/sanitizeRowsOnParseError.server.ts`:
- New private `isUnsafeJsonInteger(value)` helper — true iff value is
a finite, integer-valued JS Number where `|value| < 1e21` (i.e.
JSON.stringify emits integer form, not exponent) AND `value` falls
outside `[Int64.MIN, UInt64.MAX]`.
- `sanitizeUnknownInPlace` gains a number-branch: when the predicate
holds, replace the Number with its string form. CH's dynamic JSON
column accepts a `String` subtype on the same path, so the row
inserts cleanly on retry. The numeric value was already
precision-lossy upstream (JS Number can't represent integers above
2^53 faithfully), so type-flipping to string is information-preserving
relative to what arrived.
- Float-valued numbers and large floats (>= 1e21, NaN, Infinity) are
left alone — JSON.stringify emits them with exponents or as `null`,
both of which CH accepts.
Recovery stays purely reactive — no extra cost on the hot replication
path. The sanitizer only runs after a ClickHouse parse-error rejection,
so healthy rows pay nothing.
`apps/webapp/test/sanitizeRowsOnParseError.test.ts`: four new unit tests
covering positive/negative out-of-range integers, boundary values
(MAX_SAFE_INTEGER, 2^63, UInt64.MAX itself), non-integer numbers, and
the actual `scan-social-profiles` nested shape with `gp_id:
117039831458782870000`. Plus an extension to `sanitizeRows` that
verifies surrogate and integer fixes are counted together across rows.
`.server-changes/runs-replication-bigint-recovery.md` — release notes.
Refs TRI-9755.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>1 parent eefb96c commit 88c9f06
3 files changed
Lines changed: 176 additions & 0 deletions
File tree
- .server-changes
- apps/webapp
- app/v3/eventRepository
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
Lines changed: 36 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
10 | 42 | | |
11 | 43 | | |
12 | 44 | | |
| |||
62 | 94 | | |
63 | 95 | | |
64 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
65 | 101 | | |
66 | 102 | | |
67 | 103 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
108 | 195 | | |
109 | 196 | | |
110 | 197 | | |
| |||
158 | 245 | | |
159 | 246 | | |
160 | 247 | | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
161 | 280 | | |
0 commit comments