Commit 1906cc0
committed
perf(storage): split UnspentTokensIteratorBy into UNION ALL of two index-friendly branches
UnspentTokensIteratorBy joined the ownership table when filtering unspent
tokens, which caused HasTokenDetails to emit a predicate of the form
`(wallet_id = $1 OR owner_wallet_id = $1)` spanning both tables.
PostgreSQL's planner cannot use the partial index on owner_wallet_id under
that OR predicate; instead it scans every owner=true,is_deleted=false
row, returning a single match after filtering thousands. On a node with
263k tokens this query ran in ~38ms and accounted for 43% of all PG
activity at c=200.
Rewrite as a single SQL with two UNION ALL branches that each use their
own index:
1. tokens directly owned: filters tokens.owner_wallet_id only, which
lets the planner pick the partial index
(owner_wallet_id, token_type) WHERE is_deleted=false AND owner=true
in microseconds.
2. tokens reachable via the ownership-delegation table: filters
ownership.wallet_id and joins to tokens. Returns zero rows when
delegation is not configured (ownership table empty), at which
point the branch is essentially free.
Both branches preserve the pre-existing LEFT JOIN ownership so a tokens
row with owner_wallet_id set but no ownership entry (StoreToken allows
this when its owners parameter is empty) remains visible — the original
query saw it via the OR predicate, this one sees it via branch 1.
The two branches are emitted into a shared query builder so the
placeholder counter ($1, $2, ...) increments continuously across them,
and parentheses around the SELECT operands are intentionally omitted
because SQLite rejects parenthesised SELECTs around UNION (PostgreSQL
accepts both forms). Neither branch has ORDER BY / LIMIT, so dropping
the parens does not change binding.
UNION ALL is preferred over UNION because PG would otherwise hash every
returned row over five columns (including bytea owner_raw) for a
deduplication pass that almost never finds collisions. Instead each
branch additionally selects ownership.wallet_id as a sixth column used
only for app-side dedup; the iterator filters duplicates by
(tx_id, idx, ownership.wallet_id), which:
- drops the rare cross-branch duplicate that arises when the same
(token, ownership) row matches both tokens.owner_wallet_id=$1 and
ownership.wallet_id=$1;
- preserves multi-row results for a single token that has several
ownership rows (the original OR-and-INNER-JOIN behaviour).
The trailing wallet_id column can be NULL when the LEFT JOIN finds no
matching ownership row, so it is scanned as sql.NullString. The dedup
key namespaces NULL distinctly from a (theoretically possible) empty
string so the two never collide.
Effect under stress (c=200, full wallet pool, warm institution):
prepare p50 1.16s -> 43ms (-96%)
prepare p99 — -> 228ms
TPS 112 -> ~178 (limited by endorser tail under load)
The selector hot path that drives prepare latency now spends microseconds
per UnspentTokensIteratorBy call instead of tens of milliseconds, and
single-statement execution keeps the connection footprint at one per
caller — important under high concurrency, where any eager two-query
split would deadlock on a constrained pool.
Signed-off-by: Evan <evanyan@sign.global>1 parent b5aaea3 commit 1906cc0
2 files changed
Lines changed: 172 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | | - | |
147 | | - | |
| 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 | + | |
148 | 171 | | |
149 | | - | |
150 | | - | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
151 | 200 | | |
152 | | - | |
153 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
154 | 204 | | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
164 | 240 | | |
165 | 241 | | |
166 | 242 | | |
167 | 243 | | |
168 | | - | |
| 244 | + | |
169 | 245 | | |
170 | 246 | | |
171 | | - | |
172 | | - | |
173 | | - | |
| 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 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
174 | 297 | | |
175 | 298 | | |
176 | 299 | | |
| |||
0 commit comments