diff --git a/docs/design/bounded_distributed_topn.md b/docs/design/bounded_distributed_topn.md new file mode 100644 index 0000000000000..7065eb4aa4929 --- /dev/null +++ b/docs/design/bounded_distributed_topn.md @@ -0,0 +1,646 @@ +# Bounded distributed ORDER BY / LIMIT + +Status: implementation review completed through `b65946fd08133ad82eb46eab98dc5189643c39c8`; +substantive CI is green; independent design approval is pending (2026-09-07). +Historical measurements below remain labeled separately from final-head evidence. +Owner: issue #28285; regression constraint: #27968 must remain executable. +Current base: `e60b3eb3bdfd8ad5f1b5ae3eb8745037371f44d8`. +Implementation reviewed through: `b65946fd08133ad82eb46eab98dc5189643c39c8`. +Implementation branch: `xp/fix-28285-bounded-merge-top`. + +## Design review record + +| Field | Record | +| --- | --- | +| Change scope | Distributed ordered Top-N hierarchy, local Top admission/spill policy, remote scope encoding/write-back, runtime DOP expansion, and cleanup/accounting contracts | +| Design trigger | Changes a distributed wire protocol and ordering contract, crosses compile/process/operator/remote-execution ownership boundaries, and materially affects a hot path and capacity bounds | +| Design revision | The commit containing this document; an independent reviewer must cite that commit when recording the decision | +| Implementation revision | Code reviewed through `b65946fd08133ad82eb46eab98dc5189643c39c8`; the documentation-only alignment commit does not change runtime behavior | +| Design status | Ready for independent review; approval is not self-asserted and remains a merge gate | +| Blocking design questions from implementation review | None identified; the independent reviewer owns the final PASS or concrete design findings | +| Implementation conformance | Conforms to the selected hierarchy, protocol-v53 fallback, one-producer ordered edges, bounded gather output, resident winner admission, spill migration, and large-OFFSET fallback described below | + +### Decision log + +| Decision | Rationale and accepted boundary | +| --- | --- | +| Add a protocol-v53 ordered-stream edge and hierarchical MergeTop | Eliminates global P*K payload materialization while preserving one sorted stream per actual worker and CN boundary | +| Activate the hierarchy only for materialized order columns and estimated/unknown pressure | Avoids reevaluating volatile expressions and retains the lower-overhead compatible path for small bounded inputs | +| Let small fixed-width and varlen winners remain resident | Avoids mandatory disk I/O for ordinary small Top; actual-byte admission compacts dead history or migrates survivors under pressure | +| Keep large OFFSET on external Order above the 16,384-row prefix bound | Spill-backed Top still retains O(K) keys/references and their growth overlap, so a small final LIMIT does not prove safe Top admission | +| Retain O(K) local keys/references in v1 | They remain allocation-accounted and may return a controlled resource error; fully external key selection is a separate design | +| Drain finite producer output instead of adding prefix-stop in v1 | Preserves substantive producer errors and existing terminal arbitration; early producer stop needs a separate protocol decision | + +### Remaining rollout gates + +- An independent reviewer must approve this design revision and record the + decision on the PR before implementation approval. +- Mixed-version deployment must demonstrate that the negotiated version keeps + old peers on the compatible MergeTop/MergeOrder path and rejects an ordered + payload that a peer cannot understand. This is a deployment gate, not evidence + inferred from codec unit tests. +- The final binary has issue-shaped co-located three-CN evidence. Reproducing the + original three-host TKE topology and comparing a historical-good build remain + production rollout evidence; absence of either does not change the source-level + correctness result already reviewed. + +## Problem and review decision + +Reject the earlier proposal to unconditionally restore resident MergeTop. +It addresses the #28285 plan regression but leaves large valid outputs limited +by one allocation and mistakes a bound on row count for a bound on physical +capacity. Keep the vector ownership/error-classification fixes from #27999. + +The required result is efficient completion within available memory and spill +resources, including both the large-LIMIT narrow-row case and wide payloads. +A controlled error is appropriate when actual resources or the supported +single-record representation are exhausted. It is not an acceptable substitute +for executing an otherwise feasible query solely because all output rows were +put in one vector. + +Observed facts: + +- #28285 identifies `17c161a3`, immediately after `0db2d5e`, as the regression. + It replaces global MergeTop with blocking MergeOrder followed by Limit for + large/runtime multi-scope LIMITs. +- MergeOrder receives every candidate batch before emitting its first result. + MergeTop also consumes all input, but discards losing candidates while doing so. +- MergeTop retains all selected payload in one batch, then shuffles and returns + that batch. Allocation peaks include input clones and replacement capacity. +- In `Vector.Copy`, replacing a non-inline variable-length value appends area + bytes. Dead payload remains until reclamation; N live rows do not imply only + N rows' payload capacity. This also matters for variable-length local Top keys. +- #27968 requests up to 2,499,999 VECF32(1024) payloads: approximately 9.54 GiB + for that column if the limit is filled. The issue reports the actual vector + grow crossing 2 GiB. That ceiling can reject a feasible query even on a + machine with sufficient total RAM; actual post-delete row count must be + recorded in reproduction, not inferred from LIMIT. +- Local Top spill logic is unchanged between the two historical commits. + It writes incoming full-row batches and reconstructs selected rows in chunks. +- A WaitRegister can have multiple senders. `newParallelScope` duplicates the + operator tree; ordinary Merge and per-CN scope grouping can interleave batches. + An individually sorted batch is not evidence of a sorted receiver stream. + +Existing 55 evidence is diagnostic, not acceptance of this new proposal: +10M/500K took 10.92s versus 13.64s; old 50M/2.5M INSERT took 290.7s. +The new 50M timeout occurred while executing EXPLAIN PHYPLAN, which runs its +inner query. It is not a matched INSERT timing. Finding no directory entries +does not prove zero spill: open temporary files can already be unlinked. + +## Algebra and contracts + +### Review 5127191041: correction contract + +The two reproduced regressions are ordinary corrections to admission policy, +not a new distributed protocol. Retain the ordered gather for #28285 (no +OFFSET, K=5M), but restore the existing 16,384 candidate-prefix rewrite bound +for OFFSET. An external payload file does not externalize Top's O(K) keys, +references, or their growth overlap, including across parallel workers. + +For small local Top, a varlen type alone is not grounds for disk I/O. Admit +winning rows into a byte-limited resident batch; compare losers before copying. +Before a winner can exceed the resident logical window, compact dead replacement +history if live rows plus that candidate fit in half the window. Otherwise +write only the current survivors, construct their key/reference state, and +publish the resident-to-spill transition before consuming the pending row. +Failed copy/write/allocation aborts the attempt: no partial-row retry and no +output publication. All temporary/old/new capacity remains allocation-accounted. +The window is at most 16 MiB and one eighth of the single-allocation ceiling; +it is an admission policy, not a second physical-memory ledger or RSS claim. +The existing account remains authoritative and may reject a minimal work set. + +Spill-mode Top delays serializing an input batch until its first winner. A +fully rejected batch has zero payload writes; it still must be scanned for +ordering keys. Ordered distributed producers retain their bounded-output +contract. A genuinely single-worker scope needs no ordered-edge hint. + +Validation: exact OFFSET result and cleanup under the review's 160 MiB budget; +small-varlen zero-spill control; replacement compaction and mid-batch migration; +wide rows, disk/allocation rejection, cancel/reset; matched small-Top benchmark +and final-head issue-shaped 100M/K=5M execution. Historical timings are not reused +as final performance acceptance. + +Let S be input rows, P actual producers (not merely CNs), and K the demanded +ordered prefix. For LIMIT L OFFSET O, K = O + L only when checked addition and +the SQL semantics permit it. Otherwise retain the existing correct full-order +path. LIMIT 0 and runtime parameters retain their established public semantics. + +For the same ordering relation and bag semantics: + +`TopK(union inputs) = TopK(union TopK(each input))`. + +Rows excluded from a prefix have K rows ahead of them in that subset. They +cannot be necessary to produce a valid global prefix. Equal keys preserve +multiplicity; arbitrary tie selection is allowed only where SQL leaves it +unspecified. Tests use unique tie-breakers when exact row identity matters. + +After each producer has an ordered stream, its head is a bound on its unseen +tail. The minimum of all active heads is the next output. No output is safe +while an active stream has neither a head nor EOF. A slow stream must be waited +for or failed/cancelled, never treated as empty. + +This supports these invariants: + +1. Ordinary Top-K returns exactly min(K, S) rows in the existing SQL order. +2. Global merging does not retain K payload rows or consume all P*K candidates + before it can return output. +3. Reconstructed payload and ordered-gather output have a 64 MiB logical payload + window and an 8,192-row window, reduced further for a smaller allocator cap. + Vector capacity rounding, sidecars, input windows, and growth overlap are + charged by the existing allocation accounts, not by `Batch.Size()`. The + 64 MiB window is not an exact physical peak/RSS guarantee. Large result + cardinality alone never requires one large payload allocation. +4. Local Top retains fitting winners and spills source payload after actual + pressure. Spill mode still retains O(K) row references and ordering keys per + producer. Those allocations remain query-accounted; a fully external + key-selection state is a separate follow-up boundary. +5. Success, rejection, error, cancellation, and reuse have one cleanup owner. +6. Early stop applies only to the exclusively owned pure read subtree. Shared + producers, found-rows consumers, and required effects retain their semantics. + +## Selected architecture + +```text +scan / filter / join / aggregate output + -> local Top-K builder, resident or external + -> ordered stream per actual worker + -> bounded ordered fan-in within CN, when necessary + -> ordered CN stream + -> bounded ordered fan-in at coordinator + -> OFFSET / LIMIT semantics -> SELECT or transactional INSERT consumer +``` + +The new capability is an ordered gather at a scope boundary. Keep it distinct +from MergeOrder, whose input contract is a collection of sorted runs rather +than one sorted stream per receiver. It does not add a global spill store. +PostgreSQL's Gather Merge provides an architectural precedent for preserving +worker order; MatrixOne must additionally close its own parallel expansion, +transport, accounting, and cleanup contracts. See the +[PostgreSQL documentation](https://www.postgresql.org/docs/17/how-parallel-query-works.html). + +Implementation v1 uses the following deliberately narrow activation contract: + +- protocol version 53 plus serialized Top output, MergeTop input, and receiver + edge bits prove that every participating CN understands the boundary; +- the planner activates it only when every ORDER BY key is already a materialized + input column. Non-column expressions retain the compatible MergeTop or + MergeOrder plan until hidden-key materialization is implemented, so a + volatile expression is never silently reevaluated as a new ordering key; +- every runtime DOP worker gets its own one-sender edge, a CN-local gather + reduces those streams to one ordered CN stream, and the coordinator repeats + the same bounded merge; +- for large static limits, a plan uses the hierarchy when the estimated candidate set + `min(input rows, K * runtime fanout) * row size` exceeds 512 MiB. This is four + MergeOrder spill windows: below it, the existing vectorized path avoids the + extra hierarchy. Runtime parameters use the full estimated input as their + candidate upper bound; missing/invalid estimates choose the bounded path. + Small static limits additionally require a type-based resident payload proof: + all projected/order values are fixed width, with K times their widths fitting + a 16 MiB allowance (including conservative bitmap space). Runtime limits and + unbounded/unknown payload types choose the hierarchy independently of estimates. + When the protocol/order-expression gate forbids hierarchy, these unsafe resident + plans use Top plus MergeOrder. Every updated local Top also checks its actual + first input schema before retaining payload. Non-ordered small Top uses actual + winner-byte admission with compaction/migration as specified above; a varlen + type alone does not require disk. Single-worker scopes do not request an + ordered-edge contract they do not consume; +- once the hierarchy is selected, each local Top preserves the ordered-output + contract but chooses storage independently. Fitting small winners remain + resident; dead varlen replacement history is compacted, and survivors migrate + to external payload storage only under actual byte pressure. Large K starts in + spill mode. Both resident and spilled paths emit the same sorted rows; +- spill reconstruction and ordered gather output are bounded by both 8,192 rows + and 64 MiB (except that one individually valid row must make progress). The + logical window is additionally capped by the allocator's single-allocation + ceiling with slack for growth. Fixed and variable-width winner runs are copied + in bounded chunks using actual varlen payload sizes. A fitting resident Top may + return its complete small result in one batch; +- cleanup shares one 30-second deadline across every receiver. The first + implementation drains already-produced local Top output rather than + introducing a new receiver-stop terminal state; producer early-stop remains + disabled until error arbitration can prove that it cannot hide a substantive + producer failure. This preserves correctness while still removing the + blocking global MergeOrder materialization that caused #28285. + +### A. Preserve order across the physical topology + +- Introduce an explicit ordered-input scope/operator contract with ordering + expressions, optional prefix demand, and one logical sorted stream per edge. + Ordering keys are materialized before the boundary and carried with the row; + derived/volatile keys are not independently reevaluated by successive merges. +- After runtime DOP expansion, each Top producer has a distinct local edge to + an ordered gather. Do not let cloned connectors share an ordered receiver. + Keep parallel scans; do not force the entire scan to DOP=1. +- Per-CN regrouping must use ordered fan-in on this path. Preserve the existing + sibling dependency ownership that makes remote trees independently runnable. + CN count and receiver count are not substitutes for this proof. +- Plain Merge, shuffle, and arbitrary projection invalidate an ordering proof + unless explicitly shown to preserve the same keys. Validate the final graph + after cloning/grouping and after remote decode, before any row is published. +- Empty and failed-to-start producers complete their own edge. Do not invent + EOF to get around missing registration or a scheduling failure. + +### B. Stream the ordered prefix with bounded buffers + +- Maintain one borrowed current batch and one cursor per input; the heap holds + stream indexes, not copies of result rows. Refill only an exhausted input. +- Append winners into a bounded output batch. Release consumed input batches + after their referenced rows have been copied. No final K-row Shuffle exists. +- Use row and byte limits for chunks. Reduce a chunk before publication on + pressure; a single large row must satisfy the existing record/allocation + limit and be admitted explicitly. A row cap alone is insufficient. +- Credits bound prefetch by both count and bytes. Include decode buffers, + sender spool buffers, borrowed batches, output, and heap capacity in the + query/CN account. Reuse existing batch-flow and allocation primitives. +- Bound per-node fan-in using a tree where needed. Also bound the sum of all + active tree nodes on a CN: a tree alone does not bound total memory. +- Provide capacity for a minimal head window and output progress before + admitting the stage. If necessary choose lower DOP before producer startup; + never wait for memory that can only be released by a blocked downstream. +- Check for errors/cancellation between bounded work units. After K rows, + publish prefix completion and stop further requests. Already admitted + prefetch is finite and included in the accounting and cost model. + +### C. Local Top resource boundary + +The global change removes the reported P*K payload materialization. This PR +also makes spill reconstruction row-and-byte bounded: each selected row records +its actual output width while the source batch is available, and evaluation +uses that metadata to reconstruct at most 8,192 rows / 64 MiB per batch. A +single individually valid row may exceed the normal batch target to guarantee +progress. This closes the single-vector failure for a narrow ordering key with +wide projected payload, including the #27968 shape. + +The remaining local selection state is not independent of K. Spill mode keeps +O(K) row references and materialized ordering keys per producer. All of that +state is allocation-accounted and fails with a controlled resource error, but +v1 does not claim that an arbitrarily wide ordering key succeeds merely because +disk is available. A future fully external key-selection implementation must: + +- Keep a resident heap fast path for fitting selection state. SQL row counts + and estimated widths can guide policy, but exact allocation admission owns + safety. Compact dead variable-length bytes at a bounded threshold, with + admitted replacement capacity, or transition to external selection. +- Reserve a bounded spill/output working window before growing resident state + to its limit. Reclamation must not depend on a large new allocation after + the resident state has consumed the entire account. +- At a committed input boundary, externalize the current surviving candidates + and process the remaining input as byte-bounded sorted runs. The Top-K + identity permits discarding earlier losers. A failed multi-column mutation + cannot be used as that boundary: preflight/checkpoint unpublished changes, + and preserve the input cursor until the mutation or transition commits. +- Publish a run only after its write succeeds. Release its source state only + after publication. On partial write or failed recovery, abort the query and + clean all owned resources; never replay a partly applied row as new input. +- Use bounded fan-in external merging, truncating each completed merged run + to K. Keep merge-pass depth and resident run metadata bounded; use the + existing run management/codec where possible, with explicit common ownership + for primitives shared by Top and MergeOrder. Do not copy an entire spill engine. +- Disk admission covers live input runs plus the new output run until commit. + FD admission covers active readers and writer, not total run count. Exhausted + disk, FDs, depth, or the minimal working set returns the appropriate error. +- Output an ordered stream in chunks without a single K-row payload vector. + +The current source-payload spill writes a batch only after its first candidate +is admitted; fully rejected batches are not written. It can still write substantial input data and +requires a full scan of an unordered source. A future external key selector can +require multiple merge passes; neither O(K) total spill nor payload read-locality +improvements are claimed here. + +## Ownership, errors, and generations + +| State/owner | Transition or wait | Required terminal behavior | +|---|---|---| +| Ordered stage / scope | prepare -> register/admit -> prime all heads | Failed admission/start closes created edges and propagates the real error | +| Gather input cursor | borrow -> compare/copy -> release -> refill | One receiver owns the borrow; no next receive invalidates another stream's batch | +| Output batch / gather | build -> publish to consumer | Never publish partially built rows after an allocation or comparison error | +| Local builder | resident -> externalizing -> external -> ordered output | Run publication is the state switch; old ownership remains valid until then | +| Prefix completion / scope | emit K -> stop owned producers -> quiesce | Internal stop is success; query cancel and substantive errors remain errors | +| Shared input / consumer | early stop of one reader | Release that reader only; do not cancel other consumers | +| Transport / scope | full credit queue or missing head | Independent cancellation/stop wakes both sender and receiver | +| Prepared attempt | terminate -> quiesce -> Reset -> new admission | No previous cursor, EOF, limit, credit, error, or callback enters the next generation | + +Reuse End/Error/Abort, StopSending, and current scope error arbitration. +Call returns execution status; Reset performs terminal signaling. Do not send +terminals from Call. V1 cleanup drains the finite remaining local Top output +because the current protocol cannot distinguish an intentional prefix stop from +a producer failure without risking error masking. Concurrent teardown shares a +bounded deadline rather than paying one full timeout per edge. A later protocol +may stop producers early only after preserving any earlier substantive error. + +INSERT commits only after successful statement completion and producer +quiescence. An error after partial consumption must roll back the statement. +SELECT may have streamed rows before a later error, following its existing +protocol; never label that execution successful. + +The minimal wait graph is producer -> credit -> receiver -> missing head from +another producer. Stage startup must make every required producer runnable. +No consumer may monopolize the execution slots needed to start the producer +it waits on. Inject submission failure and a constrained worker pool to prove it. + +## Compatibility and rollout + +This is R3 design work: it changes distributed scope topology, ownership, +runtime expansion, and a material execution hot path. It requires its own +design review before implementation approval. + +Encode ordered-gather capability and configuration explicitly in the pipeline +representation. Both cloning and remote encode/decode preserve it. Version 53 +is the rollout gate. A node that cannot honor the ordering contract must +never silently decode it as ordinary Merge or MergeTop. + +New plans activate only when all participating nodes support the capability; +otherwise use the existing compatible plan before execution. Mixed-version +fallback may retain today's performance and must be visible in EXPLAIN. +Never change algorithms after result publication to accommodate an old peer. +Topology/capability changes invalidate a cached prepared physical plan. + +Retain existing row comparator, NULL/NaN/type rules, prepared provenance, and +spill codec. No table/catalog or persistent storage migration is proposed. +Spill is attempt-local and not resumable after restart. Rollback uses the +compatible planner path for new attempts after active generations quiesce. + +Existing analyzer counters expose input/output rows and Top spill rows/bytes; +allocation accounts own actual allocation admission and peak accounting. Cleanup +timeout diagnostics include receiver/channel state. Dedicated stream-count, +spill-read, and prefix-stop metrics are not implemented in this revision; neither +external key merge passes nor producer early-stop is implemented. Do not infer +these metrics from `Batch.Size()`, or add a second estimated memory ledger. +All resources remain charged to the owning tenant. + +The type-based guard retains the small fixed-width fast path. The runtime +resident path additionally retains fitting small varlen results, compacting dead +payload or migrating survivors on actual pressure. Ordered distributed producers +use the same resident-or-spill admission; once spill mode is active, lazy writing +bounds rejected-input I/O, not all candidate I/O. Updating a cluster cannot retrofit this runtime guard onto +old CN binaries: mixed-version fallback preserves protocol compatibility, while +the full payload safety guarantee requires all executors to carry this fix. + +## Cost model and alternatives + +Let F be fan-in, b the admitted input-window bytes, q the admitted queued bytes +per edge, and o the output-window bytes. One gather uses approximately +F*(b+q)+o plus O(F) cursor/heap state; allocation accounts enforce the actual +sum across the CN. None of these buffers grows with K. + +For a flat merge, selection CPU is O(P + K log P); a balanced merge tree can +perform O(K log P) comparisons plus bounded prefetch/initialization work per +level. Its buffers and copies at every level must be measured. Flat input +consumption is K plus per-stream lookahead and finite prefetch, capped by +available rows. This is not an exact K-row network or disk-read claim. + +The static activation estimate is deliberately about the pre-global-merge +candidate set, not the final K rows. The existing path remains preferable for +small inputs even when K itself is large relative to S. On 55, 1M input / +500K limit with approximately 146-byte rows ran in 1.15--1.28s through the +compatible path versus 2.82--2.94s when hierarchy was forced. At the issue-like +5% ratio, 10M input / 500K limit ran in 3.56--3.62s with bounded hierarchy +versus 21.19--21.49s on main. These measurements motivated, but do not replace, +the byte estimate and its conservative unknown-statistics fallback. + +The issue-shaped 100M input / 5M limit INSERT completed in 111.57s on the +fixed build and produced exactly 5,000,000 distinct keys from 0 through +4,999,999. The same data and service configuration on main `6e5f82f568` +remained incomplete at 300s; canceling the client rolled the target table back +to zero rows. Thus the matched one-CN comparison establishes at least a 2.69x +speedup without extrapolating the 300s lower bound. + +For 50M input and K=2.5M with 64-byte payload, global resident MergeTop needs +at least about 160 MB for that payload alone, before descriptors, keys, input +copies, dead area, and shuffle overlap. Streaming gather retains only windows. +For #27968, 4 KiB vector payloads remain bounded batches across the same path. +End-to-end improvement depends on how much time remains in local selection, +storage reads, and INSERT. No speedup multiplier is promised before measurement. + +| Alternative | Decision | +|---|---| +| Restore resident MergeTop for every K | Reject as complete fix: single-allocation ceiling and variable-length replacement history remain | +| Raise threshold / trust row-width statistics | Reject as safety design: physical capacity and stale/unknown statistics differ | +| Global spill Top on unordered candidates | Possible compatible algorithm, but existing implementation writes all candidates and can repeat local payload I/O | +| Give existing MergeOrder a terminal LIMIT only | Insufficient: still receives/materializes every candidate before first output | +| Ordered gather plus byte-bounded payload reconstruction | Selected: uses order already established by producers, bounds global state and output batches while retaining accounted O(K) local keys | + +## Validation and delivery sequence + +Historical author measurements on 55 (`10.222.1.55`) used the same service +configuration and data for fixed/main comparisons. They predate the type-based +payload admission change and are not performance acceptance for that change: + +- 1M input / K=500K stayed on the compatible path and completed in 1.286s, + matching the main-path envelope; forcing the hierarchy here was rejected + after it measured 2.82--2.94s. +- 10M input / K=500K completed in 3.56--3.62s versus 21.19--21.49s on main. +- 100M input / K=5M completed in 111.57s with exactly 5M distinct keys; main + did not complete within 300s and cancellation left the INSERT target empty. +- ASC/DESC, NULL and duplicate keys, LIMIT 0 and LIMIT beyond cardinality, + runtime parameters, expression fallback, cancellation, and a following + healthy query were exercised. Removing the final global merge was also + rejected by a black-box counterexample: parallel INSERT applied LIMIT per + worker instead of globally. +- Focused package, topology, codec, allocation-generation, and race tests pass; + static analysis reports zero issues. The five failures in the full compile + package are identical Parquet fanout failures on the clean base commit. + +### Revised-worktree validation (2026-09-07) + +Base: `f14eb824df4c74fb341ebecd46d2da3358503a95`; rebased PR commit: +`76670d12eb71fe010c426908a5b82ab47a65b6eb`, plus the local fixes. Unlike the +historical run above, the current complete compile package passes. + +- Complete Top, MergeTop and compile packages pass in normal mode. Top and + MergeTop also pass under the race detector. Changed packages and the DML + consumer pass `go vet`; the final service builds successfully. +- A 1 MiB allocator ceiling test executes K=8192 with varlen payload and no + ordered-output hint. Independent exact survivor/order and multi-batch checks + pass. MergeTop tests exercise interleaved varlen winners and an indivisible + row exceeding the injected logical output window. +- Fixed-width allocation rejection exposed an 8192-byte selection leak: failed + slice growth returned nil over the live owner. The Top wrapper now preserves + the original slice on failure. The regression asserts error classification + and zero account/pool ownership after cleanup. Small-K runtime spill disk + rejection also asserts the disk resource error and zero disk/FD/memory usage. +- The existing forced multi-CN DML fixture now includes five-row varlen Top-N, + a real remote physical plan, exact global OFFSET results and prepared reuse. + The entire fixture passed twice in one process on 55 (20.60s including cluster + startup, then 3.38s; the added subtest took 0.09s and 0.01s). Test executable + SHA256: `0abd0ab7a055a5d81334d3ec2ae40701066949877d7c83581d30ade36a01db0f`. +- LIMIT BVT extends its existing data with small-K varlen and prepared queries; + it adds no rows. Normal comparison passed 34/34 statements twice on the same + two-CN instance, and the test table count was zero after teardown. + Final service SHA256: + `455a8209b59ec9619860fb4e7c31b3139ff76e49048615b50e2a132121e7deb7`. +- Test startup incident: restarting the launch fixture with its existing data + failed CN admission before any SQL. Original data and logs were retained; + a clean test-owned data directory reached SQL and ISCP readiness. This is not + evidence of restart acceptance or a Top-N execution failure. + +The following historical worktree record predates review 5127191041. Consult +the correction evidence below for current measurements. Mixed-version execution +and restart acceptance are not implied by unit tests or historical measurements. + +1. Prove typed topology and a deterministic minimal gather, including shared + receiver and runtime-DOP counterexamples, before large benchmarks. +2. Implement ordered fan-in, byte-bounded output, and teardown as one closure. + Include encode/decode/version/clone tests and public multi-CN execution. +3. Follow up with variable-length key reclamation and a resident-to-external + key-selection transition if workloads require K-independent local key state; + do not attribute that unimplemented guarantee to this PR. +4. Run the combined implementation on 55 and review the complete change map. + Functional partial delivery cannot be described as full #28285/#27968 closure. + +| Counterexample/axis | Independent oracle | +|---|---| +| Interleaved sorted batches from two workers on one edge | Final topology rejects/repairs it before output; exact reference ordering | +| One slow/empty/failed producer; bounded scheduling slots | No premature output or fabricated EOF; injected release/error terminates | +| K=0/1, 16383/16384/16385, K>=S, parameter K, OFFSET overflow | Exact result and cardinality; reuse behaves like a fresh query | +| ASC/DESC, compound keys, NULLs, duplicates, NaN, expressions | Existing comparator semantics; full sort/reference with unique ties where required | +| N fixed, replacement count grows, increasing/decreasing payload lengths | Accounted physical peak remains within byte budget; same correct survivors | +| VECF32(1024) payload with narrow key | Byte-bounded multi-batch output succeeds despite a reduced per-allocation cap | +| Large variable-length sort keys | Accounted success or controlled resource error; external key selection is outside v1 | +| Pressure before/after candidate mutation and run publication | No lost/duplicate row; exact cleanup; external result equals resident reference | +| Disk/FD failure, partial/truncated spill, allocation rejection | Correct error class; zero terminal ownership; healthy following query | +| Full queue/held ACK, cancel, disconnect, stop racing real error | All owners quiesce; actual error is not masked; no producer drains unused output | +| Shared producers, SQL_CALC_FOUND_ROWS, DML consumer | Required work/count preserved; statement rollback after downstream failure | +| Old/new peers, prepared reuse after topology/capability change | Correct fallback/recompile; no silent ordered-to-unordered decode | +| Result identity and cleanup through SQL | COUNT and exact ordered/reference comparison; duplicate multiplicity; same-instance rerun | + +Use injected small limits for UT/BVT boundaries, not huge fixtures. Preserve +existing useful tests and add only distinct proof. Run focused/package/race +evidence for changed owners and consumers, then SQL tests on 55 only. + +Performance uses direct target SQL, without an unmeasured executing EXPLAIN in +front of it. Compare historical good, verified current main, and fixed build +under identical topology, DOP, rows, schema, data distribution, cache policy, +and resource limits. Use an ordered/interleaved repeated schedule to detect +noise rather than claiming stability from one run. Record build hashes and +data fingerprints. Compare 1M, 10M, 50M, and 100M with 1/2/3 CN where the +dimension adds evidence; vary narrow/wide rows and K/S, including high selectivity. + +Acceptance requires both original scenarios to succeed and the narrow-row +50M/100M performance to recover to the historical-good envelope in matched +runs. Report the observed variance; any material regression (initial alert +threshold 5%, subject to baseline variance) requires investigation. Small K +and single-CN controls must also remain within the measured baseline envelope. +Gather input rows must follow prefix-plus-prefetch, and gather spill must be +zero. Local spill, total CPU, RSS, allocation peaks, I/O, and cleanup are +reported separately. If gains are below expectation, attribute the remaining +time before extending the design. + +A finite suite cannot prove that no counterexample exists. Acceptance means +the stated invariants, reachable transitions, and mapped failure dimensions +have evidence; untested dimensions and known limits remain explicit. + +## Final implementation review evidence (2026-09-07) + +These checks cover the implementation through `b65946fd08133ad82eb46eab98dc5189643c39c8`. +Earlier failed or superseded measurements remain identified as historical evidence. + +Complete change-map closure against `e60b3eb3`: + +| Owner / consumer group | Contract and evidence | +| --- | --- | +| Top state, accounting and tests | Actual winner admission; old/new migration ownership; bounded output; OFFSET and small-varlen reproducers; normal/race/failure/reuse tests | +| MergeTop stream, legacy path and tests | One head per ordered producer; exact bag prefix; byte/row output windows; terminal errors and cleanup; full package/race tests | +| Compile routing, scope placement/expansion, duplication, lifecycle tests | Compatible external fallback; preserve worker/CN ordering and allocation owner attachments; scope tests and final SQL topology/result checks | +| Remote encode/decode, write-back and client cleanup | Preserve ordered flags through the entire decoded execution envelope, including the newly added local edge; decoded DOP regression repeated 100 times under race; multi-CN SQL | +| Protocol constants/generated codec and process edges | Explicit version 53 capability; legacy zero values remain unordered; receiver end counts/reuse/backpressure; protocol and edge tests | +| DML and LIMIT BVT | Exact ordered values and payloads, prepared reuse, delete/cast behavior, external large OFFSET; isolated SQL fixtures | + +Q1: resident, compacted and spilled batches have explicit owners. Migration +publishes only after successful copying; failure cleanup retains the old owner +and releases temporary keys, references and spill resources. Q2: ordered edges +have one real producer after DOP gathering; concurrent stage startup, existing +cancellation and terminal draining remain necessary, with no new wait primitive. +Q3: resident replacement history is bounded by admission/compaction, rejected +spill batches do not write payload, and large OFFSET avoids O(K) Top metadata. +Physical capacity is still charged; v1's accounted large-K key/reference state +is not claimed to be fully external. Independent approval of this design revision +and mixed-version deployment acceptance are not inferred from these tests. + +- Review OFFSET reproducer: 4,194,304 bigint rows, LIMIT 1 OFFSET 4,194,303, + 160 MiB shared account; correct final value 4,194,303 and peak accounted + bytes 103,374,880. Memory, spill-disk and spill-FD charges return to zero. + The normal UT uses the minimum 16,385-row routing boundary; the 4M fixture + is an explicit benchmark, not a permanently expensive unit test. +- Matched small-varlen Top benchmark: 128 batches of 8,192 ascending bigint + keys with 64-byte varchar payload, LIMIT 1. Five samples per build, median + main 7.682 ms versus fixed 7.738 ms; both write zero spill bytes. This + replaces the review head's full-input payload writes without unaccounted + retention or raising memory limits. +- Top, MergeTop and compile package tests pass. Resident compaction, + partial/full-heap migration, cancellation/short-write cleanup and reuse + have focused tests; full Top/MergeTop race checks pass. +- CI run 34069583657 on `7764c28e`: SCA found two `rowserrcheck` failures in + the DML fixture. Explicit per-query `rows.Err()` checks fix them without + suppressions. Matching golangci-lint 2.6.2 checks pass locally. +- That run's coverage failure is an actual `TestExecuteIteration1` timeout, + not insufficient coverage. Its internal snapshot lookup uses LIMIT 1 with + varlen payload and failed with `service LOCAL not found`; the snapshot + polling helper hid that error and kept retrying. Byte-bounded local + admission removes the unnecessary spill dependency. The unchanged test + passes in 3.862 seconds, five repetitions in 19.031 seconds, and with + `-short -tags matrixone_test -covermode=set -coverpkg=./pkg/...` in 3.942 + seconds. The full engine/test package also passes with that cross-package + instrumentation in 130.732 seconds. The downstream Coverage merge job failed only because its UT + producer failed. Remote CI for the eventual new commit remains required. + +### Remote write-back correction found during large-scale acceptance + +The three-CN 100M-row/5M-limit acceptance run exposed wrong result identity, +despite completing in about 150 seconds. The decoded remote Top was wrapped +by `appendWriteBackOperator` in an ordinary merge edge. That edge discarded +the ordered producer contract before runtime DOP expansion, allowing worker +batches to interleave on the wire. The coordinator cannot repair this while +performing a streaming merge. + +The correction must preserve the ordered, single-producer edge when wrapping +an ordered Top. Runtime DOP expansion then creates the existing local ordered +gather before the write-back merge forwards its single stream. No new protocol +or ordering algorithm is needed. Acceptance requires a decoded-scope regression +including write-back and DOP expansion, and a fresh exact-result SQL rerun; +the timings of the incorrect runs are not performance acceptance evidence. + +After preserving the write-back edge, final production binary SHA-256 +`c593f182705aef2aa3d0609c600870584068bae4f0d6f5b5a02a5e069185cfe8` +passes the issue-shaped 100M/K=5M INSERT twice: **156.078 s and 145.954 s**. +Both outputs have count/distinct count 5,000,000, min/max 0/4,999,999, +sum 12,499,997,500,000 and payload byte count 640,000,000. Main at `e60b3eb3` +(binary `8fde3aa5c4c57b568b10926140f80d3f78fac5658069d6e4e633a756542c5386`) +did not complete in the 300-second observation window; its specific query +was canceled at about 324 seconds and returned after 328.25 seconds. The +target remained empty after cancellation. This is a censored baseline, not +a measured completion time or a reproduction of the historical 900s timeout. + +Both builds used the same retained data, configuration and host 55 NVMe: +three CN services plus TN/logservice in one launch process, GOMAXPROCS=8, +8-worker scan scopes, bigint key and 128-byte varchar payload. This is not +the original three-host TKE deployment or a historical-good build comparison. +Run order was main then fixed then fixed; no cache-drop operation was used. +The source fingerprint is 100,000,000 rows, keys 0..99,999,999, sum +4,999,999,950,000,000. Final service process RSS high-water was 9,473,892 KiB; +this includes all co-located services and is not query-accounted memory. +Whole-query CPU/I/O counters and a full 1/2/3-CN scale matrix were not collected. + +On the same final instance, fixed-width LIMIT 10 returns exact 0..9 twice +(0.504/0.420 s). The extended forced multi-CN DML test also passes twice in +one process, including a 100K-row/1K-limit exact key/payload regression. +Test-harness startup encountered an existing shared `/tmp` lock and was +rerun with a task-owned TMPDIR; that failed launch is not a test pass. + +The final LIMIT BVT passes twice (35/35 statements each) against the same +final three-CN instance. Both teardown checks find zero `t1` tables and retain +the 100M performance source. Tester startup initially deleted the generated +performance fixture: its configuration loader ignores the supplied Java +property and reads `run.yml` from the working directory. The fixture was +recreated, the actual working-directory configuration now protects it, and +both final protected runs verify it survives. Generated expected results were +not used. The measured INSERT logs predate this reconstruction and are retained +separately; reconstructing test data is not counted as another performance run. + +## References + +- https://github.com/matrixorigin/matrixone/issues/28285 +- https://github.com/matrixorigin/matrixone/issues/27968 +- https://github.com/matrixorigin/matrixone/pull/27999 +- [Memory accounting implementation](query_memory_control_and_attribution_impl.md) +- [PostgreSQL 17 Gather Merge](https://www.postgresql.org/docs/17/how-parallel-query-works.html) diff --git a/pkg/defines/const.go b/pkg/defines/const.go index dd2c26e7427b6..35eba8903e9ce 100644 --- a/pkg/defines/const.go +++ b/pkg/defines/const.go @@ -88,7 +88,8 @@ const ( MORPCVersion50 int64 = 50 // ordered ODKU evaluation and logical affected-row metadata MORPCVersion51 int64 = 51 // per-action ODKU validation and statement-local target arbitration MORPCVersion52 int64 = 52 // MySQL binary JSON subtype tags - MORPCLatestVersion = MORPCVersion52 + MORPCVersion53 int64 = 53 // ordered-stream distributed Top-N merge + MORPCLatestVersion = MORPCVersion53 ) // DefaultLockWaitTimeoutSeconds is shared by the frontend default and by diff --git a/pkg/pb/pipeline/pipeline.pb.go b/pkg/pb/pipeline/pipeline.pb.go index 7790807d94029..769befbaed6b2 100644 --- a/pkg/pb/pipeline/pipeline.pb.go +++ b/pkg/pb/pipeline/pipeline.pb.go @@ -4841,11 +4841,17 @@ type Instruction struct { // PROJECTION only: vector-level expansion metadata for planner-generated // ROLLUP/CUBE grouping sets. Older CNs ignore these fields, so planning is // gated by MORPCVersion49 until every participant understands them. - ProjectionGroupingFlags []bool `protobuf:"varint,60,rep,packed,name=projection_grouping_flags,json=projectionGroupingFlags,proto3" json:"projection_grouping_flags,omitempty"` - ProjectionGroupingSetCount int32 `protobuf:"varint,61,opt,name=projection_grouping_set_count,json=projectionGroupingSetCount,proto3" json:"projection_grouping_set_count,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + ProjectionGroupingFlags []bool `protobuf:"varint,60,rep,packed,name=projection_grouping_flags,json=projectionGroupingFlags,proto3" json:"projection_grouping_flags,omitempty"` + ProjectionGroupingSetCount int32 `protobuf:"varint,61,opt,name=projection_grouping_set_count,json=projectionGroupingSetCount,proto3" json:"projection_grouping_set_count,omitempty"` + // TOP only: every runtime worker must be consolidated into one stream that + // remains ordered across batch boundaries before leaving its CN. + TopOrderedOutput bool `protobuf:"varint,62,opt,name=top_ordered_output,json=topOrderedOutput,proto3" json:"top_ordered_output,omitempty"` + // MERGE_TOP only: consume one independently ordered stream from every + // receiver. This is serialized for per-CN ordered gathers sent by RemoteRun. + MergeTopOrderedStreams bool `protobuf:"varint,63,opt,name=merge_top_ordered_streams,json=mergeTopOrderedStreams,proto3" json:"merge_top_ordered_streams,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Instruction) Reset() { *m = Instruction{} } @@ -5251,6 +5257,20 @@ func (m *Instruction) GetProjectionGroupingSetCount() int32 { return 0 } +func (m *Instruction) GetTopOrderedOutput() bool { + if m != nil { + return m.TopOrderedOutput + } + return false +} + +func (m *Instruction) GetMergeTopOrderedStreams() bool { + if m != nil { + return m.MergeTopOrderedStreams + } + return false +} + type AnalysisList struct { List []*plan.AnalyzeInfo `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -6126,23 +6146,26 @@ func (m *SessionLoggerInfo) GetLogLevel() SessionLoggerInfo_LogLevel { } type Pipeline struct { - PipelineType Pipeline_PipelineType `protobuf:"varint,1,opt,name=pipeline_type,json=pipelineType,proto3,enum=pipeline.Pipeline_PipelineType" json:"pipeline_type,omitempty"` - PipelineId int32 `protobuf:"varint,2,opt,name=pipeline_id,json=pipelineId,proto3" json:"pipeline_id,omitempty"` - Qry *plan.Plan `protobuf:"bytes,3,opt,name=qry,proto3" json:"qry,omitempty"` - DataSource *Source `protobuf:"bytes,4,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"` - Children []*Pipeline `protobuf:"bytes,5,rep,name=children,proto3" json:"children,omitempty"` - InstructionList []*Instruction `protobuf:"bytes,6,rep,name=instruction_list,json=instructionList,proto3" json:"instruction_list,omitempty"` - IsEnd bool `protobuf:"varint,7,opt,name=is_end,json=isEnd,proto3" json:"is_end,omitempty"` - IsLoad bool `protobuf:"varint,8,opt,name=is_load,json=isLoad,proto3" json:"is_load,omitempty"` - Node *NodeInfo `protobuf:"bytes,9,opt,name=node,proto3" json:"node,omitempty"` - PushDownInfo int32 `protobuf:"varint,10,opt,name=push_down_info,json=pushDownInfo,proto3" json:"push_down_info,omitempty"` - ChildrenCount int32 `protobuf:"varint,11,opt,name=children_count,json=childrenCount,proto3" json:"children_count,omitempty"` - ChannelBufferSize []int32 `protobuf:"varint,12,rep,packed,name=channel_buffer_size,json=channelBufferSize,proto3" json:"channel_buffer_size,omitempty"` - NilBatchCnt []int32 `protobuf:"varint,13,rep,packed,name=nil_batch_cnt,json=nilBatchCnt,proto3" json:"nil_batch_cnt,omitempty"` - UuidsToRegIdx []*UuidToRegIdx `protobuf:"bytes,14,rep,name=uuids_to_reg_idx,json=uuidsToRegIdx,proto3" json:"uuids_to_reg_idx,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + PipelineType Pipeline_PipelineType `protobuf:"varint,1,opt,name=pipeline_type,json=pipelineType,proto3,enum=pipeline.Pipeline_PipelineType" json:"pipeline_type,omitempty"` + PipelineId int32 `protobuf:"varint,2,opt,name=pipeline_id,json=pipelineId,proto3" json:"pipeline_id,omitempty"` + Qry *plan.Plan `protobuf:"bytes,3,opt,name=qry,proto3" json:"qry,omitempty"` + DataSource *Source `protobuf:"bytes,4,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"` + Children []*Pipeline `protobuf:"bytes,5,rep,name=children,proto3" json:"children,omitempty"` + InstructionList []*Instruction `protobuf:"bytes,6,rep,name=instruction_list,json=instructionList,proto3" json:"instruction_list,omitempty"` + IsEnd bool `protobuf:"varint,7,opt,name=is_end,json=isEnd,proto3" json:"is_end,omitempty"` + IsLoad bool `protobuf:"varint,8,opt,name=is_load,json=isLoad,proto3" json:"is_load,omitempty"` + Node *NodeInfo `protobuf:"bytes,9,opt,name=node,proto3" json:"node,omitempty"` + PushDownInfo int32 `protobuf:"varint,10,opt,name=push_down_info,json=pushDownInfo,proto3" json:"push_down_info,omitempty"` + ChildrenCount int32 `protobuf:"varint,11,opt,name=children_count,json=childrenCount,proto3" json:"children_count,omitempty"` + ChannelBufferSize []int32 `protobuf:"varint,12,rep,packed,name=channel_buffer_size,json=channelBufferSize,proto3" json:"channel_buffer_size,omitempty"` + NilBatchCnt []int32 `protobuf:"varint,13,rep,packed,name=nil_batch_cnt,json=nilBatchCnt,proto3" json:"nil_batch_cnt,omitempty"` + UuidsToRegIdx []*UuidToRegIdx `protobuf:"bytes,14,rep,name=uuids_to_reg_idx,json=uuidsToRegIdx,proto3" json:"uuids_to_reg_idx,omitempty"` + // Per receiver, preserve the single-producer ordered-stream contract across + // RemoteRun. Empty means the legacy unordered default. + OrderedStream []bool `protobuf:"varint,15,rep,packed,name=ordered_stream,json=orderedStream,proto3" json:"ordered_stream,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Pipeline) Reset() { *m = Pipeline{} } @@ -6275,6 +6298,13 @@ func (m *Pipeline) GetUuidsToRegIdx() []*UuidToRegIdx { return nil } +func (m *Pipeline) GetOrderedStream() []bool { + if m != nil { + return m.OrderedStream + } + return nil +} + type WrapNode struct { NodeAddr string `protobuf:"bytes,1,opt,name=node_addr,json=nodeAddr,proto3" json:"node_addr,omitempty"` Uuid []byte `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"` @@ -6617,551 +6647,555 @@ func init() { func init() { proto.RegisterFile("pipeline.proto", fileDescriptor_7ac67a7adf3df9c7) } var fileDescriptor_7ac67a7adf3df9c7 = []byte{ - // 8700 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7c, 0x4b, 0x6c, 0x1d, 0x47, - 0xb6, 0x98, 0x2f, 0xc9, 0xfb, 0x3b, 0xf7, 0xcb, 0x22, 0x45, 0x5d, 0x7d, 0x6c, 0xd1, 0xd7, 0x96, - 0x4c, 0xcb, 0x36, 0x25, 0xd3, 0xf6, 0xf8, 0x33, 0xe3, 0xf1, 0xa3, 0x28, 0xc9, 0xe6, 0x58, 0x94, - 0x38, 0x4d, 0xea, 0x19, 0x30, 0x90, 0x34, 0x9a, 0xdd, 0x75, 0x2f, 0xdb, 0xec, 0xdb, 0xdd, 0xea, - 0xea, 0x96, 0x48, 0xad, 0x92, 0x65, 0x82, 0xec, 0xf3, 0x90, 0xd5, 0x04, 0xc9, 0x22, 0x41, 0x96, - 0xc1, 0x64, 0x99, 0xf5, 0x43, 0x90, 0xc5, 0xac, 0xb2, 0x0c, 0x82, 0x99, 0x65, 0x80, 0x24, 0x08, - 0x90, 0x20, 0x48, 0xf0, 0x80, 0xe0, 0x9c, 0x53, 0xd5, 0xdd, 0xf7, 0xf2, 0x4a, 0xb2, 0x9d, 0xe0, - 0x6d, 0xde, 0xdb, 0x75, 0x9f, 0x73, 0xaa, 0xba, 0xba, 0xea, 0x7c, 0xeb, 0x9c, 0x2a, 0xe8, 0xc6, - 0x7e, 0x2c, 0x03, 0x3f, 0x94, 0x9b, 0x71, 0x12, 0xa5, 0x91, 0x68, 0x98, 0xf7, 0xcb, 0x1f, 0x8c, - 0xfd, 0xf4, 0x38, 0x3b, 0xda, 0x74, 0xa3, 0xc9, 0xad, 0x71, 0x34, 0x8e, 0x6e, 0x11, 0xc1, 0x51, - 0x36, 0xa2, 0x37, 0x7a, 0xa1, 0x27, 0x6e, 0x78, 0x19, 0x82, 0xc8, 0x3d, 0x31, 0xcf, 0x71, 0xe0, - 0x84, 0xfa, 0xb9, 0x97, 0xfa, 0x13, 0xa9, 0x52, 0x67, 0x12, 0x6b, 0x40, 0x33, 0x3d, 0xd5, 0xb8, - 0xe1, 0xef, 0x6a, 0x50, 0xdf, 0x93, 0x4a, 0x39, 0x63, 0x29, 0x86, 0xb0, 0xa8, 0x7c, 0x6f, 0x50, - 0x59, 0xaf, 0x6c, 0x74, 0xb7, 0xfa, 0x9b, 0xf9, 0xb0, 0x0e, 0x52, 0x27, 0xcd, 0x94, 0x85, 0x48, - 0xa4, 0x71, 0x27, 0xde, 0x60, 0x61, 0x96, 0x66, 0x4f, 0xa6, 0xc7, 0x91, 0x67, 0x21, 0x52, 0xf4, - 0x61, 0x51, 0x26, 0xc9, 0x60, 0x71, 0xbd, 0xb2, 0xd1, 0xb6, 0xf0, 0x51, 0x08, 0x58, 0xf2, 0x9c, - 0xd4, 0x19, 0x2c, 0x11, 0x88, 0x9e, 0xc5, 0xdb, 0xd0, 0x8d, 0x93, 0xc8, 0xb5, 0xfd, 0x70, 0x14, - 0xd9, 0x84, 0xad, 0x12, 0xb6, 0x8d, 0xd0, 0xdd, 0x70, 0x14, 0xdd, 0x45, 0xaa, 0x01, 0xd4, 0x9d, - 0xd0, 0x09, 0xce, 0x94, 0x1c, 0xd4, 0x08, 0x6d, 0x5e, 0x45, 0x17, 0x16, 0x7c, 0x6f, 0x50, 0x5f, - 0xaf, 0x6c, 0x2c, 0x59, 0x0b, 0xbe, 0x87, 0xdf, 0xc8, 0x32, 0xdf, 0x1b, 0x34, 0xf8, 0x1b, 0xf8, - 0x2c, 0x86, 0xd0, 0x0e, 0xa5, 0xf4, 0x1e, 0x46, 0xa9, 0x25, 0xe3, 0xe0, 0x6c, 0xd0, 0x5c, 0xaf, - 0x6c, 0x34, 0xac, 0x29, 0x98, 0xb8, 0x0c, 0x0d, 0x4f, 0x1e, 0x65, 0xe3, 0x3d, 0x35, 0x1e, 0xc0, - 0x7a, 0x65, 0xa3, 0x69, 0xe5, 0xef, 0xe2, 0x10, 0x2e, 0x26, 0xf2, 0x49, 0x26, 0x55, 0x2a, 0x3d, - 0x3b, 0x95, 0x4e, 0xe2, 0x45, 0xcf, 0x42, 0x7b, 0x12, 0x79, 0x72, 0xd0, 0xa2, 0x19, 0xb8, 0x5a, - 0x9e, 0xa5, 0x44, 0x3a, 0x93, 0x43, 0x4d, 0xb4, 0x17, 0x79, 0xd2, 0xba, 0x90, 0x37, 0x2e, 0x83, - 0x85, 0x05, 0x6b, 0x8e, 0xeb, 0xca, 0xf8, 0x7c, 0xa7, 0xed, 0x1f, 0xd1, 0xe9, 0xaa, 0x69, 0x3b, - 0xd5, 0xe7, 0x57, 0x70, 0xb5, 0x18, 0xe9, 0x91, 0x93, 0xba, 0xc7, 0xb6, 0x9b, 0x48, 0xcf, 0x4f, - 0x6d, 0x37, 0xca, 0xc2, 0x74, 0xd0, 0x59, 0xaf, 0x6c, 0x74, 0xac, 0x4b, 0x39, 0xcd, 0x1d, 0x24, - 0xd9, 0x21, 0x8a, 0x1d, 0x24, 0x78, 0x49, 0x07, 0x47, 0x67, 0xa9, 0x54, 0x83, 0x2e, 0x4d, 0xf4, - 0xdc, 0x0e, 0xee, 0x20, 0x81, 0xf8, 0x12, 0xae, 0xe4, 0x7f, 0x35, 0x67, 0x00, 0x3d, 0x1a, 0xc0, - 0xc0, 0x90, 0x9c, 0xfb, 0xfe, 0x0b, 0x9b, 0xf3, 0xe7, 0xfb, 0xf4, 0xf9, 0x79, 0xcd, 0xf9, 0xeb, - 0xd7, 0xa1, 0xcb, 0xad, 0x14, 0x0e, 0x30, 0x74, 0xe5, 0x60, 0x99, 0x5a, 0x74, 0x08, 0x7a, 0xa0, - 0x81, 0xe2, 0x7d, 0x10, 0x4c, 0xe6, 0xb8, 0x27, 0x05, 0xa9, 0x20, 0xd2, 0x3e, 0x61, 0xb6, 0xdd, - 0x13, 0x43, 0xfd, 0xc5, 0xd2, 0x5f, 0xfc, 0xee, 0xda, 0x6b, 0xc3, 0xc7, 0xd0, 0xdc, 0x89, 0xc2, - 0x50, 0xba, 0x69, 0x94, 0x88, 0x6b, 0xd0, 0x32, 0x8b, 0x63, 0x6b, 0x59, 0xa9, 0x5a, 0x60, 0x40, - 0xbb, 0x9e, 0x78, 0x07, 0x7a, 0xae, 0xa1, 0xb6, 0xfd, 0xd0, 0x93, 0xa7, 0x24, 0x2c, 0x55, 0xab, - 0x9b, 0x83, 0x77, 0x11, 0x3a, 0xfc, 0x2f, 0x8b, 0x50, 0x3f, 0x38, 0xce, 0x46, 0xa3, 0x40, 0x8a, - 0xb7, 0xa1, 0xa3, 0x1f, 0x77, 0xa2, 0x60, 0xd7, 0x3b, 0xd5, 0xfd, 0x4e, 0x03, 0xc5, 0x3a, 0xb4, - 0x34, 0xe0, 0xf0, 0x2c, 0x96, 0xba, 0xdb, 0x32, 0x68, 0xba, 0x9f, 0x3d, 0x3f, 0x24, 0x19, 0x5c, - 0xb4, 0xa6, 0x81, 0x33, 0x54, 0xce, 0x29, 0x89, 0xe5, 0x34, 0x95, 0x43, 0x5f, 0xdb, 0x0e, 0xfc, - 0xa7, 0xd2, 0x92, 0xe3, 0x9d, 0x30, 0x25, 0xe1, 0xac, 0x5a, 0x65, 0x90, 0xd8, 0x82, 0x0b, 0x8a, - 0x9b, 0xd8, 0x89, 0x13, 0x8e, 0xa5, 0xb2, 0x33, 0x3f, 0x4c, 0x7f, 0xf1, 0xf1, 0xa0, 0xb6, 0xbe, - 0xb8, 0xb1, 0x64, 0xad, 0x68, 0xa4, 0x45, 0xb8, 0xc7, 0x84, 0x12, 0xb7, 0x61, 0x75, 0xa6, 0x0d, - 0x37, 0xa9, 0xaf, 0x2f, 0x6e, 0x2c, 0x5a, 0x62, 0xaa, 0xc9, 0x2e, 0xb5, 0xb8, 0x07, 0xcb, 0x49, - 0x16, 0xa2, 0x0a, 0xbb, 0xef, 0x07, 0xa9, 0x4c, 0x0e, 0x62, 0xe9, 0x92, 0x90, 0xb7, 0xb6, 0x2e, - 0x6e, 0x92, 0x96, 0xb3, 0x66, 0xd1, 0xd6, 0xf9, 0x16, 0xe2, 0xfd, 0x7c, 0xf2, 0xee, 0x9d, 0xc6, - 0x09, 0x69, 0x82, 0xd6, 0x16, 0x70, 0x07, 0x08, 0xb1, 0xca, 0x68, 0x71, 0x13, 0x96, 0xbd, 0xc4, - 0xf1, 0x43, 0xdb, 0x09, 0x02, 0xfb, 0x28, 0x73, 0x4f, 0x64, 0xaa, 0x48, 0x3b, 0x34, 0xac, 0x1e, - 0x21, 0xb6, 0x83, 0xe0, 0x0e, 0x83, 0xc5, 0x0d, 0xe8, 0xa9, 0x34, 0xf1, 0xc3, 0xb1, 0x7d, 0xec, - 0xa8, 0x63, 0xfb, 0x44, 0x9e, 0x91, 0x72, 0x68, 0x58, 0x1d, 0x06, 0x7f, 0xe3, 0xa8, 0xe3, 0x6f, - 0xe5, 0xd9, 0xf0, 0x7f, 0x2d, 0x40, 0xe3, 0xae, 0xaf, 0x62, 0xe4, 0x32, 0x71, 0x11, 0xea, 0xa3, - 0x2c, 0x74, 0x0b, 0x1e, 0xaa, 0xe1, 0xeb, 0xae, 0x27, 0x7e, 0x05, 0xbd, 0x20, 0x72, 0x9d, 0xc0, - 0xce, 0xd9, 0x65, 0xb0, 0xb0, 0xbe, 0xb8, 0xd1, 0xda, 0x5a, 0x29, 0xb4, 0x42, 0xce, 0x8e, 0x56, - 0x97, 0x68, 0x0b, 0xf6, 0xfc, 0x12, 0xfa, 0x89, 0x9c, 0x44, 0xa9, 0x2c, 0x35, 0x5f, 0xa4, 0xe6, - 0xa2, 0x68, 0xfe, 0x5d, 0xe2, 0xc4, 0x0f, 0x51, 0x95, 0xf4, 0x98, 0xb6, 0x68, 0xfe, 0x61, 0x69, - 0x45, 0xe5, 0xd8, 0xf6, 0xbd, 0x53, 0x9b, 0x3e, 0x30, 0x58, 0x5a, 0x5f, 0xdc, 0xa8, 0x16, 0xcb, - 0x23, 0xc7, 0xbb, 0xde, 0xe9, 0x03, 0xc4, 0x88, 0x8f, 0x60, 0x6d, 0xb6, 0x09, 0xf7, 0x3a, 0xa8, - 0x52, 0x9b, 0x95, 0xa9, 0x36, 0x16, 0xa1, 0xc4, 0x9b, 0xd0, 0x36, 0x8d, 0x52, 0x64, 0xe5, 0x1a, - 0x33, 0x97, 0x2a, 0xb1, 0xf2, 0x45, 0xa8, 0xfb, 0xca, 0x56, 0x7e, 0x78, 0x42, 0x3a, 0xbe, 0x61, - 0xd5, 0x7c, 0x75, 0xe0, 0x87, 0x27, 0xe2, 0x12, 0x34, 0x12, 0xe9, 0x32, 0xa6, 0x41, 0x98, 0x7a, - 0x22, 0x5d, 0x42, 0x5d, 0x04, 0x7c, 0xb4, 0xdd, 0x54, 0x6a, 0x4d, 0x5f, 0x4b, 0xa4, 0xbb, 0x93, - 0xca, 0xa1, 0x82, 0xea, 0x9e, 0x4c, 0xc6, 0x12, 0x95, 0x3d, 0x36, 0x3c, 0x70, 0x9d, 0x90, 0xe6, - 0xbd, 0x61, 0xe5, 0xef, 0x68, 0x6a, 0x62, 0x27, 0x49, 0x7d, 0x27, 0x20, 0xd1, 0x6a, 0x58, 0xe6, - 0x55, 0x5c, 0x81, 0xa6, 0x4a, 0x9d, 0x24, 0xc5, 0xbf, 0x23, 0x91, 0xaa, 0x5a, 0x0d, 0x02, 0xa0, - 0x54, 0x5e, 0x84, 0xba, 0x0c, 0x3d, 0x42, 0x2d, 0xf1, 0x4a, 0xca, 0xd0, 0xdb, 0xf5, 0x4e, 0x87, - 0xff, 0xba, 0x02, 0x9d, 0xbd, 0x2c, 0x48, 0xfd, 0xed, 0x64, 0x9c, 0xc9, 0x49, 0x98, 0xa2, 0x89, - 0xba, 0xeb, 0xab, 0x54, 0x7f, 0x99, 0x9e, 0xc5, 0x06, 0x34, 0xbf, 0x4e, 0xa2, 0x2c, 0x26, 0xae, - 0xe4, 0x95, 0x2e, 0x73, 0x65, 0x81, 0x44, 0x0e, 0x7e, 0x94, 0x78, 0x32, 0xb9, 0x73, 0x46, 0xb4, - 0x8b, 0xe7, 0x68, 0xcb, 0x68, 0x71, 0x15, 0x9a, 0x07, 0x32, 0x76, 0x12, 0x07, 0x59, 0x60, 0x89, - 0xec, 0x5a, 0x01, 0xc0, 0x7f, 0x25, 0xe2, 0x5d, 0x4f, 0x0b, 0xb6, 0x79, 0x1d, 0xfe, 0xb3, 0x0a, - 0x34, 0xb7, 0xc7, 0xe3, 0x44, 0x8e, 0x9d, 0x94, 0x8c, 0x6c, 0x14, 0xd3, 0x78, 0x17, 0xad, 0x85, - 0x28, 0x26, 0x43, 0x8e, 0x7f, 0xc0, 0x13, 0x44, 0xcf, 0xe2, 0x0d, 0x58, 0x92, 0xf3, 0x07, 0x44, - 0x70, 0xb1, 0x06, 0x35, 0x37, 0x0a, 0x47, 0xfe, 0x58, 0x9b, 0x7f, 0xfd, 0x26, 0xbe, 0x80, 0x16, - 0x3f, 0x31, 0x0f, 0x54, 0xc9, 0xf6, 0x5d, 0xe2, 0xe6, 0xf9, 0x08, 0x76, 0x88, 0x02, 0x39, 0xc2, - 0x02, 0x37, 0x7f, 0x1e, 0xfe, 0x69, 0x09, 0xaa, 0x34, 0x33, 0xb8, 0x36, 0x68, 0xce, 0x6d, 0xf9, - 0xd4, 0x09, 0xcc, 0x92, 0x22, 0xe0, 0xde, 0x53, 0x27, 0x10, 0xeb, 0x50, 0xc5, 0x21, 0xa8, 0x39, - 0x13, 0xcb, 0x08, 0x71, 0x03, 0xaa, 0xf8, 0x75, 0x35, 0x3d, 0x7a, 0xfc, 0xc6, 0x9d, 0xa5, 0xbf, - 0xfc, 0x8f, 0xd7, 0x5e, 0xb3, 0x18, 0x2d, 0xde, 0x81, 0x25, 0x67, 0x3c, 0x56, 0x24, 0x08, 0x53, - 0xb2, 0x98, 0x8f, 0xd4, 0x22, 0x02, 0xf1, 0x09, 0x34, 0x79, 0xd1, 0x91, 0xba, 0x4a, 0xd4, 0x17, - 0x4b, 0x6e, 0x52, 0x99, 0x1f, 0xac, 0x82, 0x12, 0x97, 0xcb, 0x57, 0x5a, 0x03, 0x91, 0x38, 0x34, - 0xac, 0x02, 0x80, 0x7e, 0x4c, 0x9c, 0xc8, 0xed, 0x20, 0x88, 0xdc, 0x03, 0xff, 0xb9, 0xd4, 0x5e, - 0xcf, 0x14, 0x4c, 0xdc, 0x80, 0xee, 0x3e, 0xf3, 0xab, 0x25, 0x55, 0x16, 0xa4, 0x4a, 0x7b, 0x42, - 0x33, 0x50, 0xb1, 0x09, 0x62, 0x0a, 0x72, 0x48, 0xbf, 0xdf, 0x5c, 0x5f, 0xdc, 0xe8, 0x58, 0x73, - 0x30, 0xe2, 0x2d, 0xe8, 0x8c, 0x71, 0xa6, 0x51, 0xc1, 0x8d, 0x02, 0x07, 0x9d, 0xa4, 0x45, 0x74, - 0xa2, 0x0c, 0xf0, 0x7e, 0xe0, 0x8c, 0x49, 0x42, 0x62, 0x3f, 0x08, 0xec, 0x89, 0x9c, 0x90, 0xf6, - 0x5b, 0xb4, 0x1a, 0x04, 0xd8, 0x93, 0x13, 0xf1, 0x2e, 0x2c, 0x13, 0xb1, 0x7d, 0x74, 0x56, 0xa8, - 0xc8, 0x36, 0x69, 0x87, 0x2e, 0x21, 0xee, 0x9c, 0x69, 0x1d, 0x29, 0xde, 0x85, 0xbe, 0x77, 0x16, - 0x3a, 0x13, 0xdf, 0xb5, 0x4d, 0xff, 0xe4, 0xba, 0xa0, 0xda, 0x65, 0xf8, 0xd7, 0x1a, 0x8c, 0x8a, - 0x47, 0x4e, 0xe2, 0xf4, 0x2c, 0x27, 0xb4, 0x95, 0x44, 0x09, 0x45, 0x57, 0x05, 0x6d, 0xc9, 0x0a, - 0x61, 0x0d, 0xf9, 0x81, 0x4c, 0x77, 0x3d, 0x85, 0xf6, 0xff, 0x7c, 0x23, 0xf2, 0x4d, 0x1a, 0x56, - 0x7f, 0xb6, 0xc1, 0xf0, 0x5f, 0x2d, 0x41, 0x6d, 0x37, 0x54, 0x32, 0x49, 0x51, 0x71, 0x38, 0xa3, - 0x91, 0x74, 0x53, 0xc9, 0x0a, 0x7b, 0xc9, 0xca, 0xdf, 0x71, 0xed, 0x0e, 0xa3, 0xef, 0x12, 0x3f, - 0x95, 0x07, 0x1f, 0x69, 0xc9, 0x28, 0x00, 0x68, 0x4a, 0x1c, 0xcf, 0xb3, 0x0d, 0xb5, 0x9d, 0x44, - 0xcf, 0x14, 0x29, 0x91, 0x86, 0xd5, 0x73, 0x3c, 0x6f, 0x5b, 0xc3, 0xad, 0xe8, 0x99, 0x12, 0x6f, - 0xc2, 0x62, 0x22, 0x47, 0x24, 0x27, 0xad, 0xad, 0x1e, 0xf3, 0xe2, 0xa3, 0xa3, 0x1f, 0xa4, 0x9b, - 0x5a, 0x72, 0x64, 0x21, 0x4e, 0xac, 0x42, 0xd5, 0x49, 0xd3, 0x84, 0x79, 0xab, 0x69, 0xf1, 0x8b, - 0xd8, 0x84, 0x15, 0x52, 0x56, 0xa9, 0x1f, 0x85, 0x76, 0xea, 0x1c, 0x05, 0x92, 0x66, 0x82, 0x0d, - 0xf1, 0x72, 0x8e, 0x3a, 0x44, 0x0c, 0xce, 0xc3, 0x16, 0x5c, 0x98, 0xa5, 0x0f, 0x9d, 0x89, 0x54, - 0x64, 0x87, 0x9b, 0xd6, 0xca, 0x74, 0x8b, 0x87, 0x88, 0x42, 0x46, 0x28, 0xda, 0xa0, 0xba, 0x6b, - 0x90, 0xe6, 0x68, 0xe7, 0x40, 0xd4, 0x86, 0x17, 0xa0, 0xe6, 0x2b, 0x5b, 0x86, 0x9e, 0xd6, 0xc0, - 0x55, 0x5f, 0xdd, 0x0b, 0x3d, 0xf1, 0x1e, 0x34, 0xf9, 0x2b, 0x9e, 0x1c, 0x91, 0x1d, 0x6d, 0x6d, - 0x75, 0xb5, 0xa8, 0x21, 0xf8, 0xae, 0x1c, 0x59, 0x8d, 0x54, 0x3f, 0xa1, 0x8f, 0x95, 0x46, 0xb6, - 0x3c, 0x4d, 0x65, 0x12, 0x3a, 0x81, 0x36, 0xa6, 0x90, 0x46, 0xf7, 0x34, 0x44, 0x7c, 0x02, 0x17, - 0x0d, 0xd6, 0x56, 0xe9, 0x24, 0xb5, 0xb3, 0xd0, 0x3f, 0xb5, 0x43, 0x27, 0x8c, 0xc8, 0x83, 0x5e, - 0xb4, 0x56, 0x0d, 0xfa, 0x20, 0x9d, 0xa4, 0x8f, 0x43, 0xff, 0xf4, 0xa1, 0x13, 0x46, 0x62, 0x03, - 0xfa, 0x79, 0xb3, 0xf4, 0x39, 0xfd, 0x30, 0x31, 0x57, 0xd3, 0xea, 0x1a, 0xf8, 0xe1, 0x73, 0xfc, - 0x57, 0xe2, 0xad, 0x12, 0x65, 0x34, 0x1a, 0x21, 0x6f, 0x29, 0xe9, 0x92, 0x1b, 0x5c, 0xb5, 0x56, - 0x0a, 0xfa, 0x47, 0x84, 0x3b, 0x90, 0xee, 0xf0, 0xf7, 0x15, 0x68, 0x91, 0x40, 0x3f, 0x8e, 0x3d, - 0xd4, 0x9d, 0x6f, 0x41, 0x67, 0x7a, 0xd1, 0x99, 0x6f, 0xda, 0x4e, 0x79, 0xc5, 0xd7, 0xa0, 0xb6, - 0xed, 0xe2, 0xe4, 0x11, 0xe3, 0x74, 0x2c, 0xfd, 0x26, 0x3e, 0x85, 0x5e, 0x46, 0xdd, 0xd8, 0x6e, - 0x7a, 0x6a, 0x07, 0xa8, 0x73, 0x59, 0x43, 0x69, 0xae, 0xe0, 0x6f, 0xec, 0xa4, 0xa7, 0x56, 0x27, - 0x33, 0x8f, 0x0f, 0x50, 0x1b, 0xdf, 0x86, 0xd5, 0x44, 0x22, 0xc7, 0xd8, 0xcf, 0x65, 0x12, 0xd9, - 0xa9, 0x9c, 0xc4, 0x51, 0x42, 0x16, 0x1c, 0x67, 0x51, 0x30, 0xee, 0x7b, 0x99, 0x44, 0x87, 0x1a, - 0x33, 0x7c, 0x1d, 0xaa, 0xdb, 0x49, 0xe2, 0x9c, 0x11, 0x6b, 0xe1, 0xc3, 0xa0, 0x42, 0xb2, 0xc9, - 0x2f, 0x43, 0x17, 0x16, 0xf7, 0x9c, 0x58, 0x5c, 0x87, 0x85, 0x49, 0x4c, 0x98, 0xd6, 0xd6, 0x85, - 0x92, 0x42, 0x73, 0xe2, 0xcd, 0xbd, 0xf8, 0x5e, 0x98, 0x26, 0x67, 0xd6, 0xc2, 0x24, 0xbe, 0xfc, - 0x09, 0xd4, 0xf5, 0x2b, 0x86, 0x81, 0x28, 0xe8, 0x15, 0x9a, 0x61, 0x7c, 0xc4, 0x0f, 0x3c, 0x75, - 0x82, 0xcc, 0xb8, 0xae, 0xfc, 0xf2, 0xc5, 0xc2, 0x67, 0x95, 0xe1, 0xff, 0x58, 0x82, 0xc6, 0x5d, - 0x19, 0x48, 0xfa, 0xf7, 0x21, 0xb4, 0xcb, 0x52, 0x61, 0xe6, 0x6d, 0x4a, 0x52, 0x86, 0xd0, 0x66, - 0x5f, 0x82, 0x5a, 0x49, 0x2d, 0x76, 0x53, 0x30, 0x34, 0x72, 0xbb, 0xec, 0xa4, 0x91, 0xbc, 0x75, - 0x2c, 0xf3, 0x8a, 0x98, 0x87, 0x1a, 0xb3, 0xc4, 0x18, 0xfd, 0x2a, 0xae, 0x02, 0x24, 0xd1, 0x33, - 0xdb, 0x67, 0x83, 0xce, 0xb6, 0xb1, 0x91, 0x44, 0xcf, 0x76, 0xd1, 0xa4, 0xff, 0xb5, 0x88, 0xd9, - 0xa7, 0x30, 0x28, 0x89, 0x19, 0x86, 0x0a, 0xb6, 0x1f, 0x72, 0x48, 0xa4, 0x25, 0xae, 0xe8, 0x93, - 0x22, 0x89, 0xdd, 0x90, 0xa2, 0x21, 0xa3, 0x3c, 0x9a, 0x2f, 0x51, 0x1e, 0x73, 0x75, 0x11, 0xcc, - 0xd7, 0x45, 0x77, 0x00, 0x0e, 0xe4, 0x78, 0x22, 0xc3, 0x74, 0xcf, 0x89, 0x07, 0x2d, 0x5a, 0xf8, - 0x61, 0xb1, 0xf0, 0x66, 0xb5, 0x36, 0x0b, 0x22, 0xe6, 0x82, 0x52, 0x2b, 0xf4, 0xf3, 0x5c, 0x27, - 0xb4, 0xd3, 0x24, 0x0b, 0x5d, 0x27, 0xe5, 0xf8, 0xb6, 0x61, 0xb5, 0x5c, 0x27, 0x3c, 0xd4, 0xa0, - 0x92, 0xc2, 0xe8, 0x94, 0x15, 0xc6, 0x0d, 0xe8, 0xc5, 0x89, 0x3f, 0x71, 0x92, 0x33, 0xb4, 0x16, - 0xb4, 0x18, 0x2c, 0x7a, 0x1d, 0x0d, 0xfe, 0x56, 0x9e, 0xed, 0x7a, 0xa7, 0x97, 0xbf, 0x84, 0xde, - 0xcc, 0x00, 0x7e, 0x12, 0xdf, 0xfd, 0xa3, 0x2a, 0x34, 0xf7, 0x13, 0xa9, 0x95, 0xfc, 0x35, 0x68, - 0x29, 0xf7, 0x58, 0x4e, 0x1c, 0xd6, 0x0d, 0xdc, 0x03, 0x30, 0x88, 0xf4, 0xc2, 0x94, 0x1a, 0x5b, - 0x78, 0x85, 0x1a, 0xeb, 0xc3, 0x22, 0xfb, 0x8b, 0x28, 0x4c, 0xf8, 0x58, 0xe8, 0xee, 0xa5, 0xb2, - 0xee, 0x5e, 0x87, 0xf6, 0xb1, 0xa3, 0x6c, 0x27, 0x4b, 0x23, 0xdb, 0x8d, 0x02, 0x62, 0xba, 0x86, - 0x05, 0xc7, 0x8e, 0xda, 0xce, 0xd2, 0x68, 0x27, 0x0a, 0xc4, 0xeb, 0x00, 0x6e, 0x14, 0x68, 0x35, - 0xa4, 0x9d, 0xe5, 0xa6, 0x1b, 0x05, 0xac, 0x7b, 0x90, 0x2b, 0xa5, 0x4a, 0xfd, 0x89, 0xa3, 0x97, - 0x54, 0x47, 0xdc, 0x75, 0x52, 0x85, 0xcb, 0x39, 0xca, 0x8a, 0x9e, 0x71, 0xa8, 0x7d, 0x1b, 0xba, - 0x6e, 0x34, 0x89, 0xed, 0x18, 0x67, 0x96, 0x5c, 0xb7, 0xc6, 0xb9, 0x68, 0xa8, 0x8d, 0x14, 0xfb, - 0x27, 0x92, 0x9d, 0xc9, 0x2d, 0xe8, 0xb9, 0x41, 0xa6, 0x52, 0x99, 0xa0, 0x0d, 0x97, 0xf3, 0x03, - 0xa8, 0x8e, 0x26, 0xd1, 0x0e, 0xe8, 0x10, 0x3a, 0xbe, 0xb2, 0xa3, 0xc0, 0xb3, 0x59, 0x41, 0x69, - 0x3e, 0x6b, 0xf9, 0xea, 0x51, 0xe0, 0x69, 0x15, 0xc9, 0x34, 0xa1, 0x7c, 0x66, 0x68, 0x5a, 0x86, - 0xe6, 0xa1, 0x7c, 0xa6, 0x69, 0x5e, 0xa4, 0xd0, 0xda, 0x2f, 0x52, 0x68, 0x38, 0x1f, 0x38, 0xa1, - 0xa9, 0x93, 0x8c, 0x49, 0x6b, 0x07, 0x1c, 0x07, 0x31, 0x7f, 0x2d, 0x1f, 0x3b, 0xea, 0x90, 0x30, - 0x07, 0x1a, 0x81, 0x51, 0x8f, 0xa6, 0xc5, 0xc9, 0x0b, 0xb3, 0xc9, 0x91, 0x4c, 0x68, 0x25, 0x98, - 0xe3, 0x04, 0x23, 0xad, 0xe8, 0xd9, 0x43, 0x42, 0xe1, 0x8a, 0xdc, 0x84, 0x65, 0xdd, 0xc4, 0x71, - 0x53, 0xff, 0xa9, 0x24, 0xf2, 0x1e, 0x91, 0xf7, 0x18, 0xb1, 0x4d, 0x70, 0xa4, 0x7d, 0x37, 0xa7, - 0xd5, 0x9a, 0x05, 0x69, 0xfb, 0xbc, 0x27, 0x90, 0x77, 0xbd, 0xeb, 0xed, 0x44, 0xc1, 0xf0, 0x3f, - 0x2c, 0x40, 0x7d, 0x3f, 0x52, 0xe9, 0xdd, 0x49, 0x60, 0xc4, 0xb9, 0xf2, 0x53, 0xc5, 0x79, 0x61, - 0xbe, 0x38, 0xcf, 0x11, 0xa8, 0xc5, 0x39, 0x02, 0x85, 0x46, 0xb2, 0x4c, 0x47, 0x82, 0xc0, 0xe1, - 0x43, 0xb7, 0x20, 0x24, 0x61, 0xb8, 0x82, 0x2e, 0xab, 0xed, 0xb1, 0xfe, 0x65, 0xa6, 0x6d, 0xf8, - 0x4a, 0xeb, 0x5e, 0x46, 0xfa, 0x24, 0x57, 0xda, 0x9f, 0x6d, 0xf8, 0x4a, 0xcb, 0xd9, 0xe7, 0x70, - 0x29, 0x6f, 0x69, 0x3f, 0xf3, 0xd3, 0xe3, 0x28, 0x4b, 0xed, 0x11, 0xc5, 0xea, 0x4a, 0x47, 0x7b, - 0x6b, 0xa6, 0xa7, 0xef, 0x18, 0xcd, 0x91, 0x3c, 0xb9, 0xd7, 0xa3, 0x2c, 0x08, 0xec, 0x54, 0x9e, - 0xa6, 0x9a, 0x6d, 0x07, 0x3c, 0x37, 0x7a, 0xde, 0xee, 0x67, 0x41, 0x70, 0x28, 0x4f, 0x53, 0x34, - 0x8d, 0x8d, 0x91, 0x7e, 0x19, 0xfe, 0xe3, 0x25, 0x80, 0x07, 0x91, 0x7b, 0xc2, 0x2b, 0x8f, 0x31, - 0xa4, 0xd1, 0xde, 0xda, 0xba, 0xd4, 0x53, 0xd6, 0xd9, 0x62, 0x0b, 0xd6, 0xcc, 0xff, 0xa3, 0xcc, - 0x61, 0x3c, 0xcb, 0xea, 0x57, 0x2b, 0x0f, 0xa1, 0xb1, 0xbc, 0x27, 0x43, 0xba, 0x57, 0x7c, 0x56, - 0xcc, 0x2d, 0xb6, 0x49, 0xcf, 0x62, 0x9a, 0xdb, 0x79, 0xe1, 0x44, 0xa7, 0x68, 0x7e, 0x78, 0x16, - 0x8b, 0xdb, 0x70, 0x21, 0x91, 0xa3, 0x44, 0xaa, 0x63, 0x3b, 0x55, 0xe5, 0x8f, 0x71, 0x28, 0xb9, - 0xac, 0x91, 0x87, 0x2a, 0xff, 0xd6, 0x6d, 0xb8, 0xc0, 0x33, 0x35, 0x3b, 0x3c, 0xb6, 0x55, 0xcb, - 0x8c, 0x2c, 0x8f, 0xee, 0x75, 0xa0, 0x8d, 0x61, 0xb6, 0x3f, 0x26, 0xb6, 0x08, 0x68, 0x32, 0x8e, - 0x02, 0x89, 0xde, 0xeb, 0xce, 0xb1, 0x13, 0x8e, 0x51, 0x67, 0xe9, 0xc9, 0x2f, 0x00, 0x62, 0x08, - 0x4b, 0x7b, 0x91, 0x27, 0x69, 0xaa, 0xbb, 0x5b, 0xdd, 0x4d, 0xda, 0x62, 0xc6, 0x99, 0xa4, 0xbd, - 0x48, 0xc2, 0x89, 0x77, 0x80, 0xba, 0x63, 0xf6, 0x3b, 0xaf, 0x17, 0x1a, 0x88, 0x24, 0x1e, 0xbc, - 0x0d, 0x17, 0x8a, 0x91, 0xd8, 0x4e, 0x6a, 0xa7, 0xc7, 0x92, 0x54, 0x3f, 0xab, 0x86, 0xe5, 0x7c, - 0x50, 0xdb, 0xe9, 0xe1, 0xb1, 0x44, 0x33, 0xb0, 0x01, 0xf5, 0xe8, 0xe8, 0x07, 0x1b, 0x05, 0xa1, - 0x35, 0x5f, 0x10, 0x6a, 0xd1, 0xd1, 0x0f, 0x96, 0x1c, 0x89, 0x5f, 0x94, 0xcd, 0xe6, 0xcc, 0xd4, - 0xb4, 0x69, 0x6a, 0x56, 0x73, 0x7c, 0x69, 0x76, 0x86, 0x9f, 0x41, 0x0d, 0x7f, 0xe7, 0x51, 0x2c, - 0x36, 0xa1, 0xce, 0xe2, 0xa8, 0xb4, 0x9b, 0xb3, 0x5a, 0x58, 0xbb, 0x82, 0x77, 0x2c, 0x43, 0x34, - 0xb4, 0xa0, 0x97, 0x9b, 0x8e, 0xc7, 0xa1, 0xff, 0x24, 0x93, 0xe2, 0x2b, 0x58, 0x8e, 0x13, 0xa9, - 0xd9, 0xde, 0xce, 0x4e, 0xd0, 0x79, 0xd3, 0x12, 0xbc, 0xaa, 0xb9, 0x34, 0x6f, 0x71, 0x82, 0x1c, - 0xda, 0x8d, 0xa7, 0xde, 0x87, 0xdf, 0xc3, 0xc5, 0x9c, 0xe2, 0x40, 0xba, 0x51, 0xe8, 0x39, 0xc9, - 0x19, 0x59, 0xf9, 0x99, 0xbe, 0xd5, 0x4f, 0xe9, 0xfb, 0x80, 0xfa, 0xfe, 0x6f, 0x15, 0x68, 0xdd, - 0xcf, 0x9e, 0x3f, 0x3f, 0x63, 0x59, 0x12, 0x6d, 0xa8, 0x3c, 0xa4, 0x0e, 0x16, 0xac, 0xca, 0x43, - 0x74, 0x44, 0xf7, 0x4f, 0x50, 0xae, 0x89, 0xcf, 0x9b, 0x96, 0x7e, 0xc3, 0x00, 0x79, 0xff, 0xe4, - 0xf0, 0x25, 0x1c, 0xcd, 0x68, 0x0c, 0x90, 0xee, 0x64, 0x7e, 0x80, 0x6e, 0x92, 0x66, 0xde, 0xfc, - 0x1d, 0x43, 0xce, 0xdd, 0x11, 0x0f, 0xe5, 0x7e, 0x12, 0x4d, 0x78, 0xb2, 0xb4, 0xca, 0x98, 0x83, - 0x11, 0x5f, 0xc3, 0x8a, 0xde, 0xc0, 0xd3, 0x5a, 0xc1, 0x56, 0xb1, 0x74, 0x89, 0x75, 0x7f, 0xd2, - 0xa6, 0xdf, 0xf0, 0xef, 0xd7, 0xa0, 0x81, 0xa1, 0xe5, 0x6f, 0x22, 0x3f, 0x14, 0xb7, 0xa1, 0xf9, - 0x43, 0xe4, 0x87, 0xbc, 0xdb, 0xc0, 0x49, 0x8e, 0x15, 0xee, 0xeb, 0x61, 0xe4, 0xc9, 0x4d, 0xa4, - 0xa1, 0x7d, 0x86, 0xc6, 0x0f, 0xfa, 0x49, 0x9b, 0xa7, 0xc4, 0x1f, 0x1f, 0xa7, 0x36, 0x02, 0xb5, - 0x6e, 0x6d, 0xf9, 0xca, 0x42, 0x18, 0xf5, 0x7a, 0x15, 0x80, 0x62, 0xda, 0x28, 0xb4, 0xe3, 0x13, - 0x1d, 0xd7, 0x35, 0x10, 0xf2, 0x28, 0xdc, 0x3f, 0x41, 0xd9, 0xf3, 0x95, 0xad, 0xf7, 0xb5, 0xb4, - 0x0f, 0x5e, 0x8a, 0xeb, 0xdf, 0x86, 0x2e, 0xfa, 0x47, 0xea, 0xc4, 0x8f, 0xed, 0x38, 0x89, 0x8e, - 0xcc, 0xa4, 0xa0, 0xd7, 0x74, 0x70, 0xe2, 0xc7, 0xfb, 0x08, 0x23, 0xb7, 0x44, 0xef, 0x96, 0xa1, - 0xda, 0x66, 0xfb, 0x0f, 0x1a, 0x84, 0xf3, 0x4b, 0x5b, 0x62, 0x01, 0x47, 0x09, 0x75, 0x72, 0x37, - 0xea, 0x89, 0x0c, 0x28, 0x1c, 0xb8, 0x04, 0x0d, 0x14, 0x06, 0x42, 0x35, 0x18, 0xe5, 0x46, 0x8c, - 0x7a, 0x17, 0x20, 0x90, 0xa3, 0xd4, 0x46, 0x2e, 0xe3, 0x0d, 0x80, 0x99, 0xad, 0x27, 0xc4, 0xee, - 0x20, 0x52, 0xbc, 0x07, 0x2d, 0x9e, 0x05, 0xa6, 0x85, 0x73, 0xb4, 0x40, 0x68, 0x26, 0xbe, 0x09, - 0xad, 0x30, 0x0a, 0x6d, 0xf9, 0x84, 0xa8, 0xb5, 0xdc, 0x4e, 0x75, 0x1c, 0x46, 0xe1, 0xbd, 0x27, - 0x48, 0x2c, 0x6e, 0xe9, 0x31, 0xf0, 0x1e, 0x4c, 0xfb, 0x05, 0x7b, 0x30, 0x34, 0x12, 0xde, 0x8d, - 0xf8, 0xd0, 0x8c, 0x84, 0x5b, 0x74, 0x5e, 0xd0, 0x82, 0xc7, 0xc3, 0x4d, 0xd6, 0xa1, 0x4d, 0xeb, - 0x3e, 0x71, 0x62, 0x3b, 0x75, 0xc6, 0xda, 0xaa, 0x03, 0xc2, 0xf6, 0x9c, 0xf8, 0xd0, 0x19, 0x0b, - 0x0b, 0x2e, 0xcd, 0xf0, 0xdb, 0x11, 0xb2, 0x2e, 0xcf, 0x5a, 0xcf, 0xec, 0xe1, 0xcc, 0xe7, 0xba, - 0xb5, 0x29, 0xae, 0x23, 0x96, 0xa7, 0xd9, 0xfd, 0x1c, 0x2e, 0xc9, 0x09, 0x65, 0x3f, 0x26, 0x71, - 0x22, 0x95, 0x9a, 0x72, 0xcd, 0xfa, 0x6c, 0xe3, 0x90, 0x60, 0x27, 0xc7, 0xe7, 0xfe, 0xd9, 0xdb, - 0xd0, 0x75, 0x54, 0x34, 0xb2, 0xcd, 0x94, 0x07, 0x94, 0xcb, 0xa8, 0x5a, 0x6d, 0x84, 0x5a, 0x3c, - 0xd1, 0x01, 0x1a, 0x74, 0xa2, 0xd2, 0x43, 0x95, 0xa3, 0x94, 0xf2, 0x18, 0x0d, 0xab, 0x83, 0x60, - 0x1e, 0x88, 0x1c, 0xa5, 0xc3, 0x7f, 0xbe, 0x00, 0x8d, 0x07, 0x51, 0x14, 0xff, 0x4c, 0x19, 0x28, - 0xf3, 0xd6, 0xc2, 0x8b, 0x79, 0x6b, 0x71, 0x9a, 0xb7, 0x66, 0x78, 0x60, 0xe9, 0xc7, 0xf3, 0x40, - 0xf5, 0x27, 0xf3, 0x40, 0xed, 0x67, 0xf0, 0x40, 0x7d, 0x96, 0x07, 0x86, 0xb7, 0xa1, 0x7a, 0x20, - 0xd3, 0x47, 0x31, 0x5a, 0x33, 0xe3, 0x17, 0x1b, 0x43, 0x30, 0x65, 0xcd, 0xb4, 0x4f, 0xac, 0x86, - 0x7f, 0xd5, 0x82, 0xe6, 0x5d, 0xe9, 0x65, 0x3c, 0xb3, 0xe5, 0x79, 0xaa, 0xbc, 0x78, 0x9e, 0x16, - 0xa6, 0xe7, 0x09, 0x4d, 0xa7, 0x91, 0xc1, 0x39, 0x1b, 0xa8, 0x0d, 0x23, 0x82, 0x28, 0xac, 0x85, - 0x04, 0xea, 0x5d, 0xc8, 0xa9, 0xf9, 0xcc, 0x05, 0xf0, 0xe5, 0xdc, 0x5c, 0xfd, 0x79, 0xdc, 0x3c, - 0xad, 0xc7, 0xce, 0xed, 0x4f, 0xbe, 0x72, 0x7a, 0x67, 0x75, 0x58, 0xe3, 0x9c, 0x0e, 0x7b, 0x00, - 0x2b, 0x51, 0x68, 0x7b, 0x59, 0x1c, 0xf8, 0x18, 0x17, 0x92, 0x5f, 0x1d, 0x85, 0xe4, 0x4e, 0x50, - 0x46, 0x34, 0xe7, 0xd1, 0x47, 0xe1, 0x5d, 0x43, 0xc4, 0x7b, 0x25, 0xd6, 0x72, 0x34, 0x0b, 0x42, - 0x11, 0xf2, 0x70, 0x69, 0xc8, 0x13, 0x20, 0x1f, 0x96, 0x53, 0xbb, 0x6d, 0x82, 0xee, 0x44, 0x01, - 0xd9, 0xb6, 0xcf, 0xa0, 0x57, 0x50, 0x31, 0x33, 0xb5, 0x5e, 0xc0, 0x4c, 0x1d, 0xd3, 0x90, 0xf9, - 0xe9, 0xaf, 0x43, 0x6f, 0x7d, 0x00, 0x2b, 0x66, 0x0b, 0x48, 0xbb, 0x33, 0xb4, 0x82, 0x5d, 0xe2, - 0xa0, 0xbe, 0xde, 0xf5, 0x21, 0x4f, 0x86, 0x96, 0xe8, 0x97, 0xb0, 0x5a, 0x22, 0x47, 0xf6, 0x2d, - 0xeb, 0xaf, 0x32, 0xaf, 0x2c, 0xe7, 0x6d, 0xf1, 0xf5, 0x01, 0xef, 0xe1, 0xb7, 0x3c, 0x19, 0x98, - 0x0f, 0xe9, 0xe8, 0xa4, 0xe9, 0xc9, 0x40, 0xa7, 0x1e, 0xf7, 0xe0, 0x6d, 0x8c, 0xe4, 0x10, 0xef, - 0x3a, 0x71, 0x9a, 0x25, 0xd2, 0x8e, 0x03, 0xc7, 0x95, 0xc7, 0x51, 0xe0, 0xc9, 0xa4, 0x18, 0xdc, - 0x32, 0x0d, 0xee, 0x5a, 0x14, 0x60, 0x38, 0xb3, 0xc3, 0x94, 0xfb, 0x05, 0xa1, 0x19, 0xeb, 0x36, - 0xbc, 0x71, 0xae, 0x3b, 0x34, 0x75, 0x45, 0x47, 0x82, 0x3a, 0xba, 0x34, 0xdd, 0x11, 0x92, 0x98, - 0x2e, 0x3e, 0x84, 0x0b, 0xbc, 0x76, 0xcc, 0xdc, 0x27, 0x52, 0xc6, 0x76, 0xe0, 0xa8, 0x74, 0xb0, - 0xc2, 0x6e, 0x05, 0x21, 0x89, 0x81, 0xbf, 0x95, 0x32, 0x7e, 0xe0, 0xf0, 0x57, 0xb9, 0x89, 0x8e, - 0x3c, 0xa8, 0xcd, 0xd4, 0xdc, 0xae, 0xf2, 0x57, 0x89, 0x8a, 0xc3, 0x0f, 0x6c, 0x5c, 0x9a, 0xe4, - 0x5f, 0xc1, 0x95, 0xa9, 0x2e, 0x26, 0x4e, 0x72, 0x52, 0xb8, 0xe2, 0x83, 0x0b, 0x34, 0x6f, 0x17, - 0x4b, 0xed, 0xf7, 0x88, 0x40, 0xcf, 0xe2, 0x47, 0xb0, 0x86, 0x81, 0x69, 0xe4, 0x9d, 0x64, 0x33, - 0x41, 0xdb, 0x1a, 0x0d, 0x1a, 0xc3, 0xd6, 0x47, 0xde, 0x49, 0x36, 0x15, 0xb8, 0x7d, 0x0a, 0x83, - 0x29, 0x5a, 0x3b, 0xa1, 0xcd, 0x79, 0x3b, 0x8e, 0xd4, 0xe0, 0x22, 0xef, 0x07, 0x95, 0x77, 0x14, - 0x79, 0xeb, 0x7e, 0x3f, 0x52, 0xe2, 0x3e, 0xac, 0xc7, 0xc7, 0x67, 0xca, 0xa7, 0x64, 0x22, 0x39, - 0xf4, 0xe7, 0x3b, 0x18, 0x50, 0x07, 0x57, 0x0d, 0x1d, 0xfb, 0xfd, 0x33, 0xfd, 0x7c, 0x06, 0x97, - 0x0c, 0x63, 0x1d, 0x4b, 0xf7, 0x64, 0x7a, 0xc6, 0x2e, 0xd1, 0x8c, 0x5d, 0xd0, 0x1c, 0x85, 0xf8, - 0xd2, 0x6c, 0x6d, 0x40, 0x9f, 0xec, 0x9d, 0x3d, 0x8a, 0xb2, 0x50, 0xff, 0xe9, 0x65, 0xfa, 0xd3, - 0x2e, 0xc1, 0xef, 0x23, 0x98, 0x7e, 0x72, 0x03, 0xfa, 0x64, 0x2d, 0x59, 0xea, 0x99, 0xf2, 0x0a, - 0x53, 0x22, 0x5c, 0x0b, 0x3a, 0x52, 0x7e, 0x02, 0x17, 0x35, 0xd1, 0xc8, 0x0f, 0x9d, 0xa0, 0xfc, - 0x33, 0x57, 0xd9, 0xcd, 0x67, 0xf4, 0x7d, 0xc4, 0x16, 0x3f, 0xf1, 0x5b, 0x10, 0xa3, 0x28, 0x91, - 0xfe, 0x38, 0xa4, 0xb0, 0x96, 0xfe, 0x44, 0x0d, 0x5e, 0x27, 0xd9, 0x78, 0xbd, 0xf0, 0xf3, 0x1f, - 0xdd, 0xfd, 0xf6, 0xf1, 0x7d, 0xa6, 0xfb, 0x56, 0x9e, 0xd1, 0xff, 0x68, 0xc9, 0xec, 0x8f, 0xa6, - 0xc1, 0x6a, 0xf8, 0x4f, 0x6a, 0xd0, 0x25, 0x6b, 0xfc, 0xb7, 0x46, 0xe0, 0x6f, 0x8d, 0xc0, 0xdf, - 0x04, 0x23, 0x70, 0x13, 0x96, 0xfd, 0x30, 0xce, 0x52, 0x94, 0x20, 0x65, 0x67, 0x1c, 0xc5, 0x2d, - 0xf3, 0x76, 0x13, 0x21, 0xbe, 0x95, 0x67, 0x8a, 0x43, 0xb8, 0xe1, 0xdf, 0xab, 0x40, 0x7d, 0x3f, - 0x89, 0xbc, 0xcc, 0x4d, 0x7f, 0xa6, 0x54, 0x4c, 0x73, 0xdb, 0xe2, 0xab, 0xb8, 0x6d, 0xe9, 0x9c, - 0x47, 0xf7, 0x2f, 0x2b, 0xd0, 0xd4, 0x43, 0x78, 0xb0, 0xf5, 0x33, 0x07, 0x51, 0xe4, 0xb6, 0x2b, - 0x73, 0x73, 0xdb, 0xaf, 0x1c, 0x05, 0x32, 0xe1, 0x53, 0x2e, 0x06, 0x8a, 0xe2, 0x22, 0xd1, 0xdd, - 0xb4, 0xda, 0x0c, 0x7d, 0x14, 0x53, 0x3e, 0xfb, 0x19, 0x34, 0x29, 0xca, 0x27, 0x2d, 0xb2, 0x06, - 0x35, 0xd6, 0x6a, 0x7a, 0xa0, 0xfa, 0xed, 0xe5, 0x32, 0xbd, 0xf0, 0xb3, 0x64, 0x7a, 0xf8, 0xef, - 0x16, 0xa1, 0x43, 0x5b, 0x2e, 0xf7, 0xb3, 0x90, 0xa5, 0x26, 0xdf, 0xa4, 0xae, 0x4c, 0x6f, 0x52, - 0x2f, 0x25, 0x32, 0x35, 0x89, 0xf4, 0x36, 0x7f, 0x66, 0x27, 0x0a, 0xee, 0xca, 0x91, 0x45, 0x18, - 0x9c, 0x2a, 0x27, 0x19, 0xab, 0x79, 0x65, 0x00, 0x08, 0xc7, 0xbf, 0x8a, 0x9d, 0xc4, 0x99, 0x28, - 0x53, 0x06, 0xc0, 0x6f, 0x42, 0xc0, 0x12, 0xc9, 0x26, 0x4f, 0x0b, 0x3d, 0xeb, 0xdd, 0x43, 0xe5, - 0x87, 0xe3, 0x5c, 0xd1, 0x34, 0xa8, 0xfc, 0x63, 0x1c, 0x48, 0x71, 0x17, 0x04, 0xa7, 0x45, 0x12, - 0xe9, 0xa0, 0xf3, 0x41, 0xfd, 0x90, 0xb6, 0x69, 0x6d, 0xad, 0xf1, 0x67, 0x69, 0x2e, 0x2d, 0x42, - 0xef, 0x23, 0xd6, 0xea, 0xfb, 0x33, 0x90, 0x39, 0x93, 0xc9, 0x1e, 0x48, 0x1e, 0x29, 0xff, 0xe8, - 0xc9, 0x24, 0xb7, 0x84, 0xb8, 0xe5, 0x2b, 0x58, 0x19, 0x65, 0x41, 0x90, 0xca, 0xd3, 0xd4, 0x56, - 0x51, 0x96, 0xb8, 0xd2, 0x7e, 0x49, 0x46, 0x66, 0xd9, 0xd0, 0x1e, 0x10, 0xa9, 0x25, 0x47, 0xe2, - 0x4b, 0x10, 0x79, 0x07, 0xe6, 0x1f, 0x4d, 0xbe, 0xf4, 0x5c, 0xfb, 0xbe, 0x21, 0xd5, 0x7f, 0x3b, - 0x1a, 0x6e, 0xc3, 0x05, 0x93, 0x23, 0x45, 0xd5, 0xb6, 0x85, 0x72, 0x4b, 0x7b, 0x47, 0x66, 0x8e, - 0x2b, 0xa5, 0x39, 0x5e, 0x85, 0x6a, 0xb9, 0x3c, 0x8d, 0x5f, 0x86, 0xd7, 0xa1, 0x35, 0xf2, 0x03, - 0xa9, 0x73, 0x0d, 0xb8, 0x68, 0x3a, 0xeb, 0x50, 0xa1, 0xa4, 0xba, 0x7e, 0x1b, 0xfe, 0xbe, 0x02, - 0x17, 0x63, 0x27, 0x79, 0x92, 0xe9, 0x5d, 0x6d, 0xce, 0xef, 0xab, 0x63, 0x27, 0xf1, 0x50, 0x70, - 0xa9, 0x0b, 0xee, 0x9d, 0xab, 0x9b, 0x9a, 0x08, 0xe1, 0xb1, 0xdc, 0x80, 0x5e, 0xa9, 0x45, 0xea, - 0x24, 0x66, 0x67, 0xb5, 0x93, 0x44, 0xcf, 0x28, 0xfb, 0x7e, 0x80, 0x40, 0x31, 0x84, 0x4e, 0x41, - 0x27, 0xc9, 0x32, 0x52, 0x91, 0x90, 0xa1, 0xba, 0x17, 0x7a, 0x28, 0xb9, 0x61, 0x36, 0x61, 0x77, - 0x81, 0x8b, 0xd8, 0xea, 0x61, 0x36, 0x21, 0x3f, 0x61, 0x15, 0xaa, 0x5c, 0x39, 0x58, 0x25, 0x38, - 0xbf, 0x0c, 0xff, 0x50, 0x85, 0x95, 0x5d, 0x57, 0x1e, 0xc9, 0x64, 0x7c, 0xd7, 0x49, 0x9d, 0xfb, - 0x7e, 0x20, 0x0f, 0x1d, 0x75, 0x82, 0x0c, 0x47, 0x63, 0x8e, 0x9d, 0xf4, 0x58, 0xcf, 0x52, 0x03, - 0x01, 0xfb, 0x4e, 0x7a, 0x8c, 0x66, 0x8b, 0x90, 0xa3, 0x28, 0x99, 0xe8, 0x7d, 0xe0, 0xa6, 0x45, - 0xff, 0x78, 0x9f, 0x20, 0x79, 0x6b, 0xe5, 0x3f, 0x97, 0xba, 0xe4, 0x8e, 0x5a, 0x53, 0x5d, 0xc6, - 0x9b, 0xd0, 0x4e, 0xa4, 0x1b, 0x25, 0x9e, 0x8e, 0xfd, 0x79, 0x9c, 0x2d, 0x86, 0x71, 0xc0, 0x7f, - 0x13, 0x8a, 0xdc, 0x21, 0x6d, 0x75, 0xd9, 0xbe, 0xa9, 0xcb, 0xe9, 0xe5, 0x08, 0xe4, 0xbc, 0x5d, - 0x4f, 0xfc, 0x1d, 0xe8, 0x17, 0xb4, 0x94, 0xc8, 0x32, 0x11, 0xf0, 0x56, 0xe1, 0xc6, 0xcc, 0xf9, - 0xc5, 0xcd, 0x7d, 0xd3, 0xea, 0xcf, 0xa9, 0x11, 0x27, 0xeb, 0x8a, 0xee, 0x19, 0x2a, 0xde, 0x82, - 0x8e, 0x8a, 0x03, 0x3f, 0xd5, 0x0c, 0xa0, 0x74, 0x61, 0x5e, 0x9b, 0x80, 0x9c, 0x6f, 0x52, 0xf3, - 0x96, 0xb0, 0xf1, 0xa3, 0x96, 0xb0, 0x79, 0x7e, 0x09, 0xdf, 0x85, 0xbe, 0x9b, 0x48, 0x4f, 0x86, - 0xa9, 0xef, 0x04, 0xb6, 0x72, 0xa3, 0xd8, 0x98, 0xe9, 0x5e, 0x01, 0x3f, 0x40, 0xb0, 0xf8, 0x05, - 0x5c, 0x74, 0xa3, 0x30, 0x95, 0x61, 0x9a, 0x97, 0x6e, 0xea, 0x6c, 0x8d, 0x2e, 0x39, 0xb9, 0xa0, - 0xd1, 0xa6, 0x80, 0x93, 0xf3, 0x35, 0xe2, 0x36, 0xac, 0xf2, 0xf2, 0xcc, 0x34, 0xe2, 0x5a, 0x01, - 0x41, 0x2b, 0x35, 0xdd, 0x42, 0x67, 0x90, 0x12, 0xa9, 0x7c, 0x2f, 0x73, 0x02, 0xad, 0x21, 0x4a, - 0x19, 0x24, 0x4b, 0x63, 0xf4, 0x56, 0x2a, 0xe5, 0xa8, 0xa6, 0x68, 0xa9, 0xd0, 0x85, 0xb6, 0x9a, - 0x9a, 0x96, 0x48, 0xa6, 0xa8, 0xbf, 0x71, 0xd4, 0xf1, 0xe5, 0x3b, 0xb0, 0x3a, 0x6f, 0x41, 0x5e, - 0x95, 0xbc, 0x6c, 0x96, 0x92, 0x97, 0xba, 0x3c, 0xf5, 0xbf, 0x2f, 0xc0, 0x05, 0xb3, 0xde, 0x14, - 0x72, 0xe4, 0x4c, 0x7d, 0x8d, 0xec, 0x39, 0x86, 0x29, 0xf9, 0x76, 0x4f, 0xd3, 0x02, 0x06, 0xd1, - 0xde, 0xce, 0x06, 0xf4, 0x35, 0x41, 0xc1, 0xfc, 0xfc, 0x95, 0xae, 0x97, 0x77, 0x45, 0x22, 0x40, - 0x3f, 0x38, 0x92, 0x09, 0xce, 0x91, 0x47, 0xd5, 0xda, 0xd4, 0x84, 0x98, 0x9d, 0x7e, 0xd0, 0xe0, - 0x0c, 0xcb, 0x51, 0xa5, 0xcd, 0x93, 0xcc, 0x09, 0xfc, 0xf4, 0xcc, 0x1e, 0xf9, 0x32, 0xf0, 0x28, - 0x53, 0xce, 0x75, 0x84, 0x7d, 0x83, 0xb9, 0x8f, 0x88, 0x5d, 0x4f, 0x95, 0x46, 0xa2, 0x13, 0xb0, - 0xb9, 0x00, 0xe8, 0x91, 0x1c, 0x10, 0x78, 0xd7, 0x9b, 0x2f, 0x2b, 0xb5, 0xf9, 0xb2, 0xf2, 0x0e, - 0xf4, 0x66, 0xd7, 0x9c, 0x93, 0xa2, 0x5d, 0x35, 0xbd, 0xde, 0xf3, 0x98, 0xb0, 0x31, 0x97, 0x09, - 0xf5, 0xa4, 0xff, 0xcf, 0x05, 0x58, 0xd5, 0x93, 0xbe, 0x13, 0x05, 0xd9, 0x04, 0xad, 0x3d, 0x55, - 0x25, 0xad, 0x43, 0x7b, 0x12, 0xb1, 0x0b, 0x55, 0x52, 0x7f, 0x30, 0x89, 0x72, 0x5d, 0xbc, 0x01, - 0x7d, 0x9f, 0x5b, 0xe6, 0xf3, 0x62, 0x2a, 0x84, 0x35, 0x5c, 0xcf, 0x0a, 0x72, 0xa1, 0x0a, 0x9d, - 0x58, 0x1d, 0x47, 0xa9, 0x26, 0x25, 0x25, 0xce, 0x73, 0xbe, 0x6c, 0x50, 0x44, 0x4d, 0x9e, 0xec, - 0xfb, 0x20, 0xdc, 0x2c, 0x49, 0x50, 0x3e, 0x4a, 0xe4, 0x9c, 0xbc, 0xeb, 0x6b, 0x4c, 0x41, 0xfd, - 0x16, 0xd4, 0x27, 0x51, 0xe1, 0x91, 0x4c, 0x39, 0xa2, 0x56, 0x6d, 0x12, 0x11, 0x87, 0x5c, 0x46, - 0xaf, 0xe9, 0x49, 0xe6, 0x27, 0xd2, 0x33, 0x76, 0xd8, 0xbc, 0x6b, 0x23, 0x7d, 0xec, 0x7b, 0x9e, - 0x0c, 0x75, 0xe2, 0xa8, 0xe1, 0xab, 0x6f, 0xe8, 0x9d, 0x0a, 0x68, 0xe5, 0xc8, 0xc1, 0xd0, 0x2c, - 0xcc, 0x02, 0x92, 0x8a, 0x40, 0x97, 0x6b, 0xf6, 0x34, 0xe2, 0x61, 0x16, 0xa0, 0x44, 0x04, 0x7a, - 0x49, 0xc9, 0x96, 0x20, 0x0b, 0xda, 0xc7, 0x7e, 0x98, 0x92, 0xaa, 0x68, 0xd2, 0x92, 0x22, 0x02, - 0x99, 0xf0, 0x1b, 0x3f, 0x4c, 0x87, 0xff, 0x62, 0x01, 0xd6, 0xf4, 0xc4, 0x1f, 0xe8, 0x09, 0xd0, - 0xf6, 0x99, 0xa2, 0x0b, 0x33, 0x5d, 0x3a, 0xaf, 0xb7, 0x68, 0x81, 0x01, 0xed, 0xd2, 0x80, 0x0b, - 0xee, 0x5a, 0xd0, 0x65, 0x9c, 0x86, 0xaf, 0xde, 0x07, 0x71, 0x8e, 0xaf, 0x94, 0xde, 0xd6, 0xec, - 0xcf, 0x30, 0x96, 0x12, 0x1f, 0xc3, 0xda, 0x44, 0xa6, 0x0e, 0x09, 0x42, 0x10, 0xb9, 0x0e, 0xb5, - 0x22, 0x91, 0xe7, 0xe9, 0x5e, 0x35, 0xd8, 0x07, 0x1a, 0x89, 0x42, 0x8f, 0xdf, 0x98, 0x38, 0xa1, - 0x3f, 0x92, 0x2a, 0x25, 0x3f, 0x83, 0x5b, 0xb0, 0xe3, 0xd3, 0x37, 0x18, 0xf4, 0x24, 0x88, 0x9a, - 0x3c, 0xd6, 0x11, 0x2f, 0x62, 0x8d, 0x68, 0xea, 0x89, 0x1c, 0xe9, 0xb5, 0xeb, 0xe0, 0x5a, 0x85, - 0x7e, 0x38, 0xe6, 0x83, 0x03, 0x75, 0xf6, 0x29, 0x0d, 0x70, 0x2f, 0xf2, 0xe4, 0xf0, 0x2f, 0x96, - 0x72, 0x1e, 0xdd, 0xd7, 0xf0, 0x83, 0xd4, 0x49, 0xa9, 0x56, 0x3e, 0x1f, 0x3c, 0xdb, 0x48, 0x9e, - 0xab, 0x8e, 0x81, 0x72, 0x49, 0xfd, 0x26, 0xac, 0x4c, 0x8f, 0x96, 0x69, 0x17, 0xb8, 0xac, 0xa0, - 0x3c, 0xdc, 0xbc, 0x04, 0x3f, 0xa7, 0x67, 0x52, 0x5d, 0x7d, 0x6e, 0xa0, 0x4c, 0xf6, 0x41, 0x31, - 0x09, 0x4a, 0x27, 0xe7, 0xa5, 0xa7, 0xad, 0x62, 0xde, 0xab, 0x3a, 0xd0, 0x08, 0x14, 0xcd, 0x82, - 0x3c, 0x4e, 0xb2, 0x50, 0x7a, 0xda, 0xa4, 0xf7, 0x72, 0xf8, 0x3e, 0x81, 0x71, 0xc0, 0xb9, 0x66, - 0x2a, 0x75, 0x5d, 0xe3, 0xae, 0x3d, 0xad, 0x99, 0x8a, 0xae, 0x91, 0x47, 0x0b, 0x7a, 0xdd, 0x37, - 0x2b, 0x88, 0x5e, 0x4e, 0xad, 0xfb, 0xfe, 0x14, 0x06, 0x39, 0x2d, 0xff, 0x5d, 0xf1, 0x81, 0x06, - 0x1b, 0x1f, 0xd3, 0x84, 0x7e, 0x33, 0xff, 0xc8, 0x47, 0xb0, 0x36, 0xdb, 0x50, 0x7f, 0xa9, 0x49, - 0xcd, 0x56, 0xa6, 0x9a, 0x15, 0x7f, 0x92, 0xaf, 0xaf, 0xeb, 0xb8, 0xc7, 0xd2, 0x3e, 0xf6, 0x75, - 0x01, 0xfa, 0xa2, 0xb5, 0x6c, 0x50, 0x3b, 0x88, 0xf9, 0xc6, 0x4f, 0xd5, 0x1c, 0xfa, 0x89, 0xaf, - 0x94, 0xb6, 0x8a, 0xd3, 0xf4, 0x7b, 0xbe, 0x52, 0xc3, 0xff, 0xda, 0x82, 0xb6, 0xf1, 0x14, 0xa9, - 0xf6, 0xf9, 0xfd, 0xb2, 0xd3, 0xdf, 0xda, 0xea, 0x1b, 0xef, 0x1d, 0x49, 0xb6, 0xd3, 0x34, 0x31, - 0xb9, 0x3e, 0x0e, 0x06, 0xa6, 0xfc, 0x9d, 0x05, 0x72, 0x10, 0x0a, 0x7f, 0x67, 0x1b, 0x96, 0x4b, - 0x1e, 0xa4, 0x9d, 0x46, 0xa9, 0x13, 0xe8, 0xa0, 0xa0, 0x54, 0x37, 0x56, 0x22, 0xb1, 0x7a, 0xf8, - 0xc2, 0xbe, 0xc5, 0x21, 0x52, 0x63, 0xb0, 0xe1, 0x46, 0x81, 0x29, 0xb6, 0x9d, 0x09, 0x36, 0x10, - 0x43, 0x15, 0x31, 0x89, 0xc4, 0x38, 0x57, 0x3d, 0x09, 0xb4, 0x04, 0x35, 0x19, 0x72, 0xf0, 0x24, - 0xc8, 0x07, 0x48, 0xce, 0x7c, 0x8d, 0xe2, 0x18, 0x1a, 0x20, 0x79, 0xe9, 0x1f, 0x40, 0x2b, 0x4a, - 0xfc, 0xb1, 0x4f, 0x69, 0x62, 0x76, 0x70, 0x66, 0x3f, 0x02, 0x4c, 0xb0, 0x83, 0x9f, 0x1a, 0x42, - 0x4d, 0x9b, 0xff, 0xf3, 0x55, 0x32, 0x1a, 0x63, 0x8e, 0x00, 0xb8, 0x29, 0x0e, 0x87, 0x25, 0xb2, - 0x59, 0x1c, 0x01, 0x70, 0xd3, 0x83, 0x27, 0x01, 0x65, 0xca, 0x6f, 0x40, 0xcf, 0x25, 0x73, 0xc1, - 0x02, 0x15, 0xc8, 0x90, 0xd6, 0xb4, 0x6a, 0x75, 0x18, 0x8c, 0xe3, 0x7b, 0x20, 0x43, 0x5d, 0x6a, - 0xe9, 0x04, 0x01, 0x46, 0xac, 0x91, 0xe3, 0xe9, 0xba, 0x98, 0xb6, 0x01, 0x3e, 0x88, 0x1c, 0x4f, - 0x7c, 0x01, 0x97, 0x11, 0x67, 0x73, 0x41, 0x6b, 0x98, 0x4d, 0x64, 0xe2, 0xbb, 0xb6, 0xa3, 0xa8, - 0x4e, 0x46, 0x97, 0xc7, 0xac, 0x21, 0xc5, 0x3d, 0x24, 0x78, 0xc8, 0xf8, 0x6d, 0xf5, 0xbd, 0x4c, - 0x22, 0xf1, 0x3d, 0x65, 0xcb, 0xe7, 0xb9, 0xef, 0x66, 0x5b, 0xe2, 0xcd, 0x62, 0xad, 0x5e, 0x40, - 0x49, 0x75, 0x68, 0x88, 0xb0, 0x8c, 0xd3, 0x47, 0xed, 0xc5, 0xb7, 0x20, 0x8c, 0x81, 0x23, 0xce, - 0x4f, 0x1d, 0x75, 0xc2, 0x45, 0xb9, 0x53, 0x5b, 0x6d, 0x73, 0x7c, 0x54, 0xcb, 0x58, 0x46, 0x04, - 0x22, 0x40, 0x89, 0xdf, 0xc2, 0x6a, 0xde, 0x99, 0xf6, 0x65, 0xa8, 0x3b, 0xde, 0xd0, 0xb8, 0x76, - 0xbe, 0xbb, 0x29, 0x17, 0xc8, 0x32, 0x23, 0x61, 0x30, 0x77, 0xf9, 0x35, 0xf4, 0x4c, 0x97, 0x3c, - 0xeb, 0x6a, 0xd0, 0xa7, 0xde, 0xde, 0x38, 0xd7, 0xdb, 0x94, 0x6d, 0xcf, 0xed, 0x33, 0x43, 0xf1, - 0x47, 0x73, 0x4b, 0x6e, 0xac, 0x0c, 0x6d, 0x87, 0xb4, 0xb6, 0xd6, 0xcf, 0xf5, 0x34, 0x63, 0xac, - 0x2c, 0x33, 0x04, 0x03, 0x17, 0x1f, 0xc2, 0x05, 0xd3, 0x59, 0x44, 0x21, 0x9e, 0xed, 0x47, 0x14, - 0xfd, 0x09, 0x76, 0xb1, 0x34, 0x92, 0xc3, 0xbf, 0xdd, 0x88, 0xa3, 0xc5, 0x2b, 0xa6, 0x09, 0x5b, - 0x61, 0x8a, 0x88, 0xf3, 0x9f, 0x5a, 0x21, 0xdb, 0x35, 0xd0, 0x24, 0x6c, 0x97, 0x31, 0x02, 0x36, - 0xc3, 0xdf, 0x80, 0x3e, 0x55, 0xce, 0xe3, 0xb2, 0x46, 0x89, 0xe7, 0x87, 0x4e, 0x30, 0x58, 0xe5, - 0x3d, 0x57, 0x84, 0x5b, 0xd1, 0xb3, 0x47, 0x0c, 0x15, 0x87, 0xb0, 0x66, 0x3e, 0x94, 0xab, 0x19, - 0x85, 0xa6, 0x84, 0x36, 0xbc, 0xe7, 0x4d, 0xdc, 0x94, 0xc1, 0xb1, 0xcc, 0x12, 0x4e, 0x9b, 0xa1, - 0xbb, 0x70, 0x6d, 0x66, 0x69, 0x27, 0xce, 0xa9, 0x3d, 0x91, 0x93, 0x28, 0x39, 0xd3, 0x06, 0x64, - 0x8d, 0x14, 0xd8, 0x95, 0xa9, 0x45, 0xdc, 0x73, 0x4e, 0xf7, 0x88, 0x86, 0xcd, 0xc9, 0x57, 0x70, - 0x75, 0xa6, 0x17, 0x2e, 0x44, 0x97, 0xa1, 0x73, 0x14, 0x48, 0x8f, 0xb6, 0xc8, 0x1b, 0xd6, 0xa5, - 0xa9, 0x2e, 0x0e, 0x90, 0xe2, 0x1e, 0x13, 0x88, 0x2f, 0x81, 0x94, 0xbd, 0xa2, 0x93, 0x76, 0xb6, - 0x72, 0x9d, 0x90, 0x76, 0xc5, 0xf3, 0xaa, 0x0a, 0xe4, 0x45, 0x3e, 0x86, 0x87, 0x9a, 0xd2, 0xea, - 0x16, 0xc4, 0xa4, 0x39, 0x3f, 0x86, 0xb6, 0xd9, 0x58, 0xa6, 0xb6, 0x97, 0xa8, 0xed, 0x32, 0xb7, - 0xd5, 0x5b, 0xc9, 0xd4, 0xb0, 0x35, 0x2a, 0x5e, 0xc4, 0x26, 0xc0, 0x89, 0x33, 0x3a, 0x71, 0xb8, - 0xcd, 0xe5, 0x72, 0x80, 0xff, 0x2d, 0xc2, 0xa9, 0x45, 0xf3, 0xc4, 0x3c, 0x8a, 0xcf, 0xe1, 0x92, - 0x91, 0xc2, 0x67, 0xc7, 0x51, 0xa0, 0x1d, 0xf6, 0x91, 0x13, 0x46, 0x59, 0xaa, 0x37, 0xca, 0xd7, - 0x34, 0xc1, 0x77, 0x88, 0x47, 0x01, 0xb8, 0x4f, 0x58, 0xed, 0xb0, 0x1e, 0x41, 0x93, 0xb6, 0x79, - 0xa8, 0xb7, 0xfc, 0xd0, 0x43, 0xe5, 0xe5, 0x87, 0x1e, 0x3e, 0x80, 0xb6, 0x8e, 0x66, 0x5e, 0x74, - 0x8a, 0xa2, 0xc5, 0x78, 0x4e, 0x88, 0xbe, 0x0f, 0x4d, 0x0a, 0x65, 0xe8, 0x1b, 0xd7, 0xa0, 0xc5, - 0x27, 0xed, 0x8e, 0x82, 0xc8, 0x3d, 0x31, 0xc1, 0x07, 0x81, 0xee, 0x20, 0x64, 0x08, 0xd0, 0x78, - 0x1c, 0xfa, 0x51, 0xb8, 0x1d, 0x04, 0xc3, 0x3f, 0xd5, 0xa0, 0x89, 0x3e, 0x0f, 0xed, 0x4b, 0x61, - 0xd8, 0x48, 0x8c, 0x49, 0x75, 0x15, 0x13, 0x27, 0xd6, 0xc7, 0x3a, 0x5a, 0x08, 0x44, 0xaa, 0x3d, - 0x27, 0x9e, 0x29, 0xbb, 0x58, 0x98, 0x29, 0xbb, 0x78, 0x93, 0xcf, 0x7d, 0x72, 0xd5, 0xac, 0x34, - 0xe5, 0xf6, 0xd4, 0xc1, 0x1d, 0x06, 0xa1, 0x2f, 0x46, 0x24, 0x4e, 0x40, 0xfe, 0x1b, 0x46, 0x87, - 0x81, 0xd2, 0x15, 0x1a, 0x24, 0x17, 0xdb, 0x1a, 0x71, 0x20, 0xd9, 0xde, 0x94, 0x36, 0x23, 0xab, - 0xb3, 0x9b, 0x91, 0x37, 0x01, 0xdc, 0x28, 0xf4, 0xc8, 0x45, 0x9c, 0x49, 0x48, 0x73, 0x79, 0x44, - 0x81, 0xfd, 0x11, 0xdb, 0xe4, 0xef, 0x40, 0x3f, 0xa7, 0x40, 0x0f, 0xd0, 0x0d, 0xf3, 0xf8, 0x5a, - 0x53, 0x59, 0x72, 0xb4, 0x13, 0xa6, 0xb3, 0xfb, 0xe9, 0xcd, 0x73, 0xfb, 0xe9, 0x2f, 0x28, 0xa4, - 0x81, 0x9f, 0x7c, 0x7a, 0xee, 0x12, 0x34, 0xa8, 0x62, 0xcf, 0xcb, 0x62, 0x6d, 0x8b, 0xea, 0xbe, - 0xa2, 0xbc, 0xc7, 0x8b, 0xf6, 0xec, 0xdb, 0xff, 0xbf, 0xf6, 0xec, 0x3b, 0x3f, 0x6e, 0xcf, 0xbe, - 0xfb, 0xe3, 0xf6, 0xec, 0x67, 0xf6, 0xb8, 0x7b, 0xb3, 0x7b, 0xdc, 0x2f, 0x4c, 0x2b, 0xf6, 0x5f, - 0x98, 0x56, 0x7c, 0x45, 0x4e, 0x70, 0xf9, 0xe5, 0x39, 0xc1, 0x57, 0x27, 0x25, 0xc5, 0xab, 0x92, - 0x92, 0x37, 0xa0, 0x97, 0x26, 0x8e, 0x7b, 0xc2, 0x91, 0xd6, 0x89, 0x3c, 0x53, 0x3a, 0x09, 0xda, - 0x21, 0x30, 0xc6, 0x59, 0xdf, 0xca, 0x33, 0x35, 0x7c, 0x0c, 0x40, 0x21, 0x28, 0xfd, 0xda, 0x8b, - 0x78, 0xa3, 0xf2, 0x93, 0x8b, 0xac, 0xfe, 0xaa, 0x02, 0x70, 0xe0, 0x4c, 0x62, 0xde, 0x43, 0x16, - 0x7f, 0x06, 0x2d, 0x45, 0x6f, 0xe5, 0x22, 0x93, 0x92, 0xa1, 0x2e, 0x48, 0xf5, 0x23, 0x1f, 0xee, - 0x52, 0xf9, 0x33, 0xb1, 0x35, 0xf7, 0x90, 0x17, 0xb4, 0x56, 0x0d, 0x01, 0xed, 0xed, 0x5d, 0x87, - 0xae, 0x26, 0x88, 0x65, 0xe2, 0xca, 0x90, 0xeb, 0xfb, 0x2b, 0x56, 0x87, 0xa1, 0xfb, 0x0c, 0x14, - 0x1f, 0xe6, 0x64, 0xc6, 0x24, 0x9e, 0x4f, 0x99, 0xe9, 0x26, 0xda, 0x26, 0x0e, 0xb7, 0xcc, 0xaf, - 0xd0, 0x40, 0x1a, 0xb0, 0x84, 0xdf, 0xeb, 0xbf, 0x26, 0x5a, 0x50, 0xd7, 0xbd, 0xf6, 0x2b, 0xa2, - 0x03, 0x4d, 0x3a, 0x4d, 0x48, 0xb8, 0x85, 0xe1, 0xff, 0x5e, 0x81, 0xd6, 0x6e, 0xa8, 0xd2, 0x24, - 0x63, 0x16, 0x2e, 0xce, 0xcc, 0x55, 0xe9, 0xcc, 0x9c, 0xae, 0x03, 0xe7, 0xdf, 0xa0, 0x3a, 0xf0, - 0x0f, 0xa0, 0xae, 0x8f, 0x67, 0xea, 0xc4, 0xc2, 0xdc, 0xb3, 0x9d, 0x86, 0x46, 0x6c, 0x42, 0xc3, - 0xd3, 0xe7, 0x46, 0x75, 0x25, 0x4d, 0xe9, 0x30, 0xa7, 0x39, 0x51, 0x6a, 0xe5, 0x34, 0xe2, 0x4d, - 0x58, 0x74, 0xc6, 0x63, 0x1d, 0xd5, 0xf7, 0x0a, 0x52, 0x72, 0xd2, 0x2c, 0xc4, 0x89, 0x5b, 0xd0, - 0x24, 0xf5, 0x49, 0x55, 0x6d, 0xb5, 0xd9, 0x3e, 0x4d, 0xc9, 0x1c, 0x6b, 0x54, 0xca, 0x49, 0xdc, - 0x82, 0x66, 0x10, 0x45, 0x31, 0x37, 0xa8, 0xcf, 0x36, 0x30, 0xf5, 0x45, 0x56, 0x23, 0x30, 0x95, - 0x46, 0x37, 0xa0, 0x86, 0xee, 0x7f, 0x14, 0x6b, 0xb7, 0xb9, 0x34, 0x0e, 0xaa, 0xb3, 0xb1, 0xaa, - 0x8a, 0xca, 0x6d, 0xb6, 0x00, 0x98, 0xff, 0xa9, 0xe7, 0xe6, 0xec, 0x74, 0xe4, 0xb9, 0x55, 0x14, - 0x52, 0x93, 0x66, 0xbd, 0x03, 0x7d, 0xce, 0xa3, 0x95, 0x5a, 0x82, 0xa9, 0x05, 0x36, 0x2d, 0xa7, - 0x53, 0xb3, 0x56, 0x37, 0x99, 0x4e, 0xd5, 0xbe, 0x07, 0xf5, 0x98, 0x93, 0x43, 0xa4, 0x61, 0xc8, - 0x64, 0x9b, 0xa6, 0x3a, 0x6b, 0x64, 0x19, 0x0a, 0xf1, 0x6b, 0xe8, 0x72, 0xcd, 0xea, 0x48, 0x67, - 0x49, 0x68, 0x67, 0x6f, 0xea, 0x64, 0xdf, 0x54, 0x12, 0xc5, 0xea, 0xa4, 0x53, 0x39, 0x95, 0x5f, - 0x42, 0xa7, 0x38, 0xb0, 0x84, 0x16, 0xbf, 0x67, 0xb2, 0x15, 0xa6, 0x79, 0x39, 0x1a, 0xb3, 0xda, - 0xb2, 0x1c, 0x9b, 0x6d, 0x40, 0x4d, 0xd7, 0x51, 0xf7, 0xa9, 0x55, 0xe9, 0xd6, 0x05, 0xae, 0x9c, - 0xb4, 0x34, 0x1e, 0xe7, 0xb2, 0x28, 0x11, 0x25, 0xc7, 0x71, 0x6a, 0x2e, 0xf3, 0xfa, 0x50, 0xab, - 0x99, 0x97, 0x86, 0x8a, 0x7b, 0xd3, 0x25, 0xab, 0x9c, 0xd4, 0x5b, 0xa1, 0xa6, 0x97, 0xe6, 0x34, - 0xe5, 0xf4, 0x9e, 0xd5, 0x8b, 0x67, 0x2a, 0x5f, 0xdf, 0x87, 0x46, 0x94, 0x78, 0x74, 0x3e, 0x80, - 0xaa, 0x28, 0x72, 0x17, 0x48, 0x9f, 0x49, 0x25, 0xe5, 0x51, 0x8f, 0xf8, 0x05, 0x1d, 0x8b, 0x38, - 0x89, 0xc8, 0xcb, 0x25, 0x15, 0x77, 0xe1, 0xbc, 0x63, 0xa1, 0xf1, 0xa4, 0xe0, 0xde, 0x86, 0xba, - 0xa9, 0x0e, 0x5f, 0x3b, 0x47, 0x69, 0x50, 0xe2, 0x23, 0xe8, 0x4d, 0x2b, 0x34, 0x35, 0xb8, 0x78, - 0x8e, 0xba, 0x3b, 0xa5, 0xbf, 0xd0, 0x1a, 0x57, 0x03, 0x7f, 0xe2, 0xa7, 0xda, 0xe7, 0x9b, 0x3a, - 0x21, 0x4a, 0x08, 0x8c, 0xff, 0x74, 0x0a, 0xe4, 0xd2, 0xf9, 0xf8, 0x4f, 0xa7, 0x49, 0x06, 0x50, - 0xf7, 0xd5, 0x7d, 0x3f, 0x51, 0xa9, 0xae, 0x6f, 0x30, 0xaf, 0x62, 0x0d, 0x6a, 0xbe, 0x42, 0x33, - 0xa1, 0xbd, 0x34, 0xfd, 0x26, 0x6e, 0x42, 0x4d, 0x57, 0xce, 0xaf, 0x9f, 0x93, 0x68, 0x7d, 0xb2, - 0xc6, 0xd2, 0x14, 0xe2, 0x5d, 0xa8, 0x53, 0xd9, 0x74, 0x14, 0x0f, 0xde, 0x9c, 0xe5, 0x00, 0xae, - 0x5d, 0xb6, 0x6a, 0x01, 0xd7, 0x30, 0xbf, 0x07, 0x75, 0xe3, 0xa4, 0x0c, 0x67, 0xb9, 0x5a, 0x3b, - 0x2b, 0x96, 0xa1, 0x10, 0xd7, 0xa1, 0x3a, 0x41, 0x3d, 0x36, 0x78, 0x6b, 0x56, 0x42, 0x59, 0xbd, - 0x31, 0x56, 0xfc, 0x5d, 0xb8, 0x5c, 0x2e, 0x3c, 0x36, 0x55, 0xc9, 0x7a, 0x83, 0xf3, 0x3a, 0xb5, - 0x7d, 0x73, 0x0e, 0xab, 0x4c, 0xd7, 0x2f, 0x5b, 0x17, 0xe3, 0x17, 0x14, 0x36, 0x7f, 0x92, 0xab, - 0x7b, 0x94, 0xae, 0xc1, 0x0d, 0xe3, 0x7c, 0x9f, 0x37, 0x18, 0xc6, 0x08, 0x90, 0x9d, 0xf9, 0x0c, - 0xda, 0xa3, 0xec, 0xf9, 0xf3, 0x33, 0xb3, 0x39, 0xff, 0x0e, 0xb5, 0x2b, 0x6d, 0x31, 0x94, 0x6a, - 0x9d, 0xad, 0xd6, 0xa8, 0x54, 0xf8, 0x7c, 0x11, 0xea, 0x6e, 0x68, 0x3b, 0x9e, 0x97, 0x0c, 0x36, - 0xb8, 0xd6, 0xd9, 0x0d, 0xb7, 0x3d, 0x8f, 0x2e, 0x77, 0x88, 0x62, 0x49, 0x27, 0xa4, 0x6d, 0xdf, - 0x1b, 0xbc, 0xcb, 0x86, 0xc7, 0x80, 0x76, 0x3d, 0xba, 0xfd, 0xc1, 0xc4, 0xe5, 0xbe, 0x37, 0xb8, - 0xa9, 0x6f, 0x7f, 0xd0, 0xa0, 0x5d, 0x0f, 0x1d, 0x4f, 0x0c, 0x62, 0x0c, 0x64, 0xf0, 0x1e, 0x27, - 0x3c, 0x26, 0xce, 0xe9, 0xbe, 0x06, 0xa1, 0x90, 0x72, 0x6a, 0x8f, 0xd4, 0xd6, 0xfb, 0xb3, 0x42, - 0x9a, 0xa7, 0x81, 0xad, 0xa6, 0x9f, 0x67, 0x84, 0x49, 0xb0, 0x49, 0x15, 0xd9, 0xc1, 0xd6, 0xe0, - 0x83, 0xf3, 0x82, 0xad, 0xb3, 0xdc, 0x28, 0xd8, 0x26, 0xe1, 0xbd, 0x05, 0xc0, 0x3a, 0x8b, 0x14, - 0xce, 0xe6, 0x6c, 0x9b, 0x3c, 0x1a, 0xb0, 0xf8, 0x48, 0x13, 0xa9, 0x9a, 0x2d, 0x00, 0x4a, 0x2f, - 0x70, 0x9b, 0x5b, 0xb3, 0x6d, 0x72, 0xef, 0xde, 0x6a, 0x3e, 0xcd, 0x1d, 0xfd, 0x5b, 0xd0, 0xcc, - 0xd0, 0x8f, 0x47, 0x4f, 0x7a, 0x70, 0x7b, 0x96, 0x99, 0x8d, 0x8b, 0x6f, 0x35, 0x32, 0xfd, 0x84, - 0x1f, 0x21, 0xdb, 0x43, 0x6e, 0xc8, 0xe0, 0xc3, 0xd9, 0x8f, 0xe4, 0x71, 0x80, 0x45, 0x26, 0x8a, - 0x43, 0x82, 0x4f, 0xa0, 0xc5, 0x93, 0xc6, 0x8d, 0xb6, 0x66, 0x79, 0xa4, 0xf0, 0x6b, 0x2c, 0x9e, - 0x5d, 0x6e, 0x76, 0x1d, 0xaa, 0x4e, 0x1c, 0x07, 0x67, 0x83, 0x8f, 0x66, 0x39, 0x7c, 0x1b, 0xc1, - 0x16, 0x63, 0x91, 0x95, 0x26, 0x59, 0x90, 0xfa, 0xe6, 0x14, 0xd2, 0xc7, 0xb3, 0xac, 0x54, 0x3a, - 0xd6, 0x69, 0xb5, 0x26, 0xa5, 0x33, 0x9e, 0xef, 0x43, 0x23, 0x8e, 0x54, 0x6a, 0x7b, 0x93, 0x60, - 0xf0, 0xc9, 0x39, 0x33, 0xc2, 0x27, 0x52, 0xac, 0x7a, 0xac, 0x8f, 0xf4, 0x4c, 0x9d, 0x92, 0xfe, - 0xc5, 0xcc, 0x29, 0xe9, 0x2d, 0x68, 0x4f, 0xa2, 0x70, 0x1c, 0x79, 0x47, 0x3c, 0xfb, 0x9f, 0x96, - 0x83, 0xc2, 0x3d, 0xc4, 0x70, 0x18, 0xa9, 0x89, 0xb4, 0x69, 0xe8, 0x6b, 0xdf, 0xcd, 0x57, 0xb6, - 0xa3, 0x48, 0xed, 0x7f, 0xc6, 0x21, 0x3c, 0xc3, 0x77, 0xd5, 0x36, 0x41, 0xa7, 0xb7, 0xb7, 0x8f, - 0xce, 0x74, 0x2e, 0xf2, 0x73, 0x62, 0xcf, 0x62, 0x7b, 0xfb, 0xce, 0x19, 0x27, 0x24, 0xbf, 0x80, - 0xcb, 0xa5, 0x73, 0x8b, 0x51, 0x6c, 0x87, 0x36, 0xaa, 0x80, 0x44, 0x7a, 0x99, 0x2b, 0x07, 0x5f, - 0xe4, 0xf1, 0xa6, 0x3e, 0xbc, 0x18, 0xc5, 0x0f, 0xf7, 0x13, 0x69, 0x11, 0x56, 0x3c, 0x2c, 0x9f, - 0x91, 0x74, 0x82, 0x71, 0x94, 0xf8, 0xe9, 0xf1, 0x64, 0xf0, 0x4b, 0xf2, 0x03, 0x5f, 0x2f, 0xc5, - 0x03, 0x79, 0xfe, 0x6b, 0xdb, 0x10, 0x59, 0xc5, 0x18, 0x73, 0x98, 0xf8, 0x02, 0x2e, 0x69, 0x5b, - 0x80, 0x1d, 0x4e, 0x1d, 0x45, 0x57, 0x83, 0x5f, 0xd1, 0x59, 0xf4, 0x8b, 0x05, 0xc1, 0xd7, 0xa5, - 0x53, 0xe9, 0x4a, 0x6c, 0xc3, 0xeb, 0xf3, 0xda, 0xa2, 0x63, 0xc2, 0x13, 0xf0, 0x25, 0x4d, 0xc0, - 0xe5, 0xf3, 0xed, 0x0f, 0x24, 0xdf, 0x4b, 0xc3, 0xe1, 0xf3, 0x6f, 0x96, 0x1a, 0xcb, 0x7d, 0xf1, - 0x9b, 0xa5, 0xc6, 0xdb, 0xfd, 0xeb, 0x56, 0xab, 0xb4, 0x55, 0x30, 0xfc, 0x04, 0xda, 0xdb, 0x74, - 0x21, 0x91, 0xaf, 0xc8, 0x54, 0x5d, 0x87, 0xa5, 0xbc, 0xc4, 0x24, 0xb7, 0x81, 0x44, 0xf1, 0x5c, - 0xee, 0x86, 0xa3, 0xc8, 0x22, 0xf4, 0xf0, 0xdf, 0x2c, 0x41, 0x8d, 0xd3, 0xfe, 0xaf, 0x3e, 0x74, - 0xf8, 0xba, 0x11, 0xe4, 0xb0, 0x38, 0x9d, 0xc1, 0x32, 0x4b, 0xe8, 0xd9, 0x2a, 0xec, 0x66, 0x51, - 0xbd, 0xb2, 0x0a, 0x55, 0x8e, 0xbe, 0x39, 0x29, 0xc1, 0x2f, 0xa4, 0xc4, 0x32, 0x75, 0x4c, 0xb7, - 0x0e, 0xe9, 0x34, 0xdb, 0x92, 0x05, 0x06, 0xb4, 0xeb, 0xd1, 0xee, 0xa3, 0x21, 0x20, 0x2d, 0x59, - 0xd3, 0xd9, 0x05, 0x0d, 0x24, 0x5d, 0x69, 0x2a, 0x63, 0xea, 0x2f, 0xa8, 0x8c, 0x79, 0x03, 0x96, - 0x42, 0x73, 0x70, 0x28, 0xc7, 0xd3, 0xad, 0x23, 0x04, 0x17, 0x37, 0x21, 0x3f, 0x29, 0xa9, 0xbd, - 0xbe, 0x17, 0x9f, 0xa4, 0xdc, 0x82, 0x66, 0x7e, 0x85, 0x95, 0x76, 0xf4, 0x56, 0x37, 0x8b, 0x4b, - 0xad, 0x0e, 0xcd, 0x93, 0x55, 0x90, 0xbd, 0xbc, 0xbe, 0xa3, 0xf5, 0xf3, 0xea, 0x3b, 0x38, 0x0a, - 0x76, 0xa3, 0x50, 0xa5, 0x7a, 0x7f, 0xb5, 0xee, 0xab, 0x1d, 0x7c, 0x15, 0x9f, 0x43, 0x27, 0x91, - 0xee, 0x53, 0x7b, 0xa2, 0xc6, 0xfc, 0x89, 0x4e, 0xf9, 0xb4, 0xf6, 0x44, 0x8d, 0xbf, 0xa1, 0xda, - 0x13, 0x1d, 0x94, 0xb6, 0x90, 0x76, 0x4f, 0x8d, 0xa9, 0xd7, 0xf7, 0x60, 0x79, 0x22, 0x27, 0x47, - 0x32, 0x51, 0xc7, 0x7e, 0x6c, 0xac, 0x59, 0x97, 0x6a, 0x64, 0xfa, 0x05, 0x82, 0xc7, 0x32, 0xfc, - 0x87, 0x15, 0x68, 0xe0, 0x2c, 0x22, 0x2f, 0x09, 0x01, 0x4b, 0x13, 0x37, 0xce, 0x74, 0xac, 0x41, - 0xcf, 0xfa, 0x5a, 0x2c, 0xe6, 0x12, 0x7d, 0x2d, 0x16, 0xad, 0x21, 0x67, 0x0d, 0xe9, 0x99, 0x6f, - 0x3a, 0x39, 0xa3, 0x8d, 0x65, 0xe6, 0x0c, 0xf3, 0x2a, 0x2e, 0x40, 0xcd, 0x0d, 0x69, 0xc3, 0x81, - 0xb3, 0xaf, 0x55, 0x37, 0xdc, 0x09, 0x53, 0x0d, 0x2e, 0x0e, 0x9f, 0x54, 0xdd, 0x70, 0xd7, 0x3b, - 0x1d, 0xfe, 0xfb, 0x0a, 0x2c, 0xef, 0x27, 0x91, 0x2b, 0x95, 0x7a, 0x80, 0xbe, 0x12, 0x65, 0xba, - 0xf0, 0x8b, 0x94, 0x18, 0xe0, 0xa4, 0x12, 0x3d, 0x23, 0x0f, 0xf3, 0x6e, 0x50, 0x1e, 0xd1, 0x2d, - 0x5a, 0x4d, 0x82, 0x50, 0x40, 0x97, 0xa3, 0x4b, 0x15, 0x14, 0x8c, 0xa6, 0x94, 0xc2, 0x75, 0xe8, - 0x16, 0x2a, 0xa5, 0x54, 0xec, 0x51, 0xdc, 0x47, 0x40, 0xbd, 0x5c, 0x83, 0x96, 0x2e, 0x09, 0xa2, - 0x6e, 0x38, 0x4b, 0x04, 0x0c, 0x3a, 0xd0, 0xa3, 0x60, 0xfd, 0x4b, 0x78, 0xce, 0x0b, 0xb1, 0x46, - 0x46, 0xf4, 0xf0, 0x0f, 0x15, 0xe8, 0xef, 0x27, 0x32, 0x76, 0x12, 0x49, 0x35, 0x42, 0x34, 0xc7, - 0x6b, 0x50, 0x0b, 0x64, 0x38, 0xd6, 0x65, 0x21, 0x8b, 0x96, 0x7e, 0xcb, 0xaf, 0x34, 0x5b, 0x28, - 0x5d, 0x69, 0x86, 0x73, 0x9d, 0x48, 0x47, 0xdf, 0x7c, 0x46, 0xcf, 0x28, 0x83, 0x18, 0x96, 0x73, - 0xec, 0xd9, 0xb0, 0xf8, 0x45, 0x9f, 0x7a, 0x3e, 0xf2, 0x43, 0xaa, 0xc5, 0xa4, 0x53, 0xcf, 0x77, - 0x7c, 0xd2, 0xe7, 0x0c, 0x46, 0xff, 0x8a, 0x6f, 0x0f, 0xa2, 0xdd, 0xa4, 0x86, 0xd5, 0x25, 0x02, - 0x27, 0x39, 0x3b, 0x20, 0x28, 0x85, 0xc0, 0x7c, 0xe9, 0x10, 0x17, 0x1a, 0x71, 0x1e, 0xa3, 0x63, - 0xee, 0x1c, 0x62, 0xdd, 0xa2, 0x86, 0xff, 0xb4, 0x0e, 0x2d, 0xbd, 0x42, 0xf4, 0x37, 0xcc, 0x1d, - 0x95, 0x9c, 0x3b, 0xfa, 0xb0, 0xa8, 0x9e, 0x04, 0x9a, 0x5d, 0xf0, 0x51, 0x7c, 0x04, 0x8b, 0x81, - 0x3f, 0xd1, 0x71, 0xe9, 0x95, 0x29, 0x1f, 0x63, 0x7a, 0x9d, 0x35, 0x2b, 0x23, 0x35, 0x1a, 0x36, - 0xba, 0x82, 0x01, 0x85, 0x46, 0xaf, 0x0d, 0xda, 0xfb, 0x53, 0x94, 0x4c, 0x9c, 0x75, 0xc7, 0xe5, - 0x3a, 0x60, 0xad, 0x6e, 0x3a, 0x56, 0x53, 0x43, 0x76, 0x3d, 0xf1, 0x31, 0x34, 0xf2, 0xdd, 0x73, - 0x13, 0x89, 0xa6, 0xa7, 0xe1, 0xe6, 0xce, 0xc3, 0xc3, 0xd3, 0xd0, 0x6c, 0x8f, 0xeb, 0x8f, 0xe5, - 0x94, 0xe2, 0xd7, 0xd0, 0x56, 0x52, 0x29, 0x3e, 0x23, 0x3f, 0x8a, 0xb4, 0x1a, 0xba, 0x50, 0x0e, - 0x32, 0x09, 0x8b, 0x7f, 0x6d, 0x84, 0x4e, 0x15, 0x20, 0xf1, 0x0d, 0x74, 0x4d, 0xfb, 0x20, 0x1a, - 0x8f, 0xf3, 0xec, 0xce, 0x95, 0x73, 0x3d, 0x3c, 0x20, 0x74, 0xa9, 0x9f, 0x8e, 0x2a, 0x23, 0xc4, - 0xd7, 0xd0, 0x8d, 0x99, 0x69, 0x6c, 0x5d, 0xdf, 0xc6, 0xea, 0xec, 0xf2, 0x94, 0x4b, 0x3c, 0xc5, - 0x54, 0xc5, 0x59, 0xd0, 0x02, 0xae, 0xce, 0xdf, 0x17, 0xc1, 0xe9, 0xbe, 0xe9, 0xfb, 0x22, 0x24, - 0xac, 0xe9, 0x0b, 0x9e, 0x46, 0x89, 0x43, 0xe7, 0xde, 0xd9, 0x92, 0x99, 0xa2, 0xd5, 0x5b, 0xe7, - 0x56, 0x0c, 0x3f, 0xb8, 0xc9, 0xf7, 0x22, 0xdc, 0xd7, 0x4d, 0xc8, 0xb2, 0xe9, 0x0a, 0xa0, 0xd5, - 0x64, 0x0e, 0x4a, 0x6c, 0xc2, 0x8a, 0xfe, 0x8c, 0x3c, 0x95, 0x6e, 0xa6, 0xaf, 0xfc, 0x20, 0xa5, - 0xd7, 0xb6, 0x96, 0x19, 0x75, 0xcf, 0x60, 0x76, 0x3d, 0xf1, 0x19, 0x0c, 0x54, 0xea, 0xa4, 0x92, - 0x06, 0x64, 0xf4, 0xae, 0x3f, 0x0e, 0xa3, 0x44, 0xea, 0xaa, 0x99, 0xb5, 0x1c, 0xaf, 0xf5, 0xed, - 0x2e, 0x61, 0xc5, 0xaf, 0xa1, 0x8f, 0x2a, 0x32, 0xcf, 0xa0, 0xd8, 0xa9, 0xd2, 0xc1, 0xf5, 0x7c, - 0x15, 0xdf, 0x45, 0x6a, 0xc3, 0x16, 0x87, 0x94, 0x89, 0xa7, 0xf6, 0x63, 0x19, 0xa2, 0x9b, 0x4e, - 0x1a, 0x42, 0x66, 0x4a, 0x7a, 0xfa, 0x56, 0x97, 0x55, 0xc4, 0x7e, 0x9d, 0x23, 0x2d, 0xc2, 0xa1, - 0x63, 0x60, 0xc4, 0x47, 0x6f, 0xa0, 0x92, 0x4b, 0x5a, 0xb8, 0x2b, 0x7d, 0x62, 0xd3, 0xcb, 0x5a, - 0x9a, 0x98, 0x06, 0x3d, 0xd3, 0xdc, 0x2f, 0xb9, 0xfc, 0x35, 0x5c, 0x7a, 0xe1, 0xac, 0xbe, 0xaa, - 0x8c, 0xa7, 0x53, 0xbe, 0x83, 0xe0, 0xff, 0x2c, 0x42, 0xab, 0xc4, 0xad, 0x74, 0x91, 0xa1, 0x92, - 0x89, 0x29, 0xd6, 0xc3, 0x67, 0x84, 0x1d, 0x47, 0xca, 0xd4, 0x9e, 0xd1, 0x33, 0xc2, 0x92, 0x28, - 0xaf, 0xc1, 0xa1, 0x67, 0xe4, 0x21, 0xbd, 0x6b, 0xa4, 0x57, 0x6c, 0x89, 0xef, 0xce, 0x28, 0x80, - 0xbb, 0x1e, 0xdd, 0x78, 0xe8, 0xa4, 0xce, 0x91, 0xa3, 0x4c, 0xd5, 0x65, 0xfe, 0x8e, 0xa6, 0xe1, - 0xa9, 0x4c, 0x70, 0x2c, 0xa6, 0xe6, 0x40, 0xbf, 0xa2, 0x8c, 0xd3, 0xaa, 0x3e, 0x8f, 0x42, 0xae, - 0x37, 0x68, 0x5b, 0x0d, 0x04, 0x7c, 0x1f, 0x85, 0xd4, 0x4c, 0x4b, 0xb4, 0xae, 0x9b, 0x31, 0xaf, - 0x68, 0x33, 0x9f, 0x64, 0x12, 0xc3, 0x45, 0x8f, 0xce, 0x18, 0x36, 0xad, 0x3a, 0xbd, 0x73, 0x29, - 0x0f, 0xc5, 0xb5, 0xcf, 0x1c, 0x3f, 0x25, 0xd5, 0x11, 0x65, 0xa9, 0x66, 0xfa, 0x1e, 0x22, 0xbe, - 0x73, 0xfc, 0xf4, 0x90, 0xc1, 0xe2, 0x43, 0x7d, 0x74, 0xb8, 0x4c, 0x4b, 0x77, 0xf7, 0xf0, 0x6e, - 0xb4, 0x98, 0xa1, 0x3f, 0x90, 0x74, 0x3d, 0xdd, 0xc4, 0x49, 0x13, 0xff, 0x34, 0x0a, 0xd1, 0x77, - 0xa2, 0x63, 0xfa, 0xf9, 0x2d, 0x8b, 0x0d, 0x6b, 0x25, 0x47, 0x3e, 0x24, 0x1c, 0x25, 0x68, 0x1f, - 0xc3, 0x86, 0x3c, 0x8d, 0x03, 0xdf, 0xf5, 0x67, 0xae, 0x1b, 0xb0, 0x5d, 0x47, 0xa5, 0x76, 0x22, - 0xd3, 0x2c, 0x09, 0x15, 0x6d, 0xb4, 0x6a, 0xbe, 0x7e, 0xcb, 0xd0, 0x97, 0xaf, 0x20, 0xd8, 0x71, - 0x54, 0x6a, 0x31, 0xed, 0xc3, 0x2c, 0x08, 0x70, 0x12, 0xf2, 0xc4, 0x30, 0xd7, 0x84, 0xd5, 0x15, - 0xa7, 0x84, 0x87, 0xff, 0xb9, 0x02, 0xcb, 0xe7, 0x34, 0x0d, 0x86, 0xa8, 0xa8, 0x65, 0x4c, 0x1d, - 0x4b, 0xdb, 0xaa, 0xe1, 0xeb, 0xae, 0x47, 0x88, 0x74, 0x92, 0x9a, 0x0a, 0x16, 0x44, 0xa4, 0x13, - 0x54, 0xa3, 0x17, 0xa0, 0x96, 0x9e, 0xd2, 0x92, 0xb3, 0xf5, 0xa9, 0xa6, 0xa7, 0xb8, 0xd6, 0xdb, - 0xd0, 0x0c, 0xa2, 0xb1, 0x1d, 0xc8, 0xa7, 0x92, 0xef, 0x80, 0xe9, 0x6e, 0xbd, 0xfd, 0x12, 0x15, - 0xb7, 0xf9, 0x20, 0x1a, 0x3f, 0x40, 0x5a, 0xab, 0x11, 0xe8, 0xa7, 0xe1, 0x6f, 0xa0, 0x61, 0xa0, - 0xa2, 0x09, 0xd5, 0xbb, 0xf2, 0x28, 0x1b, 0xf7, 0x5f, 0x13, 0x0d, 0x58, 0xc2, 0x16, 0xfd, 0x0a, - 0x3e, 0x7d, 0xe7, 0x24, 0x61, 0x7f, 0x01, 0xd1, 0xf7, 0x92, 0x24, 0x4a, 0xfa, 0x8b, 0xf8, 0xb8, - 0xef, 0x84, 0xbe, 0xdb, 0x5f, 0xc2, 0xc7, 0xfb, 0x4e, 0xea, 0x04, 0xfd, 0xea, 0xf0, 0xf7, 0x55, - 0x68, 0xec, 0xeb, 0xaf, 0x8b, 0xbb, 0xd0, 0xc9, 0xef, 0x52, 0x9c, 0xbf, 0x57, 0xbc, 0x3f, 0xfb, - 0x40, 0x7b, 0xc5, 0xed, 0xb8, 0xf4, 0x36, 0x7b, 0x23, 0xe3, 0xc2, 0xb9, 0x1b, 0x19, 0xaf, 0xc2, - 0xe2, 0x93, 0xe4, 0x6c, 0xba, 0x84, 0x7b, 0x3f, 0x70, 0x42, 0x0b, 0xc1, 0xe2, 0x43, 0x68, 0x51, - 0x96, 0x9a, 0xcd, 0xa8, 0xde, 0x5f, 0x2d, 0x5f, 0x7e, 0xca, 0xc5, 0xb9, 0x80, 0x44, 0xda, 0x63, - 0xdf, 0x84, 0x86, 0x7b, 0xec, 0x07, 0x5e, 0x22, 0x43, 0x7d, 0x94, 0x42, 0x9c, 0x1f, 0xb2, 0x95, - 0xd3, 0x88, 0x3f, 0x83, 0xbe, 0x5f, 0xec, 0x0f, 0x17, 0x45, 0x09, 0x53, 0xf6, 0xaa, 0xb4, 0x83, - 0x6c, 0xf5, 0x4a, 0xe4, 0xe4, 0x22, 0x16, 0x97, 0xa4, 0xd4, 0xcb, 0x97, 0xa4, 0xf0, 0x1d, 0x79, - 0xe4, 0xc7, 0x35, 0xf2, 0xdd, 0x25, 0x74, 0xe3, 0x6e, 0x68, 0xe7, 0xbb, 0x39, 0x1b, 0x8e, 0x1b, - 0xd7, 0x51, 0x3b, 0xe1, 0x6f, 0x43, 0x17, 0x9d, 0x7a, 0x9b, 0x63, 0x01, 0xb4, 0xa3, 0xa0, 0xef, - 0x74, 0xca, 0xd4, 0xf1, 0x5d, 0x8c, 0x06, 0x90, 0x19, 0xaf, 0x43, 0xd7, 0xfc, 0x8b, 0x0e, 0x9b, - 0x5a, 0xba, 0x68, 0x41, 0x43, 0x39, 0x68, 0xdc, 0x84, 0x15, 0xf7, 0xd8, 0x09, 0x43, 0x19, 0xd8, - 0x47, 0xd9, 0x68, 0x64, 0xdc, 0x30, 0xbe, 0xe8, 0x6b, 0x59, 0xa3, 0xee, 0x10, 0x86, 0xbc, 0xb1, - 0x21, 0x74, 0x42, 0x3f, 0x30, 0x97, 0x7d, 0x86, 0xec, 0x32, 0x57, 0xad, 0x56, 0xe8, 0x07, 0x7c, - 0xbd, 0x27, 0xdd, 0x4a, 0xda, 0xcf, 0x32, 0xdf, 0x53, 0x76, 0x1a, 0x99, 0xeb, 0x05, 0x75, 0xa6, - 0xa7, 0xb4, 0x77, 0xfa, 0x38, 0xf3, 0xbd, 0xc3, 0x48, 0x5f, 0x30, 0xd8, 0x21, 0x7a, 0xf3, 0x3a, - 0xfc, 0x0a, 0xda, 0x65, 0xde, 0x41, 0x5e, 0xa4, 0xcd, 0xad, 0xfe, 0x6b, 0x02, 0xa0, 0xf6, 0x30, - 0x4a, 0x26, 0x4e, 0xd0, 0xaf, 0xe0, 0x33, 0x2b, 0xf3, 0xfe, 0x82, 0x68, 0x43, 0xc3, 0x6c, 0xd6, - 0xf4, 0x17, 0x75, 0xfa, 0xf4, 0x97, 0xd0, 0x30, 0xb7, 0x26, 0xd2, 0x8d, 0x73, 0x91, 0x27, 0x39, - 0x34, 0xd2, 0xb5, 0xc2, 0x08, 0xa0, 0xb0, 0xc8, 0xdc, 0x42, 0xbb, 0x50, 0xdc, 0x42, 0x3b, 0xfc, - 0x2d, 0xb4, 0xcb, 0x43, 0x34, 0x09, 0x81, 0x4a, 0x91, 0x10, 0x98, 0xd3, 0x8a, 0x6a, 0x58, 0x92, - 0x68, 0x62, 0x97, 0xbc, 0xf7, 0x06, 0x02, 0xf0, 0x33, 0xc3, 0x7f, 0xb0, 0x00, 0x55, 0xda, 0xc2, - 0x20, 0xef, 0x0a, 0x1f, 0x0a, 0x09, 0xaa, 0x5a, 0x4d, 0x82, 0xfc, 0x3f, 0x1c, 0xdf, 0xcd, 0x13, - 0xc4, 0x4b, 0x2f, 0x4f, 0x10, 0x6f, 0xc3, 0xf2, 0xd3, 0xd2, 0x4d, 0xa7, 0xbc, 0x71, 0x51, 0x35, - 0xae, 0x18, 0xb6, 0xf9, 0xf3, 0xe2, 0xc6, 0x53, 0xda, 0xbe, 0xe8, 0x3d, 0x9d, 0x06, 0x88, 0x37, - 0x41, 0x9f, 0x84, 0xb0, 0xb9, 0x00, 0x89, 0xab, 0x75, 0x5a, 0x0c, 0xdb, 0xa6, 0x72, 0x23, 0x0c, - 0x80, 0x4f, 0x43, 0x73, 0xfd, 0x0d, 0x17, 0x68, 0x35, 0xd3, 0xd3, 0x90, 0x4b, 0x86, 0x86, 0x13, - 0x58, 0x99, 0x73, 0x6c, 0x4b, 0xac, 0x43, 0x7b, 0x2a, 0x8d, 0xc6, 0x67, 0x29, 0xc0, 0x2d, 0xf2, - 0x66, 0x1f, 0xc3, 0x9a, 0x0c, 0xfc, 0xb1, 0x7f, 0xe4, 0x53, 0x95, 0x6a, 0xe9, 0x24, 0x19, 0x2b, - 0x91, 0xd5, 0x12, 0x36, 0x3f, 0x49, 0x76, 0xf3, 0xdf, 0x56, 0xa0, 0xc6, 0xb7, 0x1d, 0x8b, 0x65, - 0xe8, 0x3c, 0x0e, 0x4f, 0xc2, 0xe8, 0x59, 0xc8, 0x80, 0xfe, 0x6b, 0x62, 0x05, 0x7a, 0x86, 0xdf, - 0xf4, 0xb5, 0xca, 0xfd, 0x8a, 0xe8, 0x43, 0x9b, 0x38, 0xda, 0x40, 0x16, 0xc4, 0x55, 0x18, 0x68, - 0xa7, 0xf0, 0x2e, 0x1a, 0xa0, 0x28, 0xf5, 0x47, 0x67, 0x06, 0xbb, 0x28, 0x7a, 0xd0, 0x3a, 0x48, - 0xa3, 0xf8, 0x40, 0x86, 0x9e, 0x1f, 0x8e, 0xfb, 0x4b, 0x62, 0x00, 0xab, 0xa6, 0x57, 0x2e, 0x45, - 0xb8, 0xef, 0x87, 0xbe, 0x3a, 0xee, 0x57, 0xc5, 0x15, 0xb8, 0x38, 0x0f, 0xb3, 0xed, 0x9e, 0xf4, - 0x6b, 0x62, 0x15, 0xfa, 0x06, 0x79, 0x47, 0xdf, 0x6d, 0xdb, 0xaf, 0xdf, 0xfc, 0x18, 0xc4, 0xf9, - 0x6b, 0x85, 0xf1, 0x9b, 0x0f, 0xe4, 0xd8, 0x71, 0xcf, 0x76, 0x82, 0x48, 0xa1, 0x78, 0x74, 0xa0, - 0x59, 0xf4, 0x55, 0xb9, 0x79, 0x1f, 0x6a, 0x7c, 0x0f, 0x74, 0xe9, 0xaf, 0x19, 0xd0, 0x7f, 0x0d, - 0x1b, 0xa3, 0xf1, 0xf5, 0xc3, 0xf1, 0x43, 0x79, 0x9a, 0xb2, 0x49, 0x78, 0xe0, 0xa8, 0xb4, 0xbf, - 0x20, 0xba, 0x00, 0xfa, 0xc7, 0xee, 0x85, 0x5e, 0x7f, 0xf1, 0xce, 0xce, 0x5f, 0xfe, 0xf1, 0x8d, - 0xca, 0x1f, 0xfe, 0xf8, 0x46, 0xe5, 0x3f, 0xfd, 0xf1, 0x8d, 0xd7, 0x7e, 0xf7, 0xa7, 0x37, 0x2a, - 0xdf, 0x7f, 0x58, 0xba, 0xe5, 0x5a, 0xdb, 0x64, 0xaa, 0xbe, 0xba, 0x95, 0x1b, 0xe8, 0x5b, 0xf1, - 0xc9, 0xf8, 0x56, 0x7c, 0x74, 0xcb, 0x48, 0xfc, 0x51, 0x8d, 0x2e, 0xaf, 0xfe, 0xe8, 0xff, 0x06, - 0x00, 0x00, 0xff, 0xff, 0x9c, 0xdd, 0x3b, 0xba, 0x3b, 0x5b, 0x00, 0x00, + // 8763 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbc, 0x4d, 0x6c, 0x1d, 0x47, + 0xb6, 0x18, 0xec, 0x4b, 0xf2, 0xfe, 0x9d, 0xfb, 0xcb, 0x22, 0x45, 0x5d, 0xfd, 0xd8, 0xa2, 0xaf, + 0x2d, 0x99, 0x96, 0x65, 0x4a, 0xa6, 0xed, 0x19, 0xdb, 0x33, 0x1e, 0x3f, 0x8a, 0x92, 0x6c, 0x8e, + 0x45, 0x89, 0xd3, 0xa4, 0x9e, 0x01, 0x03, 0xdf, 0xd7, 0x68, 0x76, 0xd7, 0xbd, 0x6c, 0xb3, 0x6f, + 0x77, 0xab, 0xab, 0x5b, 0x22, 0xb5, 0x4a, 0x96, 0x09, 0xb2, 0xcf, 0x43, 0x56, 0x13, 0x24, 0x8b, + 0x04, 0x59, 0x06, 0x2f, 0xcb, 0xac, 0x1f, 0x82, 0x2c, 0x66, 0x95, 0x55, 0x10, 0x04, 0x33, 0xcb, + 0x00, 0x49, 0x10, 0x20, 0x41, 0x80, 0xe0, 0x01, 0xc1, 0x39, 0xa7, 0xaa, 0xbb, 0xef, 0xe5, 0x95, + 0x64, 0x3b, 0xc1, 0xdb, 0xe4, 0xed, 0xba, 0xcf, 0x39, 0x55, 0x5d, 0x5d, 0x75, 0x7e, 0xeb, 0x9c, + 0x2a, 0xe8, 0xc6, 0x7e, 0x2c, 0x03, 0x3f, 0x94, 0x9b, 0x71, 0x12, 0xa5, 0x91, 0x68, 0x98, 0xf7, + 0xcb, 0x1f, 0x8e, 0xfd, 0xf4, 0x38, 0x3b, 0xda, 0x74, 0xa3, 0xc9, 0xed, 0x71, 0x34, 0x8e, 0x6e, + 0x13, 0xc1, 0x51, 0x36, 0xa2, 0x37, 0x7a, 0xa1, 0x27, 0x6e, 0x78, 0x19, 0x82, 0xc8, 0x3d, 0x31, + 0xcf, 0x71, 0xe0, 0x84, 0xfa, 0xb9, 0x97, 0xfa, 0x13, 0xa9, 0x52, 0x67, 0x12, 0x6b, 0x40, 0x33, + 0x3d, 0xd5, 0xb8, 0xe1, 0xef, 0x6b, 0x50, 0xdf, 0x93, 0x4a, 0x39, 0x63, 0x29, 0x86, 0xb0, 0xa8, + 0x7c, 0x6f, 0x50, 0x59, 0xaf, 0x6c, 0x74, 0xb7, 0xfa, 0x9b, 0xf9, 0xb0, 0x0e, 0x52, 0x27, 0xcd, + 0x94, 0x85, 0x48, 0xa4, 0x71, 0x27, 0xde, 0x60, 0x61, 0x96, 0x66, 0x4f, 0xa6, 0xc7, 0x91, 0x67, + 0x21, 0x52, 0xf4, 0x61, 0x51, 0x26, 0xc9, 0x60, 0x71, 0xbd, 0xb2, 0xd1, 0xb6, 0xf0, 0x51, 0x08, + 0x58, 0xf2, 0x9c, 0xd4, 0x19, 0x2c, 0x11, 0x88, 0x9e, 0xc5, 0xbb, 0xd0, 0x8d, 0x93, 0xc8, 0xb5, + 0xfd, 0x70, 0x14, 0xd9, 0x84, 0xad, 0x12, 0xb6, 0x8d, 0xd0, 0xdd, 0x70, 0x14, 0xdd, 0x43, 0xaa, + 0x01, 0xd4, 0x9d, 0xd0, 0x09, 0xce, 0x94, 0x1c, 0xd4, 0x08, 0x6d, 0x5e, 0x45, 0x17, 0x16, 0x7c, + 0x6f, 0x50, 0x5f, 0xaf, 0x6c, 0x2c, 0x59, 0x0b, 0xbe, 0x87, 0xdf, 0xc8, 0x32, 0xdf, 0x1b, 0x34, + 0xf8, 0x1b, 0xf8, 0x2c, 0x86, 0xd0, 0x0e, 0xa5, 0xf4, 0x1e, 0x45, 0xa9, 0x25, 0xe3, 0xe0, 0x6c, + 0xd0, 0x5c, 0xaf, 0x6c, 0x34, 0xac, 0x29, 0x98, 0xb8, 0x0c, 0x0d, 0x4f, 0x1e, 0x65, 0xe3, 0x3d, + 0x35, 0x1e, 0xc0, 0x7a, 0x65, 0xa3, 0x69, 0xe5, 0xef, 0xe2, 0x10, 0x2e, 0x26, 0xf2, 0x69, 0x26, + 0x55, 0x2a, 0x3d, 0x3b, 0x95, 0x4e, 0xe2, 0x45, 0xcf, 0x43, 0x7b, 0x12, 0x79, 0x72, 0xd0, 0xa2, + 0x19, 0xb8, 0x5a, 0x9e, 0xa5, 0x44, 0x3a, 0x93, 0x43, 0x4d, 0xb4, 0x17, 0x79, 0xd2, 0xba, 0x90, + 0x37, 0x2e, 0x83, 0x85, 0x05, 0x6b, 0x8e, 0xeb, 0xca, 0xf8, 0x7c, 0xa7, 0xed, 0x1f, 0xd1, 0xe9, + 0xaa, 0x69, 0x3b, 0xd5, 0xe7, 0x57, 0x70, 0xb5, 0x18, 0xe9, 0x91, 0x93, 0xba, 0xc7, 0xb6, 0x9b, + 0x48, 0xcf, 0x4f, 0x6d, 0x37, 0xca, 0xc2, 0x74, 0xd0, 0x59, 0xaf, 0x6c, 0x74, 0xac, 0x4b, 0x39, + 0xcd, 0x5d, 0x24, 0xd9, 0x21, 0x8a, 0x1d, 0x24, 0x78, 0x45, 0x07, 0x47, 0x67, 0xa9, 0x54, 0x83, + 0x2e, 0x4d, 0xf4, 0xdc, 0x0e, 0xee, 0x22, 0x81, 0xf8, 0x12, 0xae, 0xe4, 0x7f, 0x35, 0x67, 0x00, + 0x3d, 0x1a, 0xc0, 0xc0, 0x90, 0x9c, 0xfb, 0xfe, 0x4b, 0x9b, 0xf3, 0xe7, 0xfb, 0xf4, 0xf9, 0x79, + 0xcd, 0xf9, 0xeb, 0xd7, 0xa1, 0xcb, 0xad, 0x14, 0x0e, 0x30, 0x74, 0xe5, 0x60, 0x99, 0x5a, 0x74, + 0x08, 0x7a, 0xa0, 0x81, 0xe2, 0x16, 0x08, 0x26, 0x73, 0xdc, 0x93, 0x82, 0x54, 0x10, 0x69, 0x9f, + 0x30, 0xdb, 0xee, 0x89, 0xa1, 0xfe, 0x62, 0xe9, 0x2f, 0x7e, 0x7f, 0xed, 0x8d, 0xe1, 0x13, 0x68, + 0xee, 0x44, 0x61, 0x28, 0xdd, 0x34, 0x4a, 0xc4, 0x35, 0x68, 0x99, 0xc5, 0xb1, 0xb5, 0xac, 0x54, + 0x2d, 0x30, 0xa0, 0x5d, 0x4f, 0xbc, 0x07, 0x3d, 0xd7, 0x50, 0xdb, 0x7e, 0xe8, 0xc9, 0x53, 0x12, + 0x96, 0xaa, 0xd5, 0xcd, 0xc1, 0xbb, 0x08, 0x1d, 0xfe, 0xe7, 0x45, 0xa8, 0x1f, 0x1c, 0x67, 0xa3, + 0x51, 0x20, 0xc5, 0xbb, 0xd0, 0xd1, 0x8f, 0x3b, 0x51, 0xb0, 0xeb, 0x9d, 0xea, 0x7e, 0xa7, 0x81, + 0x62, 0x1d, 0x5a, 0x1a, 0x70, 0x78, 0x16, 0x4b, 0xdd, 0x6d, 0x19, 0x34, 0xdd, 0xcf, 0x9e, 0x1f, + 0x92, 0x0c, 0x2e, 0x5a, 0xd3, 0xc0, 0x19, 0x2a, 0xe7, 0x94, 0xc4, 0x72, 0x9a, 0xca, 0xa1, 0xaf, + 0x6d, 0x07, 0xfe, 0x33, 0x69, 0xc9, 0xf1, 0x4e, 0x98, 0x92, 0x70, 0x56, 0xad, 0x32, 0x48, 0x6c, + 0xc1, 0x05, 0xc5, 0x4d, 0xec, 0xc4, 0x09, 0xc7, 0x52, 0xd9, 0x99, 0x1f, 0xa6, 0xbf, 0xf8, 0x64, + 0x50, 0x5b, 0x5f, 0xdc, 0x58, 0xb2, 0x56, 0x34, 0xd2, 0x22, 0xdc, 0x13, 0x42, 0x89, 0x3b, 0xb0, + 0x3a, 0xd3, 0x86, 0x9b, 0xd4, 0xd7, 0x17, 0x37, 0x16, 0x2d, 0x31, 0xd5, 0x64, 0x97, 0x5a, 0xdc, + 0x87, 0xe5, 0x24, 0x0b, 0x51, 0x85, 0x3d, 0xf0, 0x83, 0x54, 0x26, 0x07, 0xb1, 0x74, 0x49, 0xc8, + 0x5b, 0x5b, 0x17, 0x37, 0x49, 0xcb, 0x59, 0xb3, 0x68, 0xeb, 0x7c, 0x0b, 0x71, 0x2b, 0x9f, 0xbc, + 0xfb, 0xa7, 0x71, 0x42, 0x9a, 0xa0, 0xb5, 0x05, 0xdc, 0x01, 0x42, 0xac, 0x32, 0x5a, 0xdc, 0x84, + 0x65, 0x2f, 0x71, 0xfc, 0xd0, 0x76, 0x82, 0xc0, 0x3e, 0xca, 0xdc, 0x13, 0x99, 0x2a, 0xd2, 0x0e, + 0x0d, 0xab, 0x47, 0x88, 0xed, 0x20, 0xb8, 0xcb, 0x60, 0x71, 0x03, 0x7a, 0x2a, 0x4d, 0xfc, 0x70, + 0x6c, 0x1f, 0x3b, 0xea, 0xd8, 0x3e, 0x91, 0x67, 0xa4, 0x1c, 0x1a, 0x56, 0x87, 0xc1, 0xdf, 0x38, + 0xea, 0xf8, 0x5b, 0x79, 0x36, 0xfc, 0x9f, 0x0b, 0xd0, 0xb8, 0xe7, 0xab, 0x18, 0xb9, 0x4c, 0x5c, + 0x84, 0xfa, 0x28, 0x0b, 0xdd, 0x82, 0x87, 0x6a, 0xf8, 0xba, 0xeb, 0x89, 0x5f, 0x43, 0x2f, 0x88, + 0x5c, 0x27, 0xb0, 0x73, 0x76, 0x19, 0x2c, 0xac, 0x2f, 0x6e, 0xb4, 0xb6, 0x56, 0x0a, 0xad, 0x90, + 0xb3, 0xa3, 0xd5, 0x25, 0xda, 0x82, 0x3d, 0xbf, 0x84, 0x7e, 0x22, 0x27, 0x51, 0x2a, 0x4b, 0xcd, + 0x17, 0xa9, 0xb9, 0x28, 0x9a, 0x7f, 0x97, 0x38, 0xf1, 0x23, 0x54, 0x25, 0x3d, 0xa6, 0x2d, 0x9a, + 0x7f, 0x54, 0x5a, 0x51, 0x39, 0xb6, 0x7d, 0xef, 0xd4, 0xa6, 0x0f, 0x0c, 0x96, 0xd6, 0x17, 0x37, + 0xaa, 0xc5, 0xf2, 0xc8, 0xf1, 0xae, 0x77, 0xfa, 0x10, 0x31, 0xe2, 0x63, 0x58, 0x9b, 0x6d, 0xc2, + 0xbd, 0x0e, 0xaa, 0xd4, 0x66, 0x65, 0xaa, 0x8d, 0x45, 0x28, 0xf1, 0x36, 0xb4, 0x4d, 0xa3, 0x14, + 0x59, 0xb9, 0xc6, 0xcc, 0xa5, 0x4a, 0xac, 0x7c, 0x11, 0xea, 0xbe, 0xb2, 0x95, 0x1f, 0x9e, 0x90, + 0x8e, 0x6f, 0x58, 0x35, 0x5f, 0x1d, 0xf8, 0xe1, 0x89, 0xb8, 0x04, 0x8d, 0x44, 0xba, 0x8c, 0x69, + 0x10, 0xa6, 0x9e, 0x48, 0x97, 0x50, 0x17, 0x01, 0x1f, 0x6d, 0x37, 0x95, 0x5a, 0xd3, 0xd7, 0x12, + 0xe9, 0xee, 0xa4, 0x72, 0xa8, 0xa0, 0xba, 0x27, 0x93, 0xb1, 0x44, 0x65, 0x8f, 0x0d, 0x0f, 0x5c, + 0x27, 0xa4, 0x79, 0x6f, 0x58, 0xf9, 0x3b, 0x9a, 0x9a, 0xd8, 0x49, 0x52, 0xdf, 0x09, 0x48, 0xb4, + 0x1a, 0x96, 0x79, 0x15, 0x57, 0xa0, 0xa9, 0x52, 0x27, 0x49, 0xf1, 0xef, 0x48, 0xa4, 0xaa, 0x56, + 0x83, 0x00, 0x28, 0x95, 0x17, 0xa1, 0x2e, 0x43, 0x8f, 0x50, 0x4b, 0xbc, 0x92, 0x32, 0xf4, 0x76, + 0xbd, 0xd3, 0xe1, 0xbf, 0xac, 0x40, 0x67, 0x2f, 0x0b, 0x52, 0x7f, 0x3b, 0x19, 0x67, 0x72, 0x12, + 0xa6, 0x68, 0xa2, 0xee, 0xf9, 0x2a, 0xd5, 0x5f, 0xa6, 0x67, 0xb1, 0x01, 0xcd, 0xaf, 0x93, 0x28, + 0x8b, 0x89, 0x2b, 0x79, 0xa5, 0xcb, 0x5c, 0x59, 0x20, 0x91, 0x83, 0x1f, 0x27, 0x9e, 0x4c, 0xee, + 0x9e, 0x11, 0xed, 0xe2, 0x39, 0xda, 0x32, 0x5a, 0x5c, 0x85, 0xe6, 0x81, 0x8c, 0x9d, 0xc4, 0x41, + 0x16, 0x58, 0x22, 0xbb, 0x56, 0x00, 0xf0, 0x5f, 0x89, 0x78, 0xd7, 0xd3, 0x82, 0x6d, 0x5e, 0x87, + 0xff, 0xa4, 0x02, 0xcd, 0xed, 0xf1, 0x38, 0x91, 0x63, 0x27, 0x25, 0x23, 0x1b, 0xc5, 0x34, 0xde, + 0x45, 0x6b, 0x21, 0x8a, 0xc9, 0x90, 0xe3, 0x1f, 0xf0, 0x04, 0xd1, 0xb3, 0x78, 0x0b, 0x96, 0xe4, + 0xfc, 0x01, 0x11, 0x5c, 0xac, 0x41, 0xcd, 0x8d, 0xc2, 0x91, 0x3f, 0xd6, 0xe6, 0x5f, 0xbf, 0x89, + 0x2f, 0xa0, 0xc5, 0x4f, 0xcc, 0x03, 0x55, 0xb2, 0x7d, 0x97, 0xb8, 0x79, 0x3e, 0x82, 0x1d, 0xa2, + 0x40, 0x8e, 0xb0, 0xc0, 0xcd, 0x9f, 0x87, 0x7f, 0x5a, 0x82, 0x2a, 0xcd, 0x0c, 0xae, 0x0d, 0x9a, + 0x73, 0x5b, 0x3e, 0x73, 0x02, 0xb3, 0xa4, 0x08, 0xb8, 0xff, 0xcc, 0x09, 0xc4, 0x3a, 0x54, 0x71, + 0x08, 0x6a, 0xce, 0xc4, 0x32, 0x42, 0xdc, 0x80, 0x2a, 0x7e, 0x5d, 0x4d, 0x8f, 0x1e, 0xbf, 0x71, + 0x77, 0xe9, 0xaf, 0xfe, 0xc3, 0xb5, 0x37, 0x2c, 0x46, 0x8b, 0xf7, 0x60, 0xc9, 0x19, 0x8f, 0x15, + 0x09, 0xc2, 0x94, 0x2c, 0xe6, 0x23, 0xb5, 0x88, 0x40, 0x7c, 0x0a, 0x4d, 0x5e, 0x74, 0xa4, 0xae, + 0x12, 0xf5, 0xc5, 0x92, 0x9b, 0x54, 0xe6, 0x07, 0xab, 0xa0, 0xc4, 0xe5, 0xf2, 0x95, 0xd6, 0x40, + 0x24, 0x0e, 0x0d, 0xab, 0x00, 0xa0, 0x1f, 0x13, 0x27, 0x72, 0x3b, 0x08, 0x22, 0xf7, 0xc0, 0x7f, + 0x21, 0xb5, 0xd7, 0x33, 0x05, 0x13, 0x37, 0xa0, 0xbb, 0xcf, 0xfc, 0x6a, 0x49, 0x95, 0x05, 0xa9, + 0xd2, 0x9e, 0xd0, 0x0c, 0x54, 0x6c, 0x82, 0x98, 0x82, 0x1c, 0xd2, 0xef, 0x37, 0xd7, 0x17, 0x37, + 0x3a, 0xd6, 0x1c, 0x8c, 0x78, 0x07, 0x3a, 0x63, 0x9c, 0x69, 0x54, 0x70, 0xa3, 0xc0, 0x41, 0x27, + 0x69, 0x11, 0x9d, 0x28, 0x03, 0x7c, 0x10, 0x38, 0x63, 0x92, 0x90, 0xd8, 0x0f, 0x02, 0x7b, 0x22, + 0x27, 0xa4, 0xfd, 0x16, 0xad, 0x06, 0x01, 0xf6, 0xe4, 0x44, 0xbc, 0x0f, 0xcb, 0x44, 0x6c, 0x1f, + 0x9d, 0x15, 0x2a, 0xb2, 0x4d, 0xda, 0xa1, 0x4b, 0x88, 0xbb, 0x67, 0x5a, 0x47, 0x8a, 0xf7, 0xa1, + 0xef, 0x9d, 0x85, 0xce, 0xc4, 0x77, 0x6d, 0xd3, 0x3f, 0xb9, 0x2e, 0xa8, 0x76, 0x19, 0xfe, 0xb5, + 0x06, 0xa3, 0xe2, 0x91, 0x93, 0x38, 0x3d, 0xcb, 0x09, 0x6d, 0x25, 0x51, 0x42, 0xd1, 0x55, 0x41, + 0x5b, 0xb2, 0x42, 0x58, 0x43, 0x7e, 0x20, 0xd3, 0x5d, 0x4f, 0xa1, 0xfd, 0x3f, 0xdf, 0x88, 0x7c, + 0x93, 0x86, 0xd5, 0x9f, 0x6d, 0x30, 0xfc, 0x17, 0x4b, 0x50, 0xdb, 0x0d, 0x95, 0x4c, 0x52, 0x54, + 0x1c, 0xce, 0x68, 0x24, 0xdd, 0x54, 0xb2, 0xc2, 0x5e, 0xb2, 0xf2, 0x77, 0x5c, 0xbb, 0xc3, 0xe8, + 0xbb, 0xc4, 0x4f, 0xe5, 0xc1, 0xc7, 0x5a, 0x32, 0x0a, 0x00, 0x9a, 0x12, 0xc7, 0xf3, 0x6c, 0x43, + 0x6d, 0x27, 0xd1, 0x73, 0x45, 0x4a, 0xa4, 0x61, 0xf5, 0x1c, 0xcf, 0xdb, 0xd6, 0x70, 0x2b, 0x7a, + 0xae, 0xc4, 0xdb, 0xb0, 0x98, 0xc8, 0x11, 0xc9, 0x49, 0x6b, 0xab, 0xc7, 0xbc, 0xf8, 0xf8, 0xe8, + 0x07, 0xe9, 0xa6, 0x96, 0x1c, 0x59, 0x88, 0x13, 0xab, 0x50, 0x75, 0xd2, 0x34, 0x61, 0xde, 0x6a, + 0x5a, 0xfc, 0x22, 0x36, 0x61, 0x85, 0x94, 0x55, 0xea, 0x47, 0xa1, 0x9d, 0x3a, 0x47, 0x81, 0xa4, + 0x99, 0x60, 0x43, 0xbc, 0x9c, 0xa3, 0x0e, 0x11, 0x83, 0xf3, 0xb0, 0x05, 0x17, 0x66, 0xe9, 0x43, + 0x67, 0x22, 0x15, 0xd9, 0xe1, 0xa6, 0xb5, 0x32, 0xdd, 0xe2, 0x11, 0xa2, 0x90, 0x11, 0x8a, 0x36, + 0xa8, 0xee, 0x1a, 0xa4, 0x39, 0xda, 0x39, 0x10, 0xb5, 0xe1, 0x05, 0xa8, 0xf9, 0xca, 0x96, 0xa1, + 0xa7, 0x35, 0x70, 0xd5, 0x57, 0xf7, 0x43, 0x4f, 0x7c, 0x00, 0x4d, 0xfe, 0x8a, 0x27, 0x47, 0x64, + 0x47, 0x5b, 0x5b, 0x5d, 0x2d, 0x6a, 0x08, 0xbe, 0x27, 0x47, 0x56, 0x23, 0xd5, 0x4f, 0xe8, 0x63, + 0xa5, 0x91, 0x2d, 0x4f, 0x53, 0x99, 0x84, 0x4e, 0xa0, 0x8d, 0x29, 0xa4, 0xd1, 0x7d, 0x0d, 0x11, + 0x9f, 0xc2, 0x45, 0x83, 0xb5, 0x55, 0x3a, 0x49, 0xed, 0x2c, 0xf4, 0x4f, 0xed, 0xd0, 0x09, 0x23, + 0xf2, 0xa0, 0x17, 0xad, 0x55, 0x83, 0x3e, 0x48, 0x27, 0xe9, 0x93, 0xd0, 0x3f, 0x7d, 0xe4, 0x84, + 0x91, 0xd8, 0x80, 0x7e, 0xde, 0x2c, 0x7d, 0x41, 0x3f, 0x4c, 0xcc, 0xd5, 0xb4, 0xba, 0x06, 0x7e, + 0xf8, 0x02, 0xff, 0x95, 0x78, 0xab, 0x44, 0x19, 0x8d, 0x46, 0xc8, 0x5b, 0x4a, 0xba, 0xe4, 0x06, + 0x57, 0xad, 0x95, 0x82, 0xfe, 0x31, 0xe1, 0x0e, 0xa4, 0x3b, 0xfc, 0xcb, 0x0a, 0xb4, 0x48, 0xa0, + 0x9f, 0xc4, 0x1e, 0xea, 0xce, 0x77, 0xa0, 0x33, 0xbd, 0xe8, 0xcc, 0x37, 0x6d, 0xa7, 0xbc, 0xe2, + 0x6b, 0x50, 0xdb, 0x76, 0x71, 0xf2, 0x88, 0x71, 0x3a, 0x96, 0x7e, 0x13, 0xbf, 0x84, 0x5e, 0x46, + 0xdd, 0xd8, 0x6e, 0x7a, 0x6a, 0x07, 0xa8, 0x73, 0x59, 0x43, 0x69, 0xae, 0xe0, 0x6f, 0xec, 0xa4, + 0xa7, 0x56, 0x27, 0x33, 0x8f, 0x0f, 0x51, 0x1b, 0xdf, 0x81, 0xd5, 0x44, 0x22, 0xc7, 0xd8, 0x2f, + 0x64, 0x12, 0xd9, 0xa9, 0x9c, 0xc4, 0x51, 0x42, 0x16, 0x1c, 0x67, 0x51, 0x30, 0xee, 0x7b, 0x99, + 0x44, 0x87, 0x1a, 0x33, 0x7c, 0x13, 0xaa, 0xdb, 0x49, 0xe2, 0x9c, 0x11, 0x6b, 0xe1, 0xc3, 0xa0, + 0x42, 0xb2, 0xc9, 0x2f, 0x43, 0x17, 0x16, 0xf7, 0x9c, 0x58, 0x5c, 0x87, 0x85, 0x49, 0x4c, 0x98, + 0xd6, 0xd6, 0x85, 0x92, 0x42, 0x73, 0xe2, 0xcd, 0xbd, 0xf8, 0x7e, 0x98, 0x26, 0x67, 0xd6, 0xc2, + 0x24, 0xbe, 0xfc, 0x29, 0xd4, 0xf5, 0x2b, 0x86, 0x81, 0x28, 0xe8, 0x15, 0x9a, 0x61, 0x7c, 0xc4, + 0x0f, 0x3c, 0x73, 0x82, 0xcc, 0xb8, 0xae, 0xfc, 0xf2, 0xc5, 0xc2, 0x67, 0x95, 0xe1, 0x7f, 0x5f, + 0x82, 0xc6, 0x3d, 0x19, 0x48, 0xfa, 0xf7, 0x21, 0xb4, 0xcb, 0x52, 0x61, 0xe6, 0x6d, 0x4a, 0x52, + 0x86, 0xd0, 0x66, 0x5f, 0x82, 0x5a, 0x49, 0x2d, 0x76, 0x53, 0x30, 0x34, 0x72, 0xbb, 0xec, 0xa4, + 0x91, 0xbc, 0x75, 0x2c, 0xf3, 0x8a, 0x98, 0x47, 0x1a, 0xb3, 0xc4, 0x18, 0xfd, 0x2a, 0xae, 0x02, + 0x24, 0xd1, 0x73, 0xdb, 0x67, 0x83, 0xce, 0xb6, 0xb1, 0x91, 0x44, 0xcf, 0x77, 0xd1, 0xa4, 0xff, + 0x8d, 0x88, 0xd9, 0x2f, 0x61, 0x50, 0x12, 0x33, 0x0c, 0x15, 0x6c, 0x3f, 0xe4, 0x90, 0x48, 0x4b, + 0x5c, 0xd1, 0x27, 0x45, 0x12, 0xbb, 0x21, 0x45, 0x43, 0x46, 0x79, 0x34, 0x5f, 0xa1, 0x3c, 0xe6, + 0xea, 0x22, 0x98, 0xaf, 0x8b, 0xee, 0x02, 0x1c, 0xc8, 0xf1, 0x44, 0x86, 0xe9, 0x9e, 0x13, 0x0f, + 0x5a, 0xb4, 0xf0, 0xc3, 0x62, 0xe1, 0xcd, 0x6a, 0x6d, 0x16, 0x44, 0xcc, 0x05, 0xa5, 0x56, 0xe8, + 0xe7, 0xb9, 0x4e, 0x68, 0xa7, 0x49, 0x16, 0xba, 0x4e, 0xca, 0xf1, 0x6d, 0xc3, 0x6a, 0xb9, 0x4e, + 0x78, 0xa8, 0x41, 0x25, 0x85, 0xd1, 0x29, 0x2b, 0x8c, 0x1b, 0xd0, 0x8b, 0x13, 0x7f, 0xe2, 0x24, + 0x67, 0x68, 0x2d, 0x68, 0x31, 0x58, 0xf4, 0x3a, 0x1a, 0xfc, 0xad, 0x3c, 0xdb, 0xf5, 0x4e, 0x2f, + 0x7f, 0x09, 0xbd, 0x99, 0x01, 0xfc, 0x24, 0xbe, 0xfb, 0x07, 0x55, 0x68, 0xee, 0x27, 0x52, 0x2b, + 0xf9, 0x6b, 0xd0, 0x52, 0xee, 0xb1, 0x9c, 0x38, 0xac, 0x1b, 0xb8, 0x07, 0x60, 0x10, 0xe9, 0x85, + 0x29, 0x35, 0xb6, 0xf0, 0x1a, 0x35, 0xd6, 0x87, 0x45, 0xf6, 0x17, 0x51, 0x98, 0xf0, 0xb1, 0xd0, + 0xdd, 0x4b, 0x65, 0xdd, 0xbd, 0x0e, 0xed, 0x63, 0x47, 0xd9, 0x4e, 0x96, 0x46, 0xb6, 0x1b, 0x05, + 0xc4, 0x74, 0x0d, 0x0b, 0x8e, 0x1d, 0xb5, 0x9d, 0xa5, 0xd1, 0x4e, 0x14, 0x88, 0x37, 0x01, 0xdc, + 0x28, 0xd0, 0x6a, 0x48, 0x3b, 0xcb, 0x4d, 0x37, 0x0a, 0x58, 0xf7, 0x20, 0x57, 0x4a, 0x95, 0xfa, + 0x13, 0x47, 0x2f, 0xa9, 0x8e, 0xb8, 0xeb, 0xa4, 0x0a, 0x97, 0x73, 0x94, 0x15, 0x3d, 0xe7, 0x50, + 0xfb, 0x0e, 0x74, 0xdd, 0x68, 0x12, 0xdb, 0x31, 0xce, 0x2c, 0xb9, 0x6e, 0x8d, 0x73, 0xd1, 0x50, + 0x1b, 0x29, 0xf6, 0x4f, 0x24, 0x3b, 0x93, 0x5b, 0xd0, 0x73, 0x83, 0x4c, 0xa5, 0x32, 0x41, 0x1b, + 0x2e, 0xe7, 0x07, 0x50, 0x1d, 0x4d, 0xa2, 0x1d, 0xd0, 0x21, 0x74, 0x7c, 0x65, 0x47, 0x81, 0x67, + 0xb3, 0x82, 0xd2, 0x7c, 0xd6, 0xf2, 0xd5, 0xe3, 0xc0, 0xd3, 0x2a, 0x92, 0x69, 0x42, 0xf9, 0xdc, + 0xd0, 0xb4, 0x0c, 0xcd, 0x23, 0xf9, 0x5c, 0xd3, 0xbc, 0x4c, 0xa1, 0xb5, 0x5f, 0xa6, 0xd0, 0x70, + 0x3e, 0x70, 0x42, 0x53, 0x27, 0x19, 0x93, 0xd6, 0x0e, 0x38, 0x0e, 0x62, 0xfe, 0x5a, 0x3e, 0x76, + 0xd4, 0x21, 0x61, 0x0e, 0x34, 0x02, 0xa3, 0x1e, 0x4d, 0x8b, 0x93, 0x17, 0x66, 0x93, 0x23, 0x99, + 0xd0, 0x4a, 0x30, 0xc7, 0x09, 0x46, 0x5a, 0xd1, 0xf3, 0x47, 0x84, 0xc2, 0x15, 0xb9, 0x09, 0xcb, + 0xba, 0x89, 0xe3, 0xa6, 0xfe, 0x33, 0x49, 0xe4, 0x3d, 0x22, 0xef, 0x31, 0x62, 0x9b, 0xe0, 0x48, + 0xfb, 0x7e, 0x4e, 0xab, 0x35, 0x0b, 0xd2, 0xf6, 0x79, 0x4f, 0x20, 0xef, 0x7a, 0xd7, 0xdb, 0x89, + 0x82, 0xe1, 0xbf, 0x5b, 0x80, 0xfa, 0x7e, 0xa4, 0xd2, 0x7b, 0x93, 0xc0, 0x88, 0x73, 0xe5, 0xa7, + 0x8a, 0xf3, 0xc2, 0x7c, 0x71, 0x9e, 0x23, 0x50, 0x8b, 0x73, 0x04, 0x0a, 0x8d, 0x64, 0x99, 0x8e, + 0x04, 0x81, 0xc3, 0x87, 0x6e, 0x41, 0x48, 0xc2, 0x70, 0x05, 0x5d, 0x56, 0xdb, 0x63, 0xfd, 0xcb, + 0x4c, 0xdb, 0xf0, 0x95, 0xd6, 0xbd, 0x8c, 0xf4, 0x49, 0xae, 0xb4, 0x3f, 0xdb, 0xf0, 0x95, 0x96, + 0xb3, 0xcf, 0xe1, 0x52, 0xde, 0xd2, 0x7e, 0xee, 0xa7, 0xc7, 0x51, 0x96, 0xda, 0x23, 0x8a, 0xd5, + 0x95, 0x8e, 0xf6, 0xd6, 0x4c, 0x4f, 0xdf, 0x31, 0x9a, 0x23, 0x79, 0x72, 0xaf, 0x47, 0x59, 0x10, + 0xd8, 0xa9, 0x3c, 0x4d, 0x35, 0xdb, 0x0e, 0x78, 0x6e, 0xf4, 0xbc, 0x3d, 0xc8, 0x82, 0xe0, 0x50, + 0x9e, 0xa6, 0x68, 0x1a, 0x1b, 0x23, 0xfd, 0x32, 0xfc, 0x87, 0x4b, 0x00, 0x0f, 0x23, 0xf7, 0x84, + 0x57, 0x1e, 0x63, 0x48, 0xa3, 0xbd, 0xb5, 0x75, 0xa9, 0xa7, 0xac, 0xb3, 0xc5, 0x16, 0xac, 0x99, + 0xff, 0x47, 0x99, 0xc3, 0x78, 0x96, 0xd5, 0xaf, 0x56, 0x1e, 0x42, 0x63, 0x79, 0x4f, 0x86, 0x74, + 0xaf, 0xf8, 0xac, 0x98, 0x5b, 0x6c, 0x93, 0x9e, 0xc5, 0x34, 0xb7, 0xf3, 0xc2, 0x89, 0x4e, 0xd1, + 0xfc, 0xf0, 0x2c, 0x16, 0x77, 0xe0, 0x42, 0x22, 0x47, 0x89, 0x54, 0xc7, 0x76, 0xaa, 0xca, 0x1f, + 0xe3, 0x50, 0x72, 0x59, 0x23, 0x0f, 0x55, 0xfe, 0xad, 0x3b, 0x70, 0x81, 0x67, 0x6a, 0x76, 0x78, + 0x6c, 0xab, 0x96, 0x19, 0x59, 0x1e, 0xdd, 0x9b, 0x40, 0x1b, 0xc3, 0x6c, 0x7f, 0x4c, 0x6c, 0x11, + 0xd0, 0x64, 0x1c, 0x05, 0x12, 0xbd, 0xd7, 0x9d, 0x63, 0x27, 0x1c, 0xa3, 0xce, 0xd2, 0x93, 0x5f, + 0x00, 0xc4, 0x10, 0x96, 0xf6, 0x22, 0x4f, 0xd2, 0x54, 0x77, 0xb7, 0xba, 0x9b, 0xb4, 0xc5, 0x8c, + 0x33, 0x49, 0x7b, 0x91, 0x84, 0x13, 0xef, 0x01, 0x75, 0xc7, 0xec, 0x77, 0x5e, 0x2f, 0x34, 0x10, + 0x49, 0x3c, 0x78, 0x07, 0x2e, 0x14, 0x23, 0xb1, 0x9d, 0xd4, 0x4e, 0x8f, 0x25, 0xa9, 0x7e, 0x56, + 0x0d, 0xcb, 0xf9, 0xa0, 0xb6, 0xd3, 0xc3, 0x63, 0x89, 0x66, 0x60, 0x03, 0xea, 0xd1, 0xd1, 0x0f, + 0x36, 0x0a, 0x42, 0x6b, 0xbe, 0x20, 0xd4, 0xa2, 0xa3, 0x1f, 0x2c, 0x39, 0x12, 0xbf, 0x28, 0x9b, + 0xcd, 0x99, 0xa9, 0x69, 0xd3, 0xd4, 0xac, 0xe6, 0xf8, 0xd2, 0xec, 0x0c, 0x3f, 0x83, 0x1a, 0xfe, + 0xce, 0xe3, 0x58, 0x6c, 0x42, 0x9d, 0xc5, 0x51, 0x69, 0x37, 0x67, 0xb5, 0xb0, 0x76, 0x05, 0xef, + 0x58, 0x86, 0x68, 0x68, 0x41, 0x2f, 0x37, 0x1d, 0x4f, 0x42, 0xff, 0x69, 0x26, 0xc5, 0x57, 0xb0, + 0x1c, 0x27, 0x52, 0xb3, 0xbd, 0x9d, 0x9d, 0xa0, 0xf3, 0xa6, 0x25, 0x78, 0x55, 0x73, 0x69, 0xde, + 0xe2, 0x04, 0x39, 0xb4, 0x1b, 0x4f, 0xbd, 0x0f, 0xbf, 0x87, 0x8b, 0x39, 0xc5, 0x81, 0x74, 0xa3, + 0xd0, 0x73, 0x92, 0x33, 0xb2, 0xf2, 0x33, 0x7d, 0xab, 0x9f, 0xd2, 0xf7, 0x01, 0xf5, 0xfd, 0x5f, + 0x2b, 0xd0, 0x7a, 0x90, 0xbd, 0x78, 0x71, 0xc6, 0xb2, 0x24, 0xda, 0x50, 0x79, 0x44, 0x1d, 0x2c, + 0x58, 0x95, 0x47, 0xe8, 0x88, 0xee, 0x9f, 0xa0, 0x5c, 0x13, 0x9f, 0x37, 0x2d, 0xfd, 0x86, 0x01, + 0xf2, 0xfe, 0xc9, 0xe1, 0x2b, 0x38, 0x9a, 0xd1, 0x18, 0x20, 0xdd, 0xcd, 0xfc, 0x00, 0xdd, 0x24, + 0xcd, 0xbc, 0xf9, 0x3b, 0x86, 0x9c, 0xbb, 0x23, 0x1e, 0xca, 0x83, 0x24, 0x9a, 0xf0, 0x64, 0x69, + 0x95, 0x31, 0x07, 0x23, 0xbe, 0x86, 0x15, 0xbd, 0x81, 0xa7, 0xb5, 0x82, 0xad, 0x62, 0xe9, 0x12, + 0xeb, 0xfe, 0xa4, 0x4d, 0xbf, 0xe1, 0xdf, 0xad, 0x41, 0x03, 0x43, 0xcb, 0xdf, 0x46, 0x7e, 0x28, + 0xee, 0x40, 0xf3, 0x87, 0xc8, 0x0f, 0x79, 0xb7, 0x81, 0x93, 0x1c, 0x2b, 0xdc, 0xd7, 0xa3, 0xc8, + 0x93, 0x9b, 0x48, 0x43, 0xfb, 0x0c, 0x8d, 0x1f, 0xf4, 0x93, 0x36, 0x4f, 0x89, 0x3f, 0x3e, 0x4e, + 0x6d, 0x04, 0x6a, 0xdd, 0xda, 0xf2, 0x95, 0x85, 0x30, 0xea, 0xf5, 0x2a, 0x00, 0xc5, 0xb4, 0x51, + 0x68, 0xc7, 0x27, 0x3a, 0xae, 0x6b, 0x20, 0xe4, 0x71, 0xb8, 0x7f, 0x82, 0xb2, 0xe7, 0x2b, 0x5b, + 0xef, 0x6b, 0x69, 0x1f, 0xbc, 0x14, 0xd7, 0xbf, 0x0b, 0x5d, 0xf4, 0x8f, 0xd4, 0x89, 0x1f, 0xdb, + 0x71, 0x12, 0x1d, 0x99, 0x49, 0x41, 0xaf, 0xe9, 0xe0, 0xc4, 0x8f, 0xf7, 0x11, 0x46, 0x6e, 0x89, + 0xde, 0x2d, 0x43, 0xb5, 0xcd, 0xf6, 0x1f, 0x34, 0x08, 0xe7, 0x97, 0xb6, 0xc4, 0x02, 0x8e, 0x12, + 0xea, 0xe4, 0x6e, 0xd4, 0x13, 0x19, 0x50, 0x38, 0x70, 0x09, 0x1a, 0x28, 0x0c, 0x84, 0x6a, 0x30, + 0xca, 0x8d, 0x18, 0xf5, 0x3e, 0x40, 0x20, 0x47, 0xa9, 0x8d, 0x5c, 0xc6, 0x1b, 0x00, 0x33, 0x5b, + 0x4f, 0x88, 0xdd, 0x41, 0xa4, 0xf8, 0x00, 0x5a, 0x3c, 0x0b, 0x4c, 0x0b, 0xe7, 0x68, 0x81, 0xd0, + 0x4c, 0x7c, 0x13, 0x5a, 0x61, 0x14, 0xda, 0xf2, 0x29, 0x51, 0x6b, 0xb9, 0x9d, 0xea, 0x38, 0x8c, + 0xc2, 0xfb, 0x4f, 0x91, 0x58, 0xdc, 0xd6, 0x63, 0xe0, 0x3d, 0x98, 0xf6, 0x4b, 0xf6, 0x60, 0x68, + 0x24, 0xbc, 0x1b, 0xf1, 0x91, 0x19, 0x09, 0xb7, 0xe8, 0xbc, 0xa4, 0x05, 0x8f, 0x87, 0x9b, 0xac, + 0x43, 0x9b, 0xd6, 0x7d, 0xe2, 0xc4, 0x76, 0xea, 0x8c, 0xb5, 0x55, 0x07, 0x84, 0xed, 0x39, 0xf1, + 0xa1, 0x33, 0x16, 0x16, 0x5c, 0x9a, 0xe1, 0xb7, 0x23, 0x64, 0x5d, 0x9e, 0xb5, 0x9e, 0xd9, 0xc3, + 0x99, 0xcf, 0x75, 0x6b, 0x53, 0x5c, 0x47, 0x2c, 0x4f, 0xb3, 0xfb, 0x39, 0x5c, 0x92, 0x13, 0xca, + 0x7e, 0x4c, 0xe2, 0x44, 0x2a, 0x35, 0xe5, 0x9a, 0xf5, 0xd9, 0xc6, 0x21, 0xc1, 0x4e, 0x8e, 0xcf, + 0xfd, 0xb3, 0x77, 0xa1, 0xeb, 0xa8, 0x68, 0x64, 0x9b, 0x29, 0x0f, 0x28, 0x97, 0x51, 0xb5, 0xda, + 0x08, 0xb5, 0x78, 0xa2, 0x03, 0x34, 0xe8, 0x44, 0xa5, 0x87, 0x2a, 0x47, 0x29, 0xe5, 0x31, 0x1a, + 0x56, 0x07, 0xc1, 0x3c, 0x10, 0x39, 0x4a, 0x87, 0xff, 0x74, 0x01, 0x1a, 0x0f, 0xa3, 0x28, 0xfe, + 0x99, 0x32, 0x50, 0xe6, 0xad, 0x85, 0x97, 0xf3, 0xd6, 0xe2, 0x34, 0x6f, 0xcd, 0xf0, 0xc0, 0xd2, + 0x8f, 0xe7, 0x81, 0xea, 0x4f, 0xe6, 0x81, 0xda, 0xcf, 0xe0, 0x81, 0xfa, 0x2c, 0x0f, 0x0c, 0xef, + 0x40, 0xf5, 0x40, 0xa6, 0x8f, 0x63, 0xb4, 0x66, 0xc6, 0x2f, 0x36, 0x86, 0x60, 0xca, 0x9a, 0x69, + 0x9f, 0x58, 0x0d, 0xff, 0xba, 0x05, 0xcd, 0x7b, 0xd2, 0xcb, 0x78, 0x66, 0xcb, 0xf3, 0x54, 0x79, + 0xf9, 0x3c, 0x2d, 0x4c, 0xcf, 0x13, 0x9a, 0x4e, 0x23, 0x83, 0x73, 0x36, 0x50, 0x1b, 0x46, 0x04, + 0x51, 0x58, 0x0b, 0x09, 0xd4, 0xbb, 0x90, 0x53, 0xf3, 0x99, 0x0b, 0xe0, 0xab, 0xb9, 0xb9, 0xfa, + 0xf3, 0xb8, 0x79, 0x5a, 0x8f, 0x9d, 0xdb, 0x9f, 0x7c, 0xed, 0xf4, 0xce, 0xea, 0xb0, 0xc6, 0x39, + 0x1d, 0xf6, 0x10, 0x56, 0xa2, 0xd0, 0xf6, 0xb2, 0x38, 0xf0, 0x31, 0x2e, 0x24, 0xbf, 0x3a, 0x0a, + 0xc9, 0x9d, 0xa0, 0x8c, 0x68, 0xce, 0xa3, 0x8f, 0xc3, 0x7b, 0x86, 0x88, 0xf7, 0x4a, 0xac, 0xe5, + 0x68, 0x16, 0x84, 0x22, 0xe4, 0xe1, 0xd2, 0x90, 0x27, 0x40, 0x3e, 0x2c, 0xa7, 0x76, 0xdb, 0x04, + 0xdd, 0x89, 0x02, 0xb2, 0x6d, 0x9f, 0x41, 0xaf, 0xa0, 0x62, 0x66, 0x6a, 0xbd, 0x84, 0x99, 0x3a, + 0xa6, 0x21, 0xf3, 0xd3, 0xdf, 0x84, 0xde, 0xfa, 0x10, 0x56, 0xcc, 0x16, 0x90, 0x76, 0x67, 0x68, + 0x05, 0xbb, 0xc4, 0x41, 0x7d, 0xbd, 0xeb, 0x43, 0x9e, 0x0c, 0x2d, 0xd1, 0xaf, 0x60, 0xb5, 0x44, + 0x8e, 0xec, 0x5b, 0xd6, 0x5f, 0x65, 0x5e, 0x59, 0xce, 0xdb, 0xe2, 0xeb, 0x43, 0xde, 0xc3, 0x6f, + 0x79, 0x32, 0x30, 0x1f, 0xd2, 0xd1, 0x49, 0xd3, 0x93, 0x81, 0x4e, 0x3d, 0xee, 0xc1, 0xbb, 0x18, + 0xc9, 0x21, 0xde, 0x75, 0xe2, 0x34, 0x4b, 0xa4, 0x1d, 0x07, 0x8e, 0x2b, 0x8f, 0xa3, 0xc0, 0x93, + 0x49, 0x31, 0xb8, 0x65, 0x1a, 0xdc, 0xb5, 0x28, 0xc0, 0x70, 0x66, 0x87, 0x29, 0xf7, 0x0b, 0x42, + 0x33, 0xd6, 0x6d, 0x78, 0xeb, 0x5c, 0x77, 0x68, 0xea, 0x8a, 0x8e, 0x04, 0x75, 0x74, 0x69, 0xba, + 0x23, 0x24, 0x31, 0x5d, 0x7c, 0x04, 0x17, 0x78, 0xed, 0x98, 0xb9, 0x4f, 0xa4, 0x8c, 0xed, 0xc0, + 0x51, 0xe9, 0x60, 0x85, 0xdd, 0x0a, 0x42, 0x12, 0x03, 0x7f, 0x2b, 0x65, 0xfc, 0xd0, 0xe1, 0xaf, + 0x72, 0x13, 0x1d, 0x79, 0x50, 0x9b, 0xa9, 0xb9, 0x5d, 0xe5, 0xaf, 0x12, 0x15, 0x87, 0x1f, 0xd8, + 0xb8, 0x34, 0xc9, 0xbf, 0x86, 0x2b, 0x53, 0x5d, 0x4c, 0x9c, 0xe4, 0xa4, 0x70, 0xc5, 0x07, 0x17, + 0x68, 0xde, 0x2e, 0x96, 0xda, 0xef, 0x11, 0x81, 0x9e, 0xc5, 0x8f, 0x61, 0x0d, 0x03, 0xd3, 0xc8, + 0x3b, 0xc9, 0x66, 0x82, 0xb6, 0x35, 0x1a, 0x34, 0x86, 0xad, 0x8f, 0xbd, 0x93, 0x6c, 0x2a, 0x70, + 0xfb, 0x25, 0x0c, 0xa6, 0x68, 0xed, 0x84, 0x36, 0xe7, 0xed, 0x38, 0x52, 0x83, 0x8b, 0xbc, 0x1f, + 0x54, 0xde, 0x51, 0xe4, 0xad, 0xfb, 0xfd, 0x48, 0x89, 0x07, 0xb0, 0x1e, 0x1f, 0x9f, 0x29, 0x9f, + 0x92, 0x89, 0xe4, 0xd0, 0x9f, 0xef, 0x60, 0x40, 0x1d, 0x5c, 0x35, 0x74, 0xec, 0xf7, 0xcf, 0xf4, + 0xf3, 0x19, 0x5c, 0x32, 0x8c, 0x75, 0x2c, 0xdd, 0x93, 0xe9, 0x19, 0xbb, 0x44, 0x33, 0x76, 0x41, + 0x73, 0x14, 0xe2, 0x4b, 0xb3, 0xb5, 0x01, 0x7d, 0xb2, 0x77, 0xf6, 0x28, 0xca, 0x42, 0xfd, 0xa7, + 0x97, 0xe9, 0x4f, 0xbb, 0x04, 0x7f, 0x80, 0x60, 0xfa, 0xc9, 0x0d, 0xe8, 0x93, 0xb5, 0x64, 0xa9, + 0x67, 0xca, 0x2b, 0x4c, 0x89, 0x70, 0x2d, 0xe8, 0x48, 0xf9, 0x29, 0x5c, 0xd4, 0x44, 0x23, 0x3f, + 0x74, 0x82, 0xf2, 0xcf, 0x5c, 0x65, 0x37, 0x9f, 0xd1, 0x0f, 0x10, 0x5b, 0xfc, 0xc4, 0xef, 0x40, + 0x8c, 0xa2, 0x44, 0xfa, 0xe3, 0x90, 0xc2, 0x5a, 0xfa, 0x13, 0x35, 0x78, 0x93, 0x64, 0xe3, 0xcd, + 0xc2, 0xcf, 0x7f, 0x7c, 0xef, 0xdb, 0x27, 0x0f, 0x98, 0xee, 0x5b, 0x79, 0x46, 0xff, 0xa3, 0x25, + 0xb3, 0x3f, 0x9a, 0x06, 0xab, 0xe1, 0x3f, 0xaa, 0x41, 0x97, 0xac, 0xf1, 0xdf, 0x1a, 0x81, 0xbf, + 0x35, 0x02, 0xff, 0x2f, 0x18, 0x81, 0x9b, 0xb0, 0xec, 0x87, 0x71, 0x96, 0xa2, 0x04, 0x29, 0x3b, + 0xe3, 0x28, 0x6e, 0x99, 0xb7, 0x9b, 0x08, 0xf1, 0xad, 0x3c, 0x53, 0x1c, 0xc2, 0x0d, 0xff, 0x4e, + 0x05, 0xea, 0xfb, 0x49, 0xe4, 0x65, 0x6e, 0xfa, 0x33, 0xa5, 0x62, 0x9a, 0xdb, 0x16, 0x5f, 0xc7, + 0x6d, 0x4b, 0xe7, 0x3c, 0xba, 0x7f, 0x5e, 0x81, 0xa6, 0x1e, 0xc2, 0xc3, 0xad, 0x9f, 0x39, 0x88, + 0x22, 0xb7, 0x5d, 0x99, 0x9b, 0xdb, 0x7e, 0xed, 0x28, 0x90, 0x09, 0x9f, 0x71, 0x31, 0x50, 0x14, + 0x17, 0x89, 0xee, 0xa6, 0xd5, 0x66, 0xe8, 0xe3, 0x98, 0xf2, 0xd9, 0xcf, 0xa1, 0x49, 0x51, 0x3e, + 0x69, 0x91, 0x35, 0xa8, 0xb1, 0x56, 0xd3, 0x03, 0xd5, 0x6f, 0xaf, 0x96, 0xe9, 0x85, 0x9f, 0x25, + 0xd3, 0xc3, 0x7f, 0xb3, 0x08, 0x1d, 0xda, 0x72, 0x79, 0x90, 0x85, 0x2c, 0x35, 0xf9, 0x26, 0x75, + 0x65, 0x7a, 0x93, 0x7a, 0x29, 0x91, 0xa9, 0x49, 0xa4, 0xb7, 0xf9, 0x33, 0x3b, 0x51, 0x70, 0x4f, + 0x8e, 0x2c, 0xc2, 0xe0, 0x54, 0x39, 0xc9, 0x58, 0xcd, 0x2b, 0x03, 0x40, 0x38, 0xfe, 0x55, 0xec, + 0x24, 0xce, 0x44, 0x99, 0x32, 0x00, 0x7e, 0x13, 0x02, 0x96, 0x48, 0x36, 0x79, 0x5a, 0xe8, 0x59, + 0xef, 0x1e, 0x2a, 0x3f, 0x1c, 0xe7, 0x8a, 0xa6, 0x41, 0xe5, 0x1f, 0xe3, 0x40, 0x8a, 0x7b, 0x20, + 0x38, 0x2d, 0x92, 0x48, 0x07, 0x9d, 0x0f, 0xea, 0x87, 0xb4, 0x4d, 0x6b, 0x6b, 0x8d, 0x3f, 0x4b, + 0x73, 0x69, 0x11, 0x7a, 0x1f, 0xb1, 0x56, 0xdf, 0x9f, 0x81, 0xcc, 0x99, 0x4c, 0xf6, 0x40, 0xf2, + 0x48, 0xf9, 0x47, 0x4f, 0x26, 0xb9, 0x25, 0xc4, 0x2d, 0x5f, 0xc1, 0xca, 0x28, 0x0b, 0x82, 0x54, + 0x9e, 0xa6, 0xb6, 0x8a, 0xb2, 0xc4, 0x95, 0xf6, 0x2b, 0x32, 0x32, 0xcb, 0x86, 0xf6, 0x80, 0x48, + 0x2d, 0x39, 0x12, 0x5f, 0x82, 0xc8, 0x3b, 0x30, 0xff, 0x68, 0xf2, 0xa5, 0xe7, 0xda, 0xf7, 0x0d, + 0xa9, 0xfe, 0xdb, 0xd1, 0x70, 0x1b, 0x2e, 0x98, 0x1c, 0x29, 0xaa, 0xb6, 0x2d, 0x94, 0x5b, 0xda, + 0x3b, 0x32, 0x73, 0x5c, 0x29, 0xcd, 0xf1, 0x2a, 0x54, 0xcb, 0xe5, 0x69, 0xfc, 0x32, 0xbc, 0x0e, + 0xad, 0x91, 0x1f, 0x48, 0x9d, 0x6b, 0xc0, 0x45, 0xd3, 0x59, 0x87, 0x0a, 0x25, 0xd5, 0xf5, 0xdb, + 0xf0, 0x2f, 0x2b, 0x70, 0x31, 0x76, 0x92, 0xa7, 0x99, 0xde, 0xd5, 0xe6, 0xfc, 0xbe, 0x3a, 0x76, + 0x12, 0x0f, 0x05, 0x97, 0xba, 0xe0, 0xde, 0xb9, 0xba, 0xa9, 0x89, 0x10, 0x1e, 0xcb, 0x0d, 0xe8, + 0x95, 0x5a, 0xa4, 0x4e, 0x62, 0x76, 0x56, 0x3b, 0x49, 0xf4, 0x9c, 0xb2, 0xef, 0x07, 0x08, 0x14, + 0x43, 0xe8, 0x14, 0x74, 0x92, 0x2c, 0x23, 0x15, 0x09, 0x19, 0xaa, 0xfb, 0xa1, 0x87, 0x92, 0x1b, + 0x66, 0x13, 0x76, 0x17, 0xb8, 0x88, 0xad, 0x1e, 0x66, 0x13, 0xf2, 0x13, 0x56, 0xa1, 0xca, 0x95, + 0x83, 0x55, 0x82, 0xf3, 0xcb, 0xf0, 0x0f, 0x55, 0x58, 0xd9, 0x75, 0xe5, 0x91, 0x4c, 0xc6, 0xf7, + 0x9c, 0xd4, 0x79, 0xe0, 0x07, 0xf2, 0xd0, 0x51, 0x27, 0xc8, 0x70, 0x34, 0xe6, 0xd8, 0x49, 0x8f, + 0xf5, 0x2c, 0x35, 0x10, 0xb0, 0xef, 0xa4, 0xc7, 0x68, 0xb6, 0x08, 0x39, 0x8a, 0x92, 0x89, 0xde, + 0x07, 0x6e, 0x5a, 0xf4, 0x8f, 0x0f, 0x08, 0x92, 0xb7, 0x56, 0xfe, 0x0b, 0xa9, 0x4b, 0xee, 0xa8, + 0x35, 0xd5, 0x65, 0xbc, 0x0d, 0xed, 0x44, 0xba, 0x51, 0xe2, 0xe9, 0xd8, 0x9f, 0xc7, 0xd9, 0x62, + 0x18, 0x07, 0xfc, 0x37, 0xa1, 0xc8, 0x1d, 0xd2, 0x56, 0x97, 0xed, 0x9b, 0xba, 0x9c, 0x5e, 0x8e, + 0x40, 0xce, 0xdb, 0xf5, 0xc4, 0xff, 0x07, 0xfd, 0x82, 0x96, 0x12, 0x59, 0x26, 0x02, 0xde, 0x2a, + 0xdc, 0x98, 0x39, 0xbf, 0xb8, 0xb9, 0x6f, 0x5a, 0xfd, 0x39, 0x35, 0xe2, 0x64, 0x5d, 0xd1, 0x3d, + 0x43, 0xc5, 0x3b, 0xd0, 0x51, 0x71, 0xe0, 0xa7, 0x9a, 0x01, 0x94, 0x2e, 0xcc, 0x6b, 0x13, 0x90, + 0xf3, 0x4d, 0x6a, 0xde, 0x12, 0x36, 0x7e, 0xd4, 0x12, 0x36, 0xcf, 0x2f, 0xe1, 0xfb, 0xd0, 0x77, + 0x13, 0xe9, 0xc9, 0x30, 0xf5, 0x9d, 0xc0, 0x56, 0x6e, 0x14, 0x1b, 0x33, 0xdd, 0x2b, 0xe0, 0x07, + 0x08, 0x16, 0xbf, 0x80, 0x8b, 0x6e, 0x14, 0xa6, 0x32, 0x4c, 0xf3, 0xd2, 0x4d, 0x9d, 0xad, 0xd1, + 0x25, 0x27, 0x17, 0x34, 0xda, 0x14, 0x70, 0x72, 0xbe, 0x46, 0xdc, 0x81, 0x55, 0x5e, 0x9e, 0x99, + 0x46, 0x5c, 0x2b, 0x20, 0x68, 0xa5, 0xa6, 0x5b, 0xe8, 0x0c, 0x52, 0x22, 0x95, 0xef, 0x65, 0x4e, + 0xa0, 0x35, 0x44, 0x29, 0x83, 0x64, 0x69, 0x8c, 0xde, 0x4a, 0xa5, 0x1c, 0xd5, 0x14, 0x2d, 0x15, + 0xba, 0xd0, 0x56, 0x53, 0xd3, 0x12, 0xc9, 0x14, 0xf5, 0x37, 0x8e, 0x3a, 0xbe, 0x7c, 0x17, 0x56, + 0xe7, 0x2d, 0xc8, 0xeb, 0x92, 0x97, 0xcd, 0x52, 0xf2, 0x52, 0x97, 0xa7, 0xfe, 0xb7, 0x05, 0xb8, + 0x60, 0xd6, 0x9b, 0x42, 0x8e, 0x9c, 0xa9, 0xaf, 0x91, 0x3d, 0xc7, 0x30, 0x25, 0xdf, 0xee, 0x69, + 0x5a, 0xc0, 0x20, 0xda, 0xdb, 0xd9, 0x80, 0xbe, 0x26, 0x28, 0x98, 0x9f, 0xbf, 0xd2, 0xf5, 0xf2, + 0xae, 0x48, 0x04, 0xe8, 0x07, 0x47, 0x32, 0xc1, 0x39, 0xf2, 0xa8, 0x5a, 0x9b, 0x9a, 0x10, 0xb3, + 0xd3, 0x0f, 0x1a, 0x9c, 0x61, 0x39, 0xaa, 0xb4, 0x79, 0x9a, 0x39, 0x81, 0x9f, 0x9e, 0xd9, 0x23, + 0x5f, 0x06, 0x1e, 0x65, 0xca, 0xb9, 0x8e, 0xb0, 0x6f, 0x30, 0x0f, 0x10, 0xb1, 0xeb, 0xa9, 0xd2, + 0x48, 0x74, 0x02, 0x36, 0x17, 0x00, 0x3d, 0x92, 0x03, 0x02, 0xef, 0x7a, 0xf3, 0x65, 0xa5, 0x36, + 0x5f, 0x56, 0xde, 0x83, 0xde, 0xec, 0x9a, 0x73, 0x52, 0xb4, 0xab, 0xa6, 0xd7, 0x7b, 0x1e, 0x13, + 0x36, 0xe6, 0x32, 0xa1, 0x9e, 0xf4, 0xff, 0xb1, 0x00, 0xab, 0x7a, 0xd2, 0x77, 0xa2, 0x20, 0x9b, + 0xa0, 0xb5, 0xa7, 0xaa, 0xa4, 0x75, 0x68, 0x4f, 0x22, 0x76, 0xa1, 0x4a, 0xea, 0x0f, 0x26, 0x51, + 0xae, 0x8b, 0x37, 0xa0, 0xef, 0x73, 0xcb, 0x7c, 0x5e, 0x4c, 0x85, 0xb0, 0x86, 0xeb, 0x59, 0x41, + 0x2e, 0x54, 0xa1, 0x13, 0xab, 0xe3, 0x28, 0xd5, 0xa4, 0xa4, 0xc4, 0x79, 0xce, 0x97, 0x0d, 0x8a, + 0xa8, 0xc9, 0x93, 0xbd, 0x05, 0xc2, 0xcd, 0x92, 0x04, 0xe5, 0xa3, 0x44, 0xce, 0xc9, 0xbb, 0xbe, + 0xc6, 0x14, 0xd4, 0xef, 0x40, 0x7d, 0x12, 0x15, 0x1e, 0xc9, 0x94, 0x23, 0x6a, 0xd5, 0x26, 0x11, + 0x71, 0xc8, 0x65, 0xf4, 0x9a, 0x9e, 0x66, 0x7e, 0x22, 0x3d, 0x63, 0x87, 0xcd, 0xbb, 0x36, 0xd2, + 0xc7, 0xbe, 0xe7, 0xc9, 0x50, 0x27, 0x8e, 0x1a, 0xbe, 0xfa, 0x86, 0xde, 0xa9, 0x80, 0x56, 0x8e, + 0x1c, 0x0c, 0xcd, 0xc2, 0x2c, 0x20, 0xa9, 0x08, 0x74, 0xb9, 0x66, 0x4f, 0x23, 0x1e, 0x65, 0x01, + 0x4a, 0x44, 0xa0, 0x97, 0x94, 0x6c, 0x09, 0xb2, 0xa0, 0x7d, 0xec, 0x87, 0x29, 0xa9, 0x8a, 0x26, + 0x2d, 0x29, 0x22, 0x90, 0x09, 0xbf, 0xf1, 0xc3, 0x74, 0xf8, 0xcf, 0x16, 0x60, 0x4d, 0x4f, 0xfc, + 0x81, 0x9e, 0x00, 0x6d, 0x9f, 0x29, 0xba, 0x30, 0xd3, 0xa5, 0xf3, 0x7a, 0x8b, 0x16, 0x18, 0xd0, + 0x2e, 0x0d, 0xb8, 0xe0, 0xae, 0x05, 0x5d, 0xc6, 0x69, 0xf8, 0xea, 0x16, 0x88, 0x73, 0x7c, 0xa5, + 0xf4, 0xb6, 0x66, 0x7f, 0x86, 0xb1, 0x94, 0xf8, 0x04, 0xd6, 0x26, 0x32, 0x75, 0x48, 0x10, 0x82, + 0xc8, 0x75, 0xa8, 0x15, 0x89, 0x3c, 0x4f, 0xf7, 0xaa, 0xc1, 0x3e, 0xd4, 0x48, 0x14, 0x7a, 0xfc, + 0xc6, 0xc4, 0x09, 0xfd, 0x91, 0x54, 0x29, 0xf9, 0x19, 0xdc, 0x82, 0x1d, 0x9f, 0xbe, 0xc1, 0xa0, + 0x27, 0x41, 0xd4, 0xe4, 0xb1, 0x8e, 0x78, 0x11, 0x6b, 0x44, 0x53, 0x4f, 0xe4, 0x48, 0xaf, 0x5d, + 0x07, 0xd7, 0x2a, 0xf4, 0xc3, 0x31, 0x1f, 0x1c, 0xa8, 0xb3, 0x4f, 0x69, 0x80, 0x7b, 0x91, 0x27, + 0x87, 0x7f, 0xb1, 0x94, 0xf3, 0xe8, 0xbe, 0x86, 0x1f, 0xa4, 0x4e, 0x4a, 0xb5, 0xf2, 0xf9, 0xe0, + 0xd9, 0x46, 0xf2, 0x5c, 0x75, 0x0c, 0x94, 0x4b, 0xea, 0x37, 0x61, 0x65, 0x7a, 0xb4, 0x4c, 0xbb, + 0xc0, 0x65, 0x05, 0xe5, 0xe1, 0xe6, 0x25, 0xf8, 0x39, 0x3d, 0x93, 0xea, 0xea, 0x73, 0x03, 0x65, + 0xb2, 0x0f, 0x8b, 0x49, 0x50, 0x3a, 0x39, 0x2f, 0x3d, 0x6d, 0x15, 0xf3, 0x5e, 0xd5, 0x81, 0x46, + 0xa0, 0x68, 0x16, 0xe4, 0x71, 0x92, 0x85, 0xd2, 0xd3, 0x26, 0xbd, 0x97, 0xc3, 0xf7, 0x09, 0x8c, + 0x03, 0xce, 0x35, 0x53, 0xa9, 0xeb, 0x1a, 0x77, 0xed, 0x69, 0xcd, 0x54, 0x74, 0x8d, 0x3c, 0x5a, + 0xd0, 0xeb, 0xbe, 0x59, 0x41, 0xf4, 0x72, 0x6a, 0xdd, 0xf7, 0x2f, 0x61, 0x90, 0xd3, 0xf2, 0xdf, + 0x15, 0x1f, 0x68, 0xb0, 0xf1, 0x31, 0x4d, 0xe8, 0x37, 0xf3, 0x8f, 0x7c, 0x0c, 0x6b, 0xb3, 0x0d, + 0xf5, 0x97, 0x9a, 0xd4, 0x6c, 0x65, 0xaa, 0x59, 0xf1, 0x27, 0xf9, 0xfa, 0xba, 0x8e, 0x7b, 0x2c, + 0xed, 0x63, 0x5f, 0x17, 0xa0, 0x2f, 0x5a, 0xcb, 0x06, 0xb5, 0x83, 0x98, 0x6f, 0xfc, 0x54, 0xcd, + 0xa1, 0x9f, 0xf8, 0x4a, 0x69, 0xab, 0x38, 0x4d, 0xbf, 0xe7, 0x2b, 0x35, 0xfc, 0x2f, 0x2d, 0x68, + 0x1b, 0x4f, 0x91, 0x6a, 0x9f, 0x6f, 0x95, 0x9d, 0xfe, 0xd6, 0x56, 0xdf, 0x78, 0xef, 0x48, 0xb2, + 0x9d, 0xa6, 0x89, 0xc9, 0xf5, 0x71, 0x30, 0x30, 0xe5, 0xef, 0x2c, 0x90, 0x83, 0x50, 0xf8, 0x3b, + 0xdb, 0xb0, 0x5c, 0xf2, 0x20, 0xed, 0x34, 0x4a, 0x9d, 0x40, 0x07, 0x05, 0xa5, 0xba, 0xb1, 0x12, + 0x89, 0xd5, 0xc3, 0x17, 0xf6, 0x2d, 0x0e, 0x91, 0x1a, 0x83, 0x0d, 0x37, 0x0a, 0x4c, 0xb1, 0xed, + 0x4c, 0xb0, 0x81, 0x18, 0xaa, 0x88, 0x49, 0x24, 0xc6, 0xb9, 0xea, 0x69, 0xa0, 0x25, 0xa8, 0xc9, + 0x90, 0x83, 0xa7, 0x41, 0x3e, 0x40, 0x72, 0xe6, 0x6b, 0x14, 0xc7, 0xd0, 0x00, 0xc9, 0x4b, 0xff, + 0x10, 0x5a, 0x51, 0xe2, 0x8f, 0x7d, 0x4a, 0x13, 0xb3, 0x83, 0x33, 0xfb, 0x11, 0x60, 0x82, 0x1d, + 0xfc, 0xd4, 0x10, 0x6a, 0xda, 0xfc, 0x9f, 0xaf, 0x92, 0xd1, 0x18, 0x73, 0x04, 0xc0, 0x4d, 0x71, + 0x38, 0x2c, 0x91, 0xcd, 0xe2, 0x08, 0x80, 0x9b, 0x1e, 0x3c, 0x0d, 0x28, 0x53, 0x7e, 0x03, 0x7a, + 0x2e, 0x99, 0x0b, 0x16, 0xa8, 0x40, 0x86, 0xb4, 0xa6, 0x55, 0xab, 0xc3, 0x60, 0x1c, 0xdf, 0x43, + 0x19, 0xea, 0x52, 0x4b, 0x27, 0x08, 0x30, 0x62, 0x8d, 0x1c, 0x4f, 0xd7, 0xc5, 0xb4, 0x0d, 0xf0, + 0x61, 0xe4, 0x78, 0xe2, 0x0b, 0xb8, 0x8c, 0x38, 0x9b, 0x0b, 0x5a, 0xc3, 0x6c, 0x22, 0x13, 0xdf, + 0xb5, 0x1d, 0x45, 0x75, 0x32, 0xba, 0x3c, 0x66, 0x0d, 0x29, 0xee, 0x23, 0xc1, 0x23, 0xc6, 0x6f, + 0xab, 0xef, 0x65, 0x12, 0x89, 0xef, 0x29, 0x5b, 0x3e, 0xcf, 0x7d, 0x37, 0xdb, 0x12, 0x6f, 0x17, + 0x6b, 0xf5, 0x12, 0x4a, 0xaa, 0x43, 0x43, 0x84, 0x65, 0x9c, 0x3e, 0x6a, 0x2f, 0xbe, 0x05, 0x61, + 0x0c, 0x1c, 0x71, 0x7e, 0xea, 0xa8, 0x13, 0x2e, 0xca, 0x9d, 0xda, 0x6a, 0x9b, 0xe3, 0xa3, 0x5a, + 0xc6, 0x32, 0x22, 0x10, 0x01, 0x4a, 0xfc, 0x0e, 0x56, 0xf3, 0xce, 0xb4, 0x2f, 0x43, 0xdd, 0xf1, + 0x86, 0xc6, 0xb5, 0xf3, 0xdd, 0x4d, 0xb9, 0x40, 0x96, 0x19, 0x09, 0x83, 0xb9, 0xcb, 0xaf, 0xa1, + 0x67, 0xba, 0xe4, 0x59, 0x57, 0x83, 0x3e, 0xf5, 0xf6, 0xd6, 0xb9, 0xde, 0xa6, 0x6c, 0x7b, 0x6e, + 0x9f, 0x19, 0x8a, 0x3f, 0x9a, 0x5b, 0x72, 0x63, 0x65, 0x68, 0x3b, 0xa4, 0xb5, 0xb5, 0x7e, 0xae, + 0xa7, 0x19, 0x63, 0x65, 0x99, 0x21, 0x18, 0xb8, 0xf8, 0x08, 0x2e, 0x98, 0xce, 0x22, 0x0a, 0xf1, + 0x6c, 0x3f, 0xa2, 0xe8, 0x4f, 0xb0, 0x8b, 0xa5, 0x91, 0x1c, 0xfe, 0xed, 0x46, 0x1c, 0x2d, 0x5e, + 0x31, 0x4d, 0xd8, 0x0a, 0x53, 0x44, 0x9c, 0xff, 0xd4, 0x0a, 0xd9, 0xae, 0x81, 0x26, 0x61, 0xbb, + 0x8c, 0x11, 0xb0, 0x19, 0xfe, 0x06, 0xf4, 0xa9, 0x72, 0x1e, 0x97, 0x35, 0x4a, 0x3c, 0x3f, 0x74, + 0x82, 0xc1, 0x2a, 0xef, 0xb9, 0x22, 0xdc, 0x8a, 0x9e, 0x3f, 0x66, 0xa8, 0x38, 0x84, 0x35, 0xf3, + 0xa1, 0x5c, 0xcd, 0x28, 0x34, 0x25, 0xb4, 0xe1, 0x3d, 0x6f, 0xe2, 0xa6, 0x0c, 0x8e, 0x65, 0x96, + 0x70, 0xda, 0x0c, 0xdd, 0x83, 0x6b, 0x33, 0x4b, 0x3b, 0x71, 0x4e, 0xed, 0x89, 0x9c, 0x44, 0xc9, + 0x99, 0x36, 0x20, 0x6b, 0xa4, 0xc0, 0xae, 0x4c, 0x2d, 0xe2, 0x9e, 0x73, 0xba, 0x47, 0x34, 0x6c, + 0x4e, 0xbe, 0x82, 0xab, 0x33, 0xbd, 0x70, 0x21, 0xba, 0x0c, 0x9d, 0xa3, 0x40, 0x7a, 0xb4, 0x45, + 0xde, 0xb0, 0x2e, 0x4d, 0x75, 0x71, 0x80, 0x14, 0xf7, 0x99, 0x40, 0x7c, 0x09, 0xa4, 0xec, 0x15, + 0x9d, 0xb4, 0xb3, 0x95, 0xeb, 0x84, 0xb4, 0x2b, 0x9e, 0x57, 0x55, 0x20, 0x2f, 0xf2, 0x31, 0x3c, + 0xd4, 0x94, 0x56, 0xb7, 0x20, 0x26, 0xcd, 0xf9, 0x09, 0xb4, 0xcd, 0xc6, 0x32, 0xb5, 0xbd, 0x44, + 0x6d, 0x97, 0xb9, 0xad, 0xde, 0x4a, 0xa6, 0x86, 0xad, 0x51, 0xf1, 0x22, 0x36, 0x01, 0x4e, 0x9c, + 0xd1, 0x89, 0xc3, 0x6d, 0x2e, 0x97, 0x03, 0xfc, 0x6f, 0x11, 0x4e, 0x2d, 0x9a, 0x27, 0xe6, 0x51, + 0x7c, 0x0e, 0x97, 0x8c, 0x14, 0x3e, 0x3f, 0x8e, 0x02, 0xed, 0xb0, 0x8f, 0x9c, 0x30, 0xca, 0x52, + 0xbd, 0x51, 0xbe, 0xa6, 0x09, 0xbe, 0x43, 0x3c, 0x0a, 0xc0, 0x03, 0xc2, 0x6a, 0x87, 0xf5, 0x08, + 0x9a, 0xb4, 0xcd, 0x43, 0xbd, 0xe5, 0x87, 0x1e, 0x2a, 0xaf, 0x3e, 0xf4, 0xf0, 0x21, 0xb4, 0x75, + 0x34, 0xf3, 0xb2, 0x53, 0x14, 0x2d, 0xc6, 0x73, 0x42, 0xf4, 0x16, 0x34, 0x29, 0x94, 0xa1, 0x6f, + 0x5c, 0x83, 0x16, 0x9f, 0xb4, 0x3b, 0x0a, 0x22, 0xf7, 0xc4, 0x04, 0x1f, 0x04, 0xba, 0x8b, 0x90, + 0x21, 0x40, 0xe3, 0x49, 0xe8, 0x47, 0xe1, 0x76, 0x10, 0x0c, 0xff, 0x54, 0x83, 0x26, 0xfa, 0x3c, + 0xb4, 0x2f, 0x85, 0x61, 0x23, 0x31, 0x26, 0xd5, 0x55, 0x4c, 0x9c, 0x58, 0x1f, 0xeb, 0x68, 0x21, + 0x10, 0xa9, 0xf6, 0x9c, 0x78, 0xa6, 0xec, 0x62, 0x61, 0xa6, 0xec, 0xe2, 0x6d, 0x3e, 0xf7, 0xc9, + 0x55, 0xb3, 0xd2, 0x94, 0xdb, 0x53, 0x07, 0x77, 0x19, 0x84, 0xbe, 0x18, 0x91, 0x38, 0x01, 0xf9, + 0x6f, 0x18, 0x1d, 0x06, 0x4a, 0x57, 0x68, 0x90, 0x5c, 0x6c, 0x6b, 0xc4, 0x81, 0x64, 0x7b, 0x53, + 0xda, 0x8c, 0xac, 0xce, 0x6e, 0x46, 0xde, 0x04, 0x70, 0xa3, 0xd0, 0x23, 0x17, 0x71, 0x26, 0x21, + 0xcd, 0xe5, 0x11, 0x05, 0xf6, 0x47, 0x6c, 0x93, 0xbf, 0x07, 0xfd, 0x9c, 0x02, 0x3d, 0x40, 0x37, + 0xcc, 0xe3, 0x6b, 0x4d, 0x65, 0xc9, 0xd1, 0x4e, 0x98, 0xce, 0xee, 0xa7, 0x37, 0xcf, 0xed, 0xa7, + 0xbf, 0xa4, 0x90, 0x06, 0x7e, 0xf2, 0xe9, 0xb9, 0x4b, 0xd0, 0xa0, 0x8a, 0x3d, 0x2f, 0x8b, 0xb5, + 0x2d, 0xaa, 0xfb, 0x8a, 0xf2, 0x1e, 0x2f, 0xdb, 0xb3, 0x6f, 0xff, 0xdf, 0xda, 0xb3, 0xef, 0xfc, + 0xb8, 0x3d, 0xfb, 0xee, 0x8f, 0xdb, 0xb3, 0x9f, 0xd9, 0xe3, 0xee, 0xcd, 0xee, 0x71, 0xbf, 0x34, + 0xad, 0xd8, 0x7f, 0x69, 0x5a, 0xf1, 0x35, 0x39, 0xc1, 0xe5, 0x57, 0xe7, 0x04, 0x5f, 0x9f, 0x94, + 0x14, 0xaf, 0x4b, 0x4a, 0xde, 0x80, 0x5e, 0x9a, 0x38, 0xee, 0x09, 0x47, 0x5a, 0x27, 0xf2, 0x4c, + 0xe9, 0x24, 0x68, 0x87, 0xc0, 0x18, 0x67, 0x7d, 0x2b, 0xcf, 0xd4, 0xf0, 0x09, 0x00, 0x85, 0xa0, + 0xf4, 0x6b, 0x2f, 0xe3, 0x8d, 0xca, 0x4f, 0x2e, 0xb2, 0xfa, 0xeb, 0x0a, 0xc0, 0x81, 0x33, 0x89, + 0x79, 0x0f, 0x59, 0xfc, 0x19, 0xb4, 0x14, 0xbd, 0x95, 0x8b, 0x4c, 0x4a, 0x86, 0xba, 0x20, 0xd5, + 0x8f, 0x7c, 0xb8, 0x4b, 0xe5, 0xcf, 0xc4, 0xd6, 0xdc, 0x43, 0x5e, 0xd0, 0x5a, 0x35, 0x04, 0xb4, + 0xb7, 0x77, 0x1d, 0xba, 0x9a, 0x20, 0x96, 0x89, 0x2b, 0x43, 0xae, 0xef, 0xaf, 0x58, 0x1d, 0x86, + 0xee, 0x33, 0x50, 0x7c, 0x94, 0x93, 0x19, 0x93, 0x78, 0x3e, 0x65, 0xa6, 0x9b, 0x68, 0x9b, 0x38, + 0xdc, 0x32, 0xbf, 0x42, 0x03, 0x69, 0xc0, 0x12, 0x7e, 0xaf, 0xff, 0x86, 0x68, 0x41, 0x5d, 0xf7, + 0xda, 0xaf, 0x88, 0x0e, 0x34, 0xe9, 0x34, 0x21, 0xe1, 0x16, 0x86, 0x7f, 0x5a, 0x85, 0xd6, 0x6e, + 0xa8, 0xd2, 0x24, 0x63, 0x16, 0x2e, 0xce, 0xcc, 0x55, 0xe9, 0xcc, 0x9c, 0xae, 0x03, 0xe7, 0xdf, + 0xa0, 0x3a, 0xf0, 0x0f, 0xa1, 0xae, 0x8f, 0x67, 0xea, 0xc4, 0xc2, 0xdc, 0xb3, 0x9d, 0x86, 0x46, + 0x6c, 0x42, 0xc3, 0xd3, 0xe7, 0x46, 0x75, 0x25, 0x4d, 0xe9, 0x30, 0xa7, 0x39, 0x51, 0x6a, 0xe5, + 0x34, 0xe2, 0x6d, 0x58, 0x74, 0xc6, 0x63, 0x1d, 0xd5, 0xf7, 0x0a, 0x52, 0x72, 0xd2, 0x2c, 0xc4, + 0x89, 0xdb, 0xd0, 0x24, 0xf5, 0x49, 0x55, 0x6d, 0xb5, 0xd9, 0x3e, 0x4d, 0xc9, 0x1c, 0x6b, 0x54, + 0xca, 0x49, 0xdc, 0x86, 0x66, 0x10, 0x45, 0x31, 0x37, 0xa8, 0xcf, 0x36, 0x30, 0xf5, 0x45, 0x56, + 0x23, 0x30, 0x95, 0x46, 0x37, 0xa0, 0x86, 0xee, 0x7f, 0x14, 0x6b, 0xb7, 0xb9, 0x34, 0x0e, 0xaa, + 0xb3, 0xb1, 0xaa, 0x8a, 0xca, 0x6d, 0xb6, 0x00, 0x98, 0xff, 0xa9, 0xe7, 0xe6, 0xec, 0x74, 0xe4, + 0xb9, 0x55, 0x14, 0x52, 0x93, 0x66, 0xbd, 0x0b, 0x7d, 0xce, 0xa3, 0x95, 0x5a, 0x82, 0xa9, 0x05, + 0x36, 0x2d, 0xa7, 0x53, 0xb3, 0x56, 0x37, 0x99, 0x4e, 0xd5, 0x7e, 0x00, 0xf5, 0x98, 0x93, 0x43, + 0xa4, 0x61, 0xc8, 0x64, 0x9b, 0xa6, 0x3a, 0x6b, 0x64, 0x19, 0x0a, 0xf1, 0x1b, 0xe8, 0x72, 0xcd, + 0xea, 0x48, 0x67, 0x49, 0x68, 0x67, 0x6f, 0xea, 0x64, 0xdf, 0x54, 0x12, 0xc5, 0xea, 0xa4, 0x53, + 0x39, 0x95, 0x5f, 0x41, 0xa7, 0x38, 0xb0, 0x84, 0x16, 0xbf, 0x67, 0xb2, 0x15, 0xa6, 0x79, 0x39, + 0x1a, 0xb3, 0xda, 0xb2, 0x1c, 0x9b, 0x6d, 0x40, 0x4d, 0xd7, 0x51, 0xf7, 0xa9, 0x55, 0xe9, 0xd6, + 0x05, 0xae, 0x9c, 0xb4, 0x34, 0x1e, 0xe7, 0xb2, 0x28, 0x11, 0x25, 0xc7, 0x71, 0x6a, 0x2e, 0xf3, + 0xfa, 0x50, 0xab, 0x99, 0x97, 0x86, 0x8a, 0xfb, 0xd3, 0x25, 0xab, 0x9c, 0xd4, 0x5b, 0xa1, 0xa6, + 0x97, 0xe6, 0x34, 0xe5, 0xf4, 0x9e, 0xd5, 0x8b, 0x67, 0x2a, 0x5f, 0x6f, 0x41, 0x23, 0x4a, 0x3c, + 0x3a, 0x1f, 0x40, 0x55, 0x14, 0xb9, 0x0b, 0xa4, 0xcf, 0xa4, 0x92, 0xf2, 0xa8, 0x47, 0xfc, 0x82, + 0x8e, 0x45, 0x9c, 0x44, 0xe4, 0xe5, 0x92, 0x8a, 0xbb, 0x70, 0xde, 0xb1, 0xd0, 0x78, 0x52, 0x70, + 0xef, 0x42, 0xdd, 0x54, 0x87, 0xaf, 0x9d, 0xa3, 0x34, 0x28, 0xf1, 0x31, 0xf4, 0xa6, 0x15, 0x9a, + 0x1a, 0x5c, 0x3c, 0x47, 0xdd, 0x9d, 0xd2, 0x5f, 0x68, 0x8d, 0xab, 0x81, 0x3f, 0xf1, 0x53, 0xed, + 0xf3, 0x4d, 0x9d, 0x10, 0x25, 0x04, 0xc6, 0x7f, 0x3a, 0x05, 0x72, 0xe9, 0x7c, 0xfc, 0xa7, 0xd3, + 0x24, 0x03, 0xa8, 0xfb, 0xea, 0x81, 0x9f, 0xa8, 0x54, 0xd7, 0x37, 0x98, 0x57, 0xb1, 0x06, 0x35, + 0x5f, 0xa1, 0x99, 0xd0, 0x5e, 0x9a, 0x7e, 0x13, 0x37, 0xa1, 0xa6, 0x2b, 0xe7, 0xd7, 0xcf, 0x49, + 0xb4, 0x3e, 0x59, 0x63, 0x69, 0x0a, 0xf1, 0x3e, 0xd4, 0xa9, 0x6c, 0x3a, 0x8a, 0x07, 0x6f, 0xcf, + 0x72, 0x00, 0xd7, 0x2e, 0x5b, 0xb5, 0x80, 0x6b, 0x98, 0x3f, 0x80, 0xba, 0x71, 0x52, 0x86, 0xb3, + 0x5c, 0xad, 0x9d, 0x15, 0xcb, 0x50, 0x88, 0xeb, 0x50, 0x9d, 0xa0, 0x1e, 0x1b, 0xbc, 0x33, 0x2b, + 0xa1, 0xac, 0xde, 0x18, 0x2b, 0xfe, 0x7f, 0xb8, 0x5c, 0x2e, 0x3c, 0x36, 0x55, 0xc9, 0x7a, 0x83, + 0xf3, 0x3a, 0xb5, 0x7d, 0x7b, 0x0e, 0xab, 0x4c, 0xd7, 0x2f, 0x5b, 0x17, 0xe3, 0x97, 0x14, 0x36, + 0x7f, 0x9a, 0xab, 0x7b, 0x94, 0xae, 0xc1, 0x0d, 0xe3, 0x7c, 0x9f, 0x37, 0x18, 0xc6, 0x08, 0x90, + 0x9d, 0xf9, 0x0c, 0xda, 0xa3, 0xec, 0xc5, 0x8b, 0x33, 0xb3, 0x39, 0xff, 0x1e, 0xb5, 0x2b, 0x6d, + 0x31, 0x94, 0x6a, 0x9d, 0xad, 0xd6, 0xa8, 0x54, 0xf8, 0x7c, 0x11, 0xea, 0x6e, 0x68, 0x3b, 0x9e, + 0x97, 0x0c, 0x36, 0xb8, 0xd6, 0xd9, 0x0d, 0xb7, 0x3d, 0x8f, 0x2e, 0x77, 0x88, 0x62, 0x49, 0x27, + 0xa4, 0x6d, 0xdf, 0x1b, 0xbc, 0xcf, 0x86, 0xc7, 0x80, 0x76, 0x3d, 0xba, 0xfd, 0xc1, 0xc4, 0xe5, + 0xbe, 0x37, 0xb8, 0xa9, 0x6f, 0x7f, 0xd0, 0xa0, 0x5d, 0x0f, 0x1d, 0x4f, 0x0c, 0x62, 0x0c, 0x64, + 0xf0, 0x01, 0x27, 0x3c, 0x26, 0xce, 0xe9, 0xbe, 0x06, 0xa1, 0x90, 0x72, 0x6a, 0x8f, 0xd4, 0xd6, + 0xad, 0x59, 0x21, 0xcd, 0xd3, 0xc0, 0x56, 0xd3, 0xcf, 0x33, 0xc2, 0x24, 0xd8, 0xa4, 0x8a, 0xec, + 0x60, 0x6b, 0xf0, 0xe1, 0x79, 0xc1, 0xd6, 0x59, 0x6e, 0x14, 0x6c, 0x93, 0xf0, 0xde, 0x02, 0x60, + 0x9d, 0x45, 0x0a, 0x67, 0x73, 0xb6, 0x4d, 0x1e, 0x0d, 0x58, 0x7c, 0xa4, 0x89, 0x54, 0xcd, 0x16, + 0x00, 0xa5, 0x17, 0xb8, 0xcd, 0xed, 0xd9, 0x36, 0xb9, 0x77, 0x6f, 0x35, 0x9f, 0xe5, 0x8e, 0xfe, + 0x6d, 0x68, 0x66, 0xe8, 0xc7, 0xa3, 0x27, 0x3d, 0xb8, 0x33, 0xcb, 0xcc, 0xc6, 0xc5, 0xb7, 0x1a, + 0x99, 0x7e, 0xc2, 0x8f, 0x90, 0xed, 0x21, 0x37, 0x64, 0xf0, 0xd1, 0xec, 0x47, 0xf2, 0x38, 0xc0, + 0x22, 0x13, 0xc5, 0x21, 0xc1, 0xa7, 0xd0, 0xe2, 0x49, 0xe3, 0x46, 0x5b, 0xb3, 0x3c, 0x52, 0xf8, + 0x35, 0x16, 0xcf, 0x2e, 0x37, 0xbb, 0x0e, 0x55, 0x27, 0x8e, 0x83, 0xb3, 0xc1, 0xc7, 0xb3, 0x1c, + 0xbe, 0x8d, 0x60, 0x8b, 0xb1, 0xc8, 0x4a, 0x93, 0x2c, 0x48, 0x7d, 0x73, 0x0a, 0xe9, 0x93, 0x59, + 0x56, 0x2a, 0x1d, 0xeb, 0xb4, 0x5a, 0x93, 0xd2, 0x19, 0xcf, 0x5b, 0xd0, 0x88, 0x23, 0x95, 0xda, + 0xde, 0x24, 0x18, 0x7c, 0x7a, 0xce, 0x8c, 0xf0, 0x89, 0x14, 0xab, 0x1e, 0xeb, 0x23, 0x3d, 0x53, + 0xa7, 0xa4, 0x7f, 0x31, 0x73, 0x4a, 0x7a, 0x0b, 0xda, 0x93, 0x28, 0x1c, 0x47, 0xde, 0x11, 0xcf, + 0xfe, 0x2f, 0xcb, 0x41, 0xe1, 0x1e, 0x62, 0x38, 0x8c, 0xd4, 0x44, 0xda, 0x34, 0xf4, 0xb5, 0xef, + 0xe6, 0x2b, 0xdb, 0x51, 0xa4, 0xf6, 0x3f, 0xe3, 0x10, 0x9e, 0xe1, 0xbb, 0x6a, 0x9b, 0xa0, 0xd3, + 0xdb, 0xdb, 0x47, 0x67, 0x3a, 0x17, 0xf9, 0x39, 0xb1, 0x67, 0xb1, 0xbd, 0x7d, 0xf7, 0x8c, 0x13, + 0x92, 0x5f, 0xc0, 0xe5, 0xd2, 0xb9, 0xc5, 0x28, 0xb6, 0x43, 0x1b, 0x55, 0x40, 0x22, 0xbd, 0xcc, + 0x95, 0x83, 0x2f, 0xf2, 0x78, 0x53, 0x1f, 0x5e, 0x8c, 0xe2, 0x47, 0xfb, 0x89, 0xb4, 0x08, 0x2b, + 0x1e, 0x95, 0xcf, 0x48, 0x3a, 0xc1, 0x38, 0x4a, 0xfc, 0xf4, 0x78, 0x32, 0xf8, 0x15, 0xf9, 0x81, + 0x6f, 0x96, 0xe2, 0x81, 0x3c, 0xff, 0xb5, 0x6d, 0x88, 0xac, 0x62, 0x8c, 0x39, 0x4c, 0x7c, 0x01, + 0x97, 0xb4, 0x2d, 0xc0, 0x0e, 0xa7, 0x8e, 0xa2, 0xab, 0xc1, 0xaf, 0xe9, 0x2c, 0xfa, 0xc5, 0x82, + 0xe0, 0xeb, 0xd2, 0xa9, 0x74, 0x25, 0xb6, 0xe1, 0xcd, 0x79, 0x6d, 0xd1, 0x31, 0xe1, 0x09, 0xf8, + 0x92, 0x26, 0xe0, 0xf2, 0xf9, 0xf6, 0x07, 0x52, 0xdf, 0x4b, 0x73, 0x0b, 0x04, 0x4e, 0x00, 0x59, + 0x2e, 0xe9, 0xd9, 0x51, 0x96, 0xc6, 0x59, 0x3a, 0xf8, 0x0d, 0xc7, 0x89, 0x69, 0x14, 0x3f, 0x66, + 0xc4, 0x63, 0x82, 0x63, 0x9c, 0x4e, 0x4a, 0xd3, 0x2e, 0xb7, 0xe1, 0xdd, 0x02, 0x35, 0xf8, 0x8a, + 0xe7, 0x8d, 0x08, 0x0e, 0xf3, 0x96, 0xbc, 0xb7, 0xa0, 0x38, 0x4e, 0xff, 0xed, 0x52, 0x63, 0xb9, + 0x2f, 0x7e, 0xbb, 0xd4, 0x78, 0xb7, 0x7f, 0xdd, 0x6a, 0x95, 0xf6, 0x24, 0x86, 0x9f, 0x42, 0x7b, + 0x9b, 0x6e, 0x3e, 0xf2, 0x15, 0xd9, 0xc4, 0xeb, 0xb0, 0x94, 0xd7, 0xb2, 0xe4, 0xc6, 0x96, 0x28, + 0x5e, 0xc8, 0xdd, 0x70, 0x14, 0x59, 0x84, 0x1e, 0xfe, 0xab, 0x25, 0xa8, 0x71, 0x7d, 0xc1, 0xeb, + 0x4f, 0x37, 0xbe, 0x69, 0x34, 0x46, 0x58, 0x1c, 0x03, 0x61, 0xe5, 0x40, 0xe8, 0xd9, 0x72, 0xef, + 0x66, 0x51, 0x26, 0xb3, 0x0a, 0x55, 0x0e, 0xf3, 0x39, 0xfb, 0xc1, 0x2f, 0xa4, 0x2d, 0x33, 0x75, + 0x4c, 0xd7, 0x1b, 0xe9, 0x7c, 0xde, 0x92, 0x05, 0x06, 0xb4, 0xeb, 0xd1, 0x36, 0xa7, 0x21, 0x20, + 0x75, 0x5c, 0xd3, 0x69, 0x0c, 0x0d, 0x24, 0xa5, 0x6c, 0x4a, 0x70, 0xea, 0x2f, 0x29, 0xc1, 0x79, + 0x0b, 0x96, 0x42, 0x73, 0x42, 0x29, 0xc7, 0xd3, 0xf5, 0x26, 0x04, 0x17, 0x37, 0x21, 0x3f, 0x92, + 0xa9, 0xdd, 0xcb, 0x97, 0x1f, 0xd9, 0xdc, 0x82, 0x66, 0x7e, 0x57, 0x96, 0xf6, 0x28, 0x57, 0x37, + 0x8b, 0xdb, 0xb3, 0x0e, 0xcd, 0x93, 0x55, 0x90, 0xbd, 0xba, 0x90, 0xa4, 0xf5, 0xf3, 0x0a, 0x49, + 0x38, 0xdc, 0x76, 0xa3, 0x50, 0xa5, 0x7a, 0x23, 0xb7, 0xee, 0xab, 0x1d, 0x7c, 0x15, 0x9f, 0x43, + 0x27, 0x91, 0xee, 0x33, 0x7b, 0xa2, 0xc6, 0xfc, 0x89, 0x4e, 0xf9, 0x58, 0xf8, 0x44, 0x8d, 0xbf, + 0xa1, 0x22, 0x17, 0x1d, 0xfd, 0xb6, 0x90, 0x76, 0x4f, 0x8d, 0xa9, 0xd7, 0x0f, 0x60, 0x79, 0x22, + 0x27, 0x47, 0x32, 0x51, 0xc7, 0x7e, 0x6c, 0xcc, 0x66, 0x97, 0x8a, 0x71, 0xfa, 0x05, 0x82, 0xc7, + 0x32, 0xfc, 0xfb, 0x15, 0x68, 0xe0, 0x2c, 0x22, 0x2f, 0x09, 0x01, 0x4b, 0x13, 0x37, 0xce, 0x74, + 0x50, 0x43, 0xcf, 0xfa, 0xfe, 0x2d, 0xe6, 0x12, 0x7d, 0xff, 0x16, 0xad, 0x21, 0xa7, 0x27, 0xe9, + 0x99, 0xaf, 0x54, 0x39, 0xa3, 0x1d, 0x6c, 0xe6, 0x0c, 0xf3, 0x2a, 0x2e, 0x40, 0xcd, 0x0d, 0x69, + 0x67, 0x83, 0xd3, 0xbc, 0x55, 0x37, 0xdc, 0x09, 0x53, 0x0d, 0x2e, 0x4e, 0xb9, 0x54, 0xdd, 0x70, + 0xd7, 0x3b, 0x1d, 0xfe, 0xdb, 0x0a, 0x2c, 0xef, 0x27, 0x91, 0x2b, 0x95, 0x7a, 0x88, 0x4e, 0x19, + 0xa5, 0xd4, 0xf0, 0x8b, 0x94, 0x81, 0xe0, 0xec, 0x15, 0x3d, 0x23, 0x0f, 0xf3, 0xb6, 0x53, 0x1e, + 0x3a, 0x2e, 0x5a, 0x4d, 0x82, 0x50, 0xe4, 0x98, 0xa3, 0x4b, 0xa5, 0x1a, 0x8c, 0xa6, 0xdc, 0xc5, + 0x75, 0xe8, 0x16, 0xba, 0xab, 0x54, 0x55, 0x52, 0x5c, 0x7c, 0x40, 0xbd, 0x5c, 0x83, 0x96, 0xae, + 0x3d, 0xa2, 0x6e, 0x38, 0x1d, 0x05, 0x0c, 0x3a, 0xd0, 0xa3, 0x60, 0x45, 0x4f, 0x78, 0x4e, 0x40, + 0xb1, 0xea, 0x47, 0xf4, 0xf0, 0x0f, 0x15, 0xe8, 0xef, 0x27, 0x32, 0x76, 0x12, 0x49, 0xc5, 0x48, + 0x34, 0xc7, 0x6b, 0x50, 0x0b, 0x64, 0x38, 0xd6, 0xf5, 0x27, 0x8b, 0x96, 0x7e, 0xcb, 0xef, 0x4e, + 0x5b, 0x28, 0xdd, 0x9d, 0x86, 0x73, 0x9d, 0x48, 0x47, 0x5f, 0xb1, 0x46, 0xcf, 0x28, 0x83, 0x18, + 0xff, 0x73, 0x90, 0xdb, 0xb0, 0xf8, 0x45, 0x1f, 0xaf, 0x3e, 0xf2, 0x43, 0x2a, 0xfa, 0xa4, 0xe3, + 0xd5, 0x77, 0x7d, 0x32, 0x1c, 0x0c, 0x46, 0x47, 0x8e, 0xaf, 0x29, 0xa2, 0x6d, 0xab, 0x86, 0xd5, + 0x25, 0x02, 0x27, 0x39, 0x3b, 0x20, 0x28, 0xc5, 0xda, 0x7c, 0xbb, 0x11, 0x57, 0x34, 0x71, 0xc2, + 0xa4, 0x63, 0x2e, 0x37, 0x62, 0xdd, 0xa2, 0x86, 0xff, 0xb8, 0x0e, 0x2d, 0xbd, 0x42, 0xf4, 0x37, + 0xcc, 0x1d, 0x95, 0x9c, 0x3b, 0xfa, 0xb0, 0xa8, 0x9e, 0x06, 0x9a, 0x5d, 0xf0, 0x51, 0x7c, 0x0c, + 0x8b, 0x81, 0x3f, 0xd1, 0x01, 0xf0, 0x95, 0x29, 0x67, 0x66, 0x7a, 0x9d, 0x35, 0x2b, 0x23, 0x35, + 0x5a, 0x50, 0xba, 0xeb, 0x01, 0x85, 0x46, 0xaf, 0x0d, 0x3a, 0x16, 0xa7, 0x28, 0x99, 0x38, 0xeb, + 0x8e, 0xcb, 0x05, 0xc7, 0x5a, 0xdd, 0x74, 0xac, 0xa6, 0x86, 0xec, 0x7a, 0xe2, 0x13, 0x68, 0xe4, + 0xdb, 0xf4, 0x26, 0xe4, 0x4d, 0x4f, 0xc3, 0xcd, 0x9d, 0x47, 0x87, 0xa7, 0xa1, 0xd9, 0x87, 0xd7, + 0x1f, 0xcb, 0x29, 0xc5, 0x6f, 0xa0, 0xad, 0xa4, 0x52, 0x7c, 0x18, 0x7f, 0x14, 0x69, 0x35, 0x74, + 0xa1, 0x1c, 0xcd, 0x12, 0x16, 0xff, 0xda, 0x08, 0x9d, 0x2a, 0x40, 0xe2, 0x1b, 0xe8, 0x9a, 0xf6, + 0x41, 0x34, 0x1e, 0xe7, 0x69, 0xa4, 0x2b, 0xe7, 0x7a, 0x78, 0x48, 0xe8, 0x52, 0x3f, 0x1d, 0x55, + 0x46, 0x88, 0xaf, 0xa1, 0x1b, 0x33, 0xd3, 0xd8, 0xba, 0x90, 0x8e, 0xd5, 0xd9, 0xe5, 0x29, 0xdf, + 0x7b, 0x8a, 0xa9, 0x8a, 0x43, 0xa7, 0x05, 0x5c, 0x9d, 0xbf, 0x98, 0x82, 0xf3, 0x8a, 0xd3, 0x17, + 0x53, 0x48, 0x58, 0xd3, 0x37, 0x49, 0x8d, 0x12, 0x87, 0x0e, 0xd8, 0xb3, 0xc9, 0x34, 0xd5, 0xb1, + 0xb7, 0xcf, 0xad, 0x18, 0x7e, 0x70, 0x93, 0x2f, 0x60, 0x78, 0xa0, 0x9b, 0x90, 0x09, 0xd5, 0xa5, + 0x46, 0xab, 0xc9, 0x1c, 0x94, 0xd8, 0x84, 0x15, 0xfd, 0x19, 0x79, 0x2a, 0xdd, 0x4c, 0xdf, 0x2d, + 0x42, 0x4a, 0xaf, 0x6d, 0x2d, 0x33, 0xea, 0xbe, 0xc1, 0xec, 0x7a, 0xe2, 0x33, 0x18, 0xa8, 0xd4, + 0x49, 0x25, 0x0d, 0xc8, 0xe8, 0x5d, 0x7f, 0x1c, 0x46, 0x89, 0xd4, 0xe5, 0x39, 0x6b, 0x39, 0x5e, + 0xeb, 0xdb, 0x5d, 0xc2, 0x8a, 0xdf, 0x40, 0x1f, 0x55, 0x64, 0x9e, 0xaa, 0xb1, 0x53, 0xa5, 0xa3, + 0xf8, 0xf9, 0x2a, 0xbe, 0x8b, 0xd4, 0x86, 0x2d, 0x0e, 0x29, 0xe5, 0x4f, 0xed, 0xc7, 0x32, 0xc4, + 0x78, 0x80, 0x34, 0x84, 0xcc, 0x94, 0xf4, 0xf4, 0xf5, 0x31, 0xab, 0x88, 0xfd, 0x3a, 0x47, 0x5a, + 0x84, 0x43, 0x0f, 0xc4, 0x88, 0x8f, 0xde, 0xa9, 0x25, 0xdf, 0xb7, 0xf0, 0x8b, 0xfa, 0xc4, 0xa6, + 0x97, 0xb5, 0x34, 0x31, 0x0d, 0xba, 0xc0, 0xb9, 0x03, 0x74, 0xf9, 0x6b, 0xb8, 0xf4, 0xd2, 0x59, + 0x7d, 0x5d, 0xbd, 0x50, 0xa7, 0x7c, 0xd9, 0xc1, 0xff, 0x5a, 0x84, 0x56, 0x89, 0x5b, 0xe9, 0xc6, + 0x44, 0x25, 0x13, 0x53, 0x15, 0x88, 0xcf, 0x08, 0x3b, 0x8e, 0x94, 0x29, 0x72, 0xa3, 0x67, 0x84, + 0x25, 0x51, 0x5e, 0xec, 0x43, 0xcf, 0xc8, 0x43, 0x7a, 0x7b, 0x4a, 0xaf, 0xd8, 0x12, 0x5f, 0xd2, + 0x51, 0x00, 0x77, 0x3d, 0xba, 0x5a, 0xd1, 0x49, 0x9d, 0x23, 0x47, 0x99, 0xf2, 0xce, 0xfc, 0x1d, + 0x4d, 0xc3, 0x33, 0x99, 0xe0, 0x58, 0x4c, 0x71, 0x83, 0x7e, 0x45, 0x19, 0xa7, 0x55, 0x7d, 0x11, + 0x85, 0x5c, 0xd8, 0xd0, 0xb6, 0x1a, 0x08, 0xf8, 0x3e, 0x0a, 0xa9, 0x99, 0x96, 0x68, 0x5d, 0xa0, + 0x63, 0x5e, 0xd1, 0x66, 0x3e, 0xcd, 0x24, 0xc6, 0xa5, 0x1e, 0x1d, 0x66, 0x6c, 0x5a, 0x75, 0x7a, + 0xe7, 0x9a, 0x21, 0x0a, 0xa0, 0x9f, 0x3b, 0x7e, 0x4a, 0xaa, 0x23, 0xca, 0x52, 0xcd, 0xf4, 0x3d, + 0x44, 0x7c, 0xe7, 0xf8, 0xe9, 0x21, 0x83, 0xc5, 0x47, 0xfa, 0x8c, 0x72, 0x99, 0x96, 0x2e, 0x09, + 0xe2, 0x6d, 0x6f, 0x31, 0x43, 0x7f, 0x20, 0xe9, 0x1e, 0xbc, 0x89, 0x93, 0x26, 0xfe, 0x69, 0x14, + 0xa2, 0xef, 0x44, 0xf7, 0x01, 0xe4, 0xd7, 0x39, 0x36, 0xac, 0x95, 0x1c, 0xf9, 0x88, 0x70, 0x94, + 0x09, 0x7e, 0x02, 0x1b, 0xf2, 0x34, 0x0e, 0x7c, 0xd7, 0x9f, 0xb9, 0xd7, 0xc0, 0x76, 0x1d, 0x95, + 0xda, 0x89, 0x4c, 0xb3, 0x24, 0x54, 0xb4, 0xa3, 0xab, 0xf9, 0xfa, 0x1d, 0x43, 0x5f, 0xbe, 0xeb, + 0x60, 0xc7, 0x51, 0xa9, 0xc5, 0xb4, 0x8f, 0xb2, 0x20, 0xc0, 0x49, 0xc8, 0x33, 0xd0, 0x5c, 0x7c, + 0x56, 0x57, 0x9c, 0x7b, 0x1e, 0xfe, 0xa7, 0x0a, 0x2c, 0x9f, 0xd3, 0x34, 0x18, 0x0b, 0xa3, 0x96, + 0x31, 0x05, 0x33, 0x6d, 0xab, 0x86, 0xaf, 0xbb, 0x1e, 0x21, 0xd2, 0x49, 0x6a, 0x4a, 0x65, 0x10, + 0x91, 0x4e, 0x50, 0x8d, 0x5e, 0x80, 0x5a, 0x7a, 0x4a, 0x4b, 0xce, 0xd6, 0xa7, 0x9a, 0x9e, 0xe2, + 0x5a, 0x6f, 0x43, 0x33, 0x88, 0xc6, 0x76, 0x20, 0x9f, 0x49, 0xbe, 0x6c, 0xa6, 0xbb, 0xf5, 0xee, + 0x2b, 0x54, 0xdc, 0xe6, 0xc3, 0x68, 0xfc, 0x10, 0x69, 0xad, 0x46, 0xa0, 0x9f, 0x86, 0xbf, 0x85, + 0x86, 0x81, 0x8a, 0x26, 0x54, 0xef, 0xc9, 0xa3, 0x6c, 0xdc, 0x7f, 0x43, 0x34, 0x60, 0x09, 0x5b, + 0xf4, 0x2b, 0xf8, 0xf4, 0x9d, 0x93, 0x84, 0xfd, 0x05, 0x44, 0xdf, 0x4f, 0x92, 0x28, 0xe9, 0x2f, + 0xe2, 0xe3, 0xbe, 0x13, 0xfa, 0x6e, 0x7f, 0x09, 0x1f, 0x1f, 0x38, 0xa9, 0x13, 0xf4, 0xab, 0xc3, + 0x7f, 0x5f, 0x85, 0xc6, 0xbe, 0xfe, 0xba, 0xb8, 0x07, 0x9d, 0xfc, 0xd2, 0xc6, 0xf9, 0x9b, 0xd2, + 0xfb, 0xb3, 0x0f, 0xb4, 0x29, 0xdd, 0x8e, 0x4b, 0x6f, 0xb3, 0x57, 0x3f, 0x2e, 0x9c, 0xbb, 0xfa, + 0xf1, 0x2a, 0x2c, 0x3e, 0x4d, 0xce, 0xa6, 0x6b, 0xc5, 0xf7, 0x03, 0x27, 0xb4, 0x10, 0x2c, 0x3e, + 0x82, 0x16, 0xa5, 0xc3, 0xd9, 0x8c, 0xea, 0x8d, 0xdc, 0xf2, 0x2d, 0xab, 0x5c, 0x05, 0x0c, 0x48, + 0xa4, 0x3d, 0xf6, 0x4d, 0x68, 0xb8, 0xc7, 0x7e, 0xe0, 0x25, 0x32, 0xd4, 0x67, 0x36, 0xc4, 0xf9, + 0x21, 0x5b, 0x39, 0x8d, 0xf8, 0x33, 0xe8, 0xfb, 0xc5, 0x46, 0x74, 0x51, 0xfd, 0x30, 0x65, 0xaf, + 0x4a, 0x5b, 0xd5, 0x56, 0xaf, 0x44, 0x4e, 0x2e, 0x62, 0x71, 0x1b, 0x4b, 0xbd, 0x7c, 0x1b, 0x0b, + 0x5f, 0xc6, 0x47, 0x7e, 0x5c, 0x23, 0xdf, 0xc6, 0x42, 0x37, 0xee, 0x86, 0x76, 0xbe, 0x9b, 0xb3, + 0x71, 0xbf, 0x71, 0x1d, 0xb5, 0x13, 0xfe, 0x2e, 0x74, 0xd1, 0xa9, 0xb7, 0x39, 0x16, 0x40, 0x3b, + 0x0a, 0xfa, 0xf2, 0xa8, 0x4c, 0x1d, 0xdf, 0xc3, 0x68, 0x00, 0x99, 0xf1, 0x3a, 0x74, 0xcd, 0xbf, + 0xe8, 0xf8, 0xac, 0xa5, 0xab, 0x23, 0x34, 0x94, 0x43, 0xb2, 0x4d, 0x58, 0x71, 0x8f, 0x9d, 0x30, + 0x94, 0x81, 0x7d, 0x94, 0x8d, 0x46, 0xc6, 0x0d, 0xe3, 0x1b, 0xc5, 0x96, 0x35, 0xea, 0x2e, 0x61, + 0xc8, 0x1b, 0x1b, 0x42, 0x27, 0xf4, 0x03, 0x73, 0xab, 0x68, 0xc8, 0x2e, 0x73, 0xd5, 0x6a, 0x85, + 0x7e, 0xc0, 0xf7, 0x88, 0xd2, 0xf5, 0xa7, 0xfd, 0x2c, 0xf3, 0x3d, 0x65, 0xa7, 0x91, 0xb9, 0xc7, + 0x50, 0xa7, 0x94, 0x4a, 0x9b, 0xb4, 0x4f, 0x32, 0xdf, 0x3b, 0x8c, 0xf4, 0x4d, 0x86, 0x1d, 0xa2, + 0x37, 0xaf, 0x38, 0xf6, 0xe9, 0x78, 0x8f, 0x4a, 0x14, 0x1a, 0x56, 0x27, 0x2a, 0x87, 0x79, 0xc3, + 0xaf, 0xa0, 0x5d, 0x66, 0x31, 0x64, 0x59, 0xda, 0x6c, 0xeb, 0xbf, 0x21, 0x00, 0x6a, 0x8f, 0xa2, + 0x64, 0xe2, 0x04, 0xfd, 0x0a, 0x3e, 0xb3, 0xce, 0xef, 0x2f, 0x88, 0x36, 0x34, 0xcc, 0xe6, 0x51, + 0x7f, 0x51, 0xa7, 0x73, 0x7f, 0x05, 0x0d, 0x73, 0x8b, 0x23, 0xdd, 0x80, 0x17, 0x79, 0x92, 0x23, + 0x28, 0x5d, 0xbb, 0x8c, 0x00, 0x8a, 0x9e, 0xcc, 0xad, 0xb8, 0x0b, 0xc5, 0xad, 0xb8, 0xc3, 0xdf, + 0x41, 0xbb, 0xfc, 0x27, 0x26, 0x41, 0x51, 0x29, 0x12, 0x14, 0x73, 0x5a, 0x51, 0x4d, 0x4d, 0x12, + 0x4d, 0xec, 0x92, 0x93, 0xdf, 0x40, 0x00, 0x7e, 0x66, 0xf8, 0xf7, 0x16, 0xa0, 0x4a, 0x5b, 0x2a, + 0xe4, 0x84, 0xe1, 0x43, 0x21, 0x68, 0x55, 0xab, 0x49, 0x90, 0xff, 0x83, 0xe3, 0xc4, 0x79, 0xc2, + 0x7a, 0xe9, 0xd5, 0x09, 0xeb, 0x6d, 0x58, 0x7e, 0x56, 0xba, 0x79, 0x95, 0x37, 0x52, 0xaa, 0xc6, + 0x63, 0xc3, 0x36, 0x7f, 0x5e, 0xdc, 0xc0, 0x4a, 0xdb, 0x29, 0xbd, 0x67, 0xd3, 0x00, 0xf1, 0x36, + 0xe8, 0x93, 0x19, 0x36, 0x17, 0x44, 0x71, 0xf5, 0x50, 0x8b, 0x61, 0xdb, 0x54, 0xfe, 0x84, 0x71, + 0xf2, 0x69, 0x68, 0xae, 0xe3, 0xe1, 0x82, 0xb1, 0x66, 0x7a, 0x1a, 0x72, 0x09, 0xd3, 0x70, 0x02, + 0x2b, 0x73, 0x8e, 0x91, 0x89, 0x75, 0x68, 0x4f, 0xa5, 0xf5, 0xf8, 0x6c, 0x07, 0xb8, 0x45, 0x1e, + 0xef, 0x13, 0x58, 0x93, 0x81, 0x3f, 0xf6, 0x8f, 0x7c, 0xaa, 0x9a, 0x2d, 0x9d, 0x6c, 0x63, 0x5d, + 0xb3, 0x5a, 0xc2, 0xe6, 0x27, 0xdb, 0x6e, 0xfe, 0xeb, 0x0a, 0xd4, 0xf8, 0xf6, 0x65, 0xb1, 0x0c, + 0x9d, 0x27, 0xe1, 0x49, 0x18, 0x3d, 0x0f, 0x19, 0xd0, 0x7f, 0x43, 0xac, 0x40, 0xcf, 0xf0, 0x9b, + 0xbe, 0xe6, 0xb9, 0x5f, 0x11, 0x7d, 0x68, 0x13, 0xe3, 0x1b, 0xc8, 0x82, 0xb8, 0x0a, 0x03, 0xed, + 0x3b, 0xde, 0x43, 0x3b, 0x15, 0xa5, 0xfe, 0xe8, 0xcc, 0x60, 0x17, 0x45, 0x0f, 0x5a, 0x07, 0x69, + 0x14, 0x1f, 0xc8, 0xd0, 0xf3, 0xc3, 0x71, 0x7f, 0x49, 0x0c, 0x60, 0xd5, 0xf4, 0xca, 0x7c, 0xfd, + 0xc0, 0x0f, 0x7d, 0x75, 0xdc, 0xaf, 0x8a, 0x2b, 0x70, 0x71, 0x1e, 0x66, 0xdb, 0x3d, 0xe9, 0xd7, + 0xc4, 0x2a, 0xf4, 0x0d, 0xf2, 0xae, 0xbe, 0x6b, 0xb7, 0x5f, 0xbf, 0xf9, 0x09, 0x88, 0xf3, 0xd7, + 0x1c, 0xe3, 0x37, 0x1f, 0xca, 0xb1, 0xe3, 0x9e, 0xed, 0x04, 0x91, 0x42, 0xf1, 0xe8, 0x40, 0xb3, + 0xe8, 0xab, 0x72, 0xf3, 0x01, 0xd4, 0xf8, 0x5e, 0xea, 0xd2, 0x5f, 0x33, 0xa0, 0xff, 0x06, 0x36, + 0x46, 0x1b, 0xed, 0x87, 0xe3, 0x47, 0xf2, 0x34, 0x65, 0xcb, 0xf1, 0xd0, 0x51, 0x69, 0x7f, 0x41, + 0x74, 0x01, 0xf4, 0x8f, 0xdd, 0x0f, 0xbd, 0xfe, 0xe2, 0xdd, 0x9d, 0xbf, 0xfa, 0xe3, 0x5b, 0x95, + 0x3f, 0xfc, 0xf1, 0xad, 0xca, 0x7f, 0xfc, 0xe3, 0x5b, 0x6f, 0xfc, 0xfe, 0x4f, 0x6f, 0x55, 0xbe, + 0xff, 0xa8, 0x74, 0xeb, 0xb6, 0x36, 0xdd, 0x54, 0x0d, 0x76, 0x3b, 0xb7, 0xe3, 0xb7, 0xe3, 0x93, + 0xf1, 0xed, 0xf8, 0xe8, 0xb6, 0x51, 0x0c, 0x47, 0x35, 0xba, 0x4c, 0xfb, 0xe3, 0xff, 0x1d, 0x00, + 0x00, 0xff, 0xff, 0xb4, 0xc1, 0xbc, 0x3b, 0xcb, 0x5b, 0x00, 0x00, } func (m *Message) Marshal() (dAtA []byte, err error) { @@ -11900,6 +11934,30 @@ func (m *Instruction) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.MergeTopOrderedStreams { + i-- + if m.MergeTopOrderedStreams { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x3 + i-- + dAtA[i] = 0xf8 + } + if m.TopOrderedOutput { + i-- + if m.TopOrderedOutput { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x3 + i-- + dAtA[i] = 0xf0 + } if m.ProjectionGroupingSetCount != 0 { i = encodeVarintPipeline(dAtA, i, uint64(m.ProjectionGroupingSetCount)) i-- @@ -13356,6 +13414,19 @@ func (m *Pipeline) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.OrderedStream) > 0 { + for iNdEx := len(m.OrderedStream) - 1; iNdEx >= 0; iNdEx-- { + i-- + if m.OrderedStream[iNdEx] { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + } + i = encodeVarintPipeline(dAtA, i, uint64(len(m.OrderedStream))) + i-- + dAtA[i] = 0x7a + } if len(m.UuidsToRegIdx) > 0 { for iNdEx := len(m.UuidsToRegIdx) - 1; iNdEx >= 0; iNdEx-- { { @@ -15970,6 +16041,12 @@ func (m *Instruction) ProtoSize() (n int) { if m.ProjectionGroupingSetCount != 0 { n += 2 + sovPipeline(uint64(m.ProjectionGroupingSetCount)) } + if m.TopOrderedOutput { + n += 3 + } + if m.MergeTopOrderedStreams { + n += 3 + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -16392,6 +16469,9 @@ func (m *Pipeline) ProtoSize() (n int) { n += 1 + l + sovPipeline(uint64(l)) } } + if len(m.OrderedStream) > 0 { + n += 1 + sovPipeline(uint64(len(m.OrderedStream))) + len(m.OrderedStream)*1 + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -31591,6 +31671,46 @@ func (m *Instruction) Unmarshal(dAtA []byte) error { break } } + case 62: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TopOrderedOutput", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TopOrderedOutput = bool(v != 0) + case 63: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MergeTopOrderedStreams", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.MergeTopOrderedStreams = bool(v != 0) default: iNdEx = preIndex skippy, err := skipPipeline(dAtA[iNdEx:]) @@ -34684,6 +34804,76 @@ func (m *Pipeline) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 15: + if wireType == 0 { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.OrderedStream = append(m.OrderedStream, bool(v != 0)) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthPipeline + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthPipeline + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen + if elementCount != 0 && len(m.OrderedStream) == 0 { + m.OrderedStream = make([]bool, 0, elementCount) + } + for iNdEx < postIndex { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.OrderedStream = append(m.OrderedStream, bool(v != 0)) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field OrderedStream", wireType) + } default: iNdEx = preIndex skippy, err := skipPipeline(dAtA[iNdEx:]) diff --git a/pkg/sql/colexec/mergetop/stream.go b/pkg/sql/colexec/mergetop/stream.go new file mode 100644 index 0000000000000..b44ef80f7302c --- /dev/null +++ b/pkg/sql/colexec/mergetop/stream.go @@ -0,0 +1,473 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mergetop + +import ( + "container/heap" + "time" + + "github.com/matrixorigin/matrixone/pkg/common/moerr" + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/compare" + "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + plan2 "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/sql/colexec" + "github.com/matrixorigin/matrixone/pkg/vm" + "github.com/matrixorigin/matrixone/pkg/vm/process" +) + +const ( + mergeTopStreamBatchRows = 8192 + mergeTopStreamBatchBytes = 64 * mpool.MB +) + +type orderedStream struct { + receiver *process.PipelineSignalReceiver + bat *batch.Batch + orderCols []*vector.Vector + ownedCols []bool + row int64 + ended bool +} + +type streamContainer struct { + limit uint64 + emitted uint64 + limitExecutor colexec.ExpressionExecutor + executors []colexec.ExpressionExecutor + compares []compare.Compare + streams []orderedStream + heap streamHeap + output *batch.Batch + primed bool + done bool + // A per-instance seam for byte-boundary tests; zero uses the normal window. + outputByteLimit int +} + +func (ctr *streamContainer) outputBytesLimit() int { + limit := mergeTopStreamBatchBytes + if ctr.outputByteLimit > 0 { + limit = min(limit, ctr.outputByteLimit) + } + // Payload chunks must remain feasible with a reduced allocator ceiling. + // Reserve slack for vector growth, headers and simultaneous old/new buffers. + return min(limit, int(mpool.MaxAllocationSize()/4)) +} + +type streamHeap struct { + ctr *streamContainer + items []int +} + +func (h *streamHeap) Len() int { return len(h.items) } +func (h *streamHeap) Less(i, j int) bool { + left, right := h.items[i], h.items[j] + r := h.ctr.compareRows(left, right) + if r == 0 { + return left < right + } + return r < 0 +} +func (h *streamHeap) Swap(i, j int) { h.items[i], h.items[j] = h.items[j], h.items[i] } +func (h *streamHeap) Push(x any) { h.items = append(h.items, x.(int)) } +func (h *streamHeap) Pop() any { + n := len(h.items) - 1 + x := h.items[n] + h.items = h.items[:n] + return x +} + +func (mergeTop *MergeTop) prepareStream(proc *process.Process) (err error) { + ctr := mergeTop.ctr.stream + if ctr == nil { + ctr = &streamContainer{} + mergeTop.ctr.stream = ctr + } + ctr.emitted = 0 + ctr.primed = false + ctr.done = false + ctr.heap = streamHeap{ctr: ctr} + + if ctr.limitExecutor == nil { + ctr.limitExecutor, err = colexec.NewExpressionExecutorWithAllocation( + proc, mergeTop.Limit, mergeTop.ctr.expressionAllocation) + if err != nil { + return err + } + } + vec, err := ctr.limitExecutor.Eval( + proc, []*batch.Batch{batch.EmptyForConstFoldBatch}, nil) + if err != nil { + return err + } + ctr.limit = vector.MustFixedColWithTypeCheck[uint64](vec)[0] + + if len(ctr.executors) != len(mergeTop.Fs) { + for _, executor := range ctr.executors { + if executor != nil { + executor.Free() + } + } + ctr.executors = make([]colexec.ExpressionExecutor, len(mergeTop.Fs)) + } + for i := range mergeTop.Fs { + if ctr.executors[i] == nil { + ctr.executors[i], err = colexec.NewExpressionExecutorWithAllocation( + proc, mergeTop.Fs[i].Expr, mergeTop.ctr.expressionAllocation) + if err != nil { + return err + } + } + } + ctr.compares = make([]compare.Compare, len(mergeTop.Fs)) + for i, spec := range mergeTop.Fs { + desc := spec.Flag&plan2.OrderBySpec_DESC != 0 + nullsLast := desc + if spec.Flag&plan2.OrderBySpec_NULLS_FIRST != 0 { + nullsLast = false + } else if spec.Flag&plan2.OrderBySpec_NULLS_LAST != 0 { + nullsLast = true + } + t := spec.Expr.Typ + typ := types.NewWithCharset(types.T(t.Id), t.Width, t.Scale, uint8(t.Charset)) + ctr.compares[i] = compare.NewOrder(typ, desc, nullsLast) + } + + ctr.streams = make([]orderedStream, len(proc.Reg.MergeReceivers)) + for i, reg := range proc.Reg.MergeReceivers { + ctr.streams[i].receiver = process.InitPipelineSignalReceiverFromProcess( + proc, []*process.WaitRegister{reg}) + } + return nil +} + +func (mergeTop *MergeTop) callStream( + proc *process.Process, + analyzer process.Analyzer, +) (vm.CallResult, error) { + ctr := mergeTop.ctr.stream + if ctr == nil { + return vm.CancelResult, nil + } + if ctr.output != nil { + ctr.output.Clean(proc.Mp()) + ctr.output = nil + } + if ctr.done || ctr.limit == 0 { + ctr.done = true + return vm.CancelResult, nil + } + if !ctr.primed { + for i := range ctr.streams { + if err := ctr.loadNextBatch(proc, analyzer, i); err != nil { + return vm.CancelResult, err + } + if !ctr.streams[i].ended { + ctr.heap.items = append(ctr.heap.items, i) + } + } + heap.Init(&ctr.heap) + ctr.primed = true + } + if ctr.heap.Len() == 0 { + ctr.done = true + return vm.CancelResult, nil + } + + first := ctr.streams[ctr.heap.items[0]].bat + var err error + ctr.output, err = proc.NewBatchFromSrcWithAllocation( + first, 0, mergeTop.ctr.retainedAllocation) + if err != nil { + return vm.CancelResult, err + } + + remaining := ctr.limit - ctr.emitted + maxRows := uint64(mergeTopStreamBatchRows) + if remaining < maxRows { + maxRows = remaining + } + rows := 0 + for uint64(rows) < maxRows && ctr.heap.Len() > 0 { + winner := ctr.heap.items[0] + stream := &ctr.streams[winner] + if rows > 0 && ctr.output.Size()+stream.rowBytes() > ctr.outputBytesLimit() { + break + } + chunk := ctr.winnerChunk(winner, int(maxRows)-rows) + for col := range ctr.output.Vecs { + if err = ctr.output.Vecs[col].UnionBatch( + stream.bat.Vecs[col], stream.row, chunk, nil, proc.Mp()); err != nil { + return vm.CancelResult, err + } + } + rows += chunk + ctr.emitted += uint64(chunk) + stream.row += int64(chunk) + if stream.row >= int64(stream.bat.RowCount()) { + if err = ctr.loadNextBatch(proc, analyzer, winner); err != nil { + return vm.CancelResult, err + } + if stream.ended { + heap.Pop(&ctr.heap) + } else { + heap.Fix(&ctr.heap, 0) + } + } else { + heap.Fix(&ctr.heap, 0) + } + if ctr.output.Size() >= ctr.outputBytesLimit() { + break + } + } + ctr.output.SetRowCount(rows) + if ctr.emitted >= ctr.limit || ctr.heap.Len() == 0 { + ctr.done = true + } + result := vm.NewCallResult() + result.Batch = ctr.output + if !ctr.done { + result.Status = vm.ExecHasMore + } + return result, nil +} + +func (stream *orderedStream) rowBytes() int { + return stream.rowBytesAt(stream.row) +} + +func (stream *orderedStream) rowBytesAt(row int64) int { + bytes := 0 + for _, vec := range stream.bat.Vecs { + typ := vec.GetType() + bytes += typ.TypeSize() + if typ.IsVarlen() { + bytes += len(vec.GetBytesAt(int(row))) + } + } + return bytes +} + +// winnerChunk batches contiguous winner rows while preserving both the k-way +// merge boundary and the 64 MiB output budget. Varlen rows use their actual +// payload lengths rather than an average-size estimate. +func (ctr *streamContainer) winnerChunk(winner, maxRows int) int { + stream := &ctr.streams[winner] + remaining := stream.bat.RowCount() - int(stream.row) + if remaining < maxRows { + maxRows = remaining + } + if maxRows <= 1 { + return 1 + } + rowBytes := 0 + fixedWidth := true + for _, vec := range stream.bat.Vecs { + typ := vec.GetType() + if typ.IsVarlen() { + fixedWidth = false + break + } + rowBytes += typ.TypeSize() + } + if maxRows > 256 { + maxRows = 256 + } + if fixedWidth { + if rowBytes <= 0 { + return 1 + } + budgetRows := (ctr.outputBytesLimit() - ctr.output.Size()) / rowBytes + if budgetRows < 1 { + return 1 + } + if maxRows > budgetRows { + maxRows = budgetRows + } + } else { + budget := ctr.outputBytesLimit() - ctr.output.Size() + used := 0 + budgetRows := 0 + for budgetRows < maxRows { + next := stream.rowBytesAt(stream.row + int64(budgetRows)) + if budgetRows > 0 && used+next > budget { + break + } + used += next + budgetRows++ + } + if budgetRows < 1 { + return 1 + } + maxRows = budgetRows + } + if ctr.heap.Len() == 1 { + return maxRows + } + + second := ctr.heap.items[1] + if ctr.heap.Len() > 2 && ctr.compareRows(ctr.heap.items[2], second) < 0 { + second = ctr.heap.items[2] + } + start := stream.row + chunk := 1 + for chunk < maxRows { + stream.row = start + int64(chunk) + if ctr.compareRows(winner, second) > 0 { + break + } + chunk++ + } + stream.row = start + return chunk +} + +func (ctr *streamContainer) loadNextBatch( + proc *process.Process, + analyzer process.Analyzer, + idx int, +) error { + stream := &ctr.streams[idx] + ctr.freeOrderCols(proc, stream) + for { + bat, err := stream.receiver.GetNextBatch(analyzer) + if err != nil { + return err + } + if bat == nil { + if cancelErr, canceled := vm.CancelCheck(proc); canceled { + return cancelErr + } + stream.bat = nil + stream.ended = true + return nil + } + if bat.IsEmpty() { + continue + } + stream.bat = bat + stream.row = 0 + stream.ended = false + stream.orderCols = make([]*vector.Vector, len(ctr.executors)) + stream.ownedCols = make([]bool, len(ctr.executors)) + for i, executor := range ctr.executors { + if executor.IsColumnExpr() { + col := executor.(*colexec.ColumnExpressionExecutor).GetColIndex() + if col < 0 || col >= len(bat.Vecs) { + return moerr.NewInternalErrorf(proc.Ctx, + "merge top ordered stream column %d out of range [0,%d)", + col, len(bat.Vecs)) + } + stream.orderCols[i] = bat.Vecs[col] + continue + } + vec, evalErr := executor.EvalWithoutResultReusing( + proc, []*batch.Batch{bat}, nil) + if evalErr != nil { + ctr.freeOrderCols(proc, stream) + return evalErr + } + stream.orderCols[i] = vec + stream.ownedCols[i] = true + } + return nil + } +} + +func (ctr *streamContainer) compareRows(left, right int) int { + l, r := &ctr.streams[left], &ctr.streams[right] + for i, cmp := range ctr.compares { + cmp.Set(0, l.orderCols[i]) + cmp.Set(1, r.orderCols[i]) + if result := cmp.Compare(0, 1, l.row, r.row); result != 0 { + return result + } + } + return 0 +} + +func (ctr *streamContainer) freeOrderCols(proc *process.Process, stream *orderedStream) { + for i, col := range stream.orderCols { + if i < len(stream.ownedCols) && stream.ownedCols[i] && col != nil { + col.Free(proc.Mp()) + } + } + stream.orderCols = nil + stream.ownedCols = nil +} + +func (ctr *container) resetStream( + proc *process.Process, + pipelineFailed bool, + err error, +) { + if ctr.stream == nil { + return + } + s := ctr.stream + cleanupDeadline := time.Now().Add(process.PipelineCleanupWaitTimeout) + if s.output != nil { + s.output.Clean(proc.Mp()) + s.output = nil + } + for i := range s.streams { + s.freeOrderCols(proc, &s.streams[i]) + remaining := time.Until(cleanupDeadline) + if remaining <= 0 { + remaining = time.Nanosecond + } + if receiver := s.streams[i].receiver; receiver != nil && + !receiver.WaitingEndWithTimeout(remaining) { + state := receiver.State() + process.WarnPipelineCleanupf( + proc, + "merge_top_stream_cleanup_wait_end_timeout", + "merge top stream cleanup timed out: timeout=%s stream=%d alive=%d nil_batch_count=%v channel_len=%v channel_cap=%v pipeline_failed=%t err=%v", + process.PipelineCleanupWaitTimeout, + i, + state.Alive, + state.NilBatches, + state.ChannelLen, + state.ChannelCap, + pipelineFailed, + err) + } + } + s.streams = nil + s.heap.items = nil + s.primed = false + s.done = false + s.emitted = 0 + for i, executor := range s.executors { + if executor != nil { + executor.Free() + s.executors[i] = nil + } + } + s.executors = nil + if s.limitExecutor != nil { + s.limitExecutor.Free() + s.limitExecutor = nil + } + s.compares = nil + ctr.stream = nil +} + +func (ctr *container) freeStream(proc *process.Process) { + ctr.resetStream(proc, false, nil) +} diff --git a/pkg/sql/colexec/mergetop/top.go b/pkg/sql/colexec/mergetop/top.go index 91737b76a405f..b7009409f8d6d 100644 --- a/pkg/sql/colexec/mergetop/top.go +++ b/pkg/sql/colexec/mergetop/top.go @@ -58,6 +58,9 @@ func (mergeTop *MergeTop) Prepare(proc *process.Process) (err error) { } else { mergeTop.OpAnalyzer.Reset() } + if mergeTop.OrderedStreams { + return mergeTop.prepareStream(proc) + } // limit executor if mergeTop.ctr.limitExecutor == nil { @@ -116,6 +119,9 @@ func (mergeTop *MergeTop) Call(proc *process.Process) ( err = mergeTopTerminalCapacityError(proc.Ctx, err) }() analyzer := mergeTop.OpAnalyzer + if mergeTop.OrderedStreams { + return mergeTop.callStream(proc, analyzer) + } result = vm.NewCallResult() if mergeTop.ctr.limit == 0 { diff --git a/pkg/sql/colexec/mergetop/top_test.go b/pkg/sql/colexec/mergetop/top_test.go index 671e8a028c70c..e4138bfc658ad 100644 --- a/pkg/sql/colexec/mergetop/top_test.go +++ b/pkg/sql/colexec/mergetop/top_test.go @@ -17,17 +17,21 @@ package mergetop import ( "bytes" "context" + "fmt" "math" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/common/mpool" "github.com/matrixorigin/matrixone/pkg/container/batch" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/colexec" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/top" plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" "github.com/matrixorigin/matrixone/pkg/testutil" "github.com/matrixorigin/matrixone/pkg/vm" @@ -148,6 +152,346 @@ func TestMergeTopMaxUint64LimitReturnsAllRows(t *testing.T) { require.Equal(t, int64(0), tc.proc.Mp().CurrNB()) } +func TestMergeTopOrderedStreamsMergesWithoutRetainingLimitRows(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + proc.Reg.MergeReceivers = []*process.WaitRegister{ + process.NewPipelineEdge(4, 1), + process.NewPipelineEdge(4, 1), + } + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(4)). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(0)}}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + t.Cleanup(func() { + if t.Failed() { + for _, reg := range proc.Reg.MergeReceivers { + reg.Abort(context.Canceled) + } + } + arg.Reset(proc, t.Failed(), nil) + arg.Free(proc, t.Failed(), nil) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) + }) + + // Exercise the actual producer/consumer boundary, including resident Top + // output: an ordered edge must not depend on the producer's storage mode. + for i, values := range [][]int64{{5, 1, 3}, {6, 2, 4}} { + func() { + bat := batch.NewWithSize(1) + child := colexec.NewMockOperator().WithBatchs([]*batch.Batch{bat}) + defer child.Free(proc, false, nil) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + for _, value := range values { + require.NoError(t, vector.AppendFixed(bat.Vecs[0], value, false, proc.Mp())) + } + bat.SetRowCount(len(values)) + producer := top.NewArgument().WithLimit(plan2.MakePlan2Uint64ConstExprWithType(3)). + WithFs(arg.Fs).WithOrderedOutput() + producer.AppendChild(child) + defer producer.Release() + defer producer.Free(proc, false, nil) + require.NoError(t, producer.Prepare(proc)) + result, err := producer.Call(proc) + require.NoError(t, err) + copied, err := result.Batch.Dup(proc.Mp()) + require.NoError(t, err) + require.True(t, proc.Reg.MergeReceivers[i].SendDataDirect(proc.Ctx, copied, proc.Mp())) + require.True(t, proc.Reg.MergeReceivers[i].SendEnd()) + }() + } + result, err := arg.Call(proc) + require.NoError(t, err) + require.Equal(t, []int64{1, 2, 3, 4}, + vector.MustFixedColWithTypeCheck[int64](result.Batch.Vecs[0])) + require.Less(t, result.Batch.Size(), mergeTopStreamBatchBytes) +} + +func TestMergeTopOrderedStreamsAcrossBatchBoundariesDescending(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + proc.Reg.MergeReceivers = []*process.WaitRegister{ + process.NewPipelineEdge(4, 1), + process.NewPipelineEdge(4, 1), + process.NewPipelineEdge(1, 1), + } + sendInt64Stream(t, proc, proc.Reg.MergeReceivers[0], []int64{9, 7}, []int64{5, 1}) + sendInt64Stream(t, proc, proc.Reg.MergeReceivers[1], []int64{10, 8, 6}, []int64{4, 2}) + require.True(t, proc.Reg.MergeReceivers[2].SendEnd()) + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(7)). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(0), Flag: plan.OrderBySpec_DESC}}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + result, err := arg.Call(proc) + require.NoError(t, err) + require.Equal(t, []int64{10, 9, 8, 7, 6, 5, 4}, + vector.MustFixedColWithTypeCheck[int64](result.Batch.Vecs[0])) + + arg.Reset(proc, false, nil) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func TestMergeTopOrderedStreamsChunksOutputRows(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + reg := process.NewPipelineEdge(2, 1) + proc.Reg.MergeReceivers = []*process.WaitRegister{reg} + values := make([]int64, mergeTopStreamBatchRows+17) + for i := range values { + values[i] = int64(i) + } + sendInt64Stream(t, proc, reg, values) + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(uint64(len(values)))). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(0)}}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + first, err := arg.Call(proc) + require.NoError(t, err) + require.Equal(t, mergeTopStreamBatchRows, first.Batch.RowCount()) + second, err := arg.Call(proc) + require.NoError(t, err) + require.Equal(t, 17, second.Batch.RowCount()) + require.Equal(t, int64(mergeTopStreamBatchRows), + vector.MustFixedColWithTypeCheck[int64](second.Batch.Vecs[0])[0]) + + arg.Reset(proc, false, nil) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func TestMergeTopOrderedStreamsVarlenNullsFirst(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + proc.Reg.MergeReceivers = []*process.WaitRegister{ + process.NewPipelineEdge(2, 1), + process.NewPipelineEdge(2, 1), + } + sendStringStream(t, proc, proc.Reg.MergeReceivers[0], + []string{"", "a", "c"}, []bool{true, false, false}) + sendStringStream(t, proc, proc.Reg.MergeReceivers[1], + []string{"", "b", "d"}, []bool{true, false, false}) + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(6)). + WithFs([]*plan.OrderBySpec{{ + Expr: newStringExpression(0), + Flag: plan.OrderBySpec_NULLS_FIRST, + }}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + result, err := arg.Call(proc) + require.NoError(t, err) + require.Equal(t, 6, result.Batch.RowCount()) + require.True(t, result.Batch.Vecs[0].IsNull(0)) + require.True(t, result.Batch.Vecs[0].IsNull(1)) + for row, expected := range []string{"a", "b", "c", "d"} { + require.Equal(t, expected, + string(result.Batch.Vecs[0].GetBytesAt(row+2))) + } + + arg.Reset(proc, false, nil) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func TestMergeTopOrderedStreamsVarlenByteBoundary(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + proc.Reg.MergeReceivers = []*process.WaitRegister{ + process.NewPipelineEdge(2, 1), process.NewPipelineEdge(2, 1), + } + const byteLimit = 1024 + expected := make([]string, 20) + for i := range expected { + expected[i] = fmt.Sprintf("%04d%s", i, bytes.Repeat([]byte("x"), 130+i)) + } + // An indivisible valid row may exceed the normal output window. + expected[19] += string(bytes.Repeat([]byte("y"), 2*byteLimit)) + for parity := range 2 { + var values []string + for i := parity; i < len(expected); i += 2 { + values = append(values, expected[i]) + } + sendStringStream(t, proc, proc.Reg.MergeReceivers[parity], values, make([]bool, len(values))) + } + arg := NewArgument().WithLimit(plan2.MakePlan2Uint64ConstExprWithType(uint64(len(expected)))). + WithFs([]*plan.OrderBySpec{{Expr: newStringExpression(0)}}).WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + arg.ctr.stream.outputByteLimit = byteLimit + var got []string + batches := 0 + for { + result, err := arg.Call(proc) + require.NoError(t, err) + if result.Batch == nil { + break + } + batches++ + if result.Batch.Size() > byteLimit { + require.Equal(t, 1, result.Batch.RowCount()) + } + for row := range result.Batch.RowCount() { + got = append(got, string(result.Batch.Vecs[0].GetBytesAt(row))) + } + } + require.Greater(t, batches, 2) + require.Equal(t, expected, got) + arg.Reset(proc, false, nil) + arg.Free(proc, false, nil) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func TestMergeTopOrderedStreamsPropagatesTerminalError(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + proc.Reg.MergeReceivers = []*process.WaitRegister{ + process.NewPipelineEdge(1, 1), + process.NewPipelineEdge(1, 1), + } + require.True(t, proc.Reg.MergeReceivers[0].SendError(context.Canceled)) + require.True(t, proc.Reg.MergeReceivers[1].SendEnd()) + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(1)). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(0)}}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + _, err := arg.Call(proc) + require.ErrorIs(t, err, context.Canceled) + + arg.Reset(proc, true, err) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func TestMergeTopOrderedStreamsPreservesQueryTerminalOverLateEdgeFailure(t *testing.T) { + tests := []struct { + name string + buildContext func(*process.Process) (context.Context, func()) + wantErr error + }{ + { + name: "query cancellation", + buildContext: func(proc *process.Process) (context.Context, func()) { + queryCtx := proc.Base.GetContextBase().BuildQueryCtx(proc.GetTopContext()) + _, cancel := process.GetQueryCtxFromProc(proc) + return queryCtx, cancel + }, + wantErr: context.Canceled, + }, + { + name: "query deadline", + buildContext: func(proc *process.Process) (context.Context, func()) { + parentCtx, cancel := context.WithDeadline( + proc.GetTopContext(), time.Now().Add(-time.Second)) + return proc.Base.GetContextBase().BuildQueryCtx(parentCtx), cancel + }, + wantErr: context.DeadlineExceeded, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + queryCtx, cancelQuery := test.buildContext(proc) + proc.BuildPipelineContext(queryCtx) + edge := process.NewPipelineEdge(1, 1) + proc.Reg.MergeReceivers = []*process.WaitRegister{edge} + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(1)). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(0)}}). + WithOrderedStreams() + var callErr error + t.Cleanup(func() { + arg.Reset(proc, callErr != nil, callErr) + arg.Release() + proc.Cancel(nil) + cancelQuery() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) + }) + + require.NoError(t, arg.Prepare(proc)) + edge.Ch2 <- process.NewPipelineSignalToDirectly(batch.EmptyBatch, nil, nil) + cancelQuery() + lateErr := moerr.NewDuplicateEntryNoCtx("ordered-top", "primary") + require.False(t, edge.SendError(lateErr)) + + _, callErr = arg.Call(proc) + require.ErrorIs(t, callErr, test.wantErr) + require.NotErrorIs(t, callErr, lateErr) + }) + } +} + +func TestMergeTopOrderedStreamsRejectsInvalidOrderColumn(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + reg := process.NewPipelineEdge(2, 1) + proc.Reg.MergeReceivers = []*process.WaitRegister{reg} + sendInt64Stream(t, proc, reg, []int64{1}) + + arg := NewArgument(). + WithLimit(plan2.MakePlan2Uint64ConstExprWithType(1)). + WithFs([]*plan.OrderBySpec{{Expr: newExpression(1)}}). + WithOrderedStreams() + require.NoError(t, arg.Prepare(proc)) + _, err := arg.Call(proc) + require.ErrorContains(t, err, "column 1 out of range") + + arg.Reset(proc, true, err) + arg.Release() + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + +func sendInt64Stream( + t *testing.T, + proc *process.Process, + reg *process.WaitRegister, + batches ...[]int64, +) { + t.Helper() + for _, values := range batches { + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + for _, value := range values { + require.NoError(t, vector.AppendFixed(bat.Vecs[0], value, false, proc.Mp())) + } + bat.SetRowCount(len(values)) + require.True(t, reg.SendDataDirect(proc.Ctx, bat, proc.Mp())) + } + require.True(t, reg.SendEnd()) +} + +func sendStringStream( + t *testing.T, + proc *process.Process, + reg *process.WaitRegister, + values []string, + nulls []bool, +) { + t.Helper() + require.Len(t, nulls, len(values)) + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_varchar.ToType()) + for i, value := range values { + require.NoError(t, + vector.AppendBytes(bat.Vecs[0], []byte(value), nulls[i], proc.Mp())) + } + bat.SetRowCount(len(values)) + require.True(t, reg.SendDataDirect(proc.Ctx, bat, proc.Mp())) + require.True(t, reg.SendEnd()) +} + func TestMergeTopEmitsFinalBatchOnce(t *testing.T) { tc := newTestCase(t, []bool{false}, []types.Type{types.T_int64.ToType()}, 1, []*plan.OrderBySpec{{Expr: newExpression(0)}}) @@ -390,6 +734,12 @@ func newExpression(pos int32) *plan.Expr { } } +func newStringExpression(pos int32) *plan.Expr { + expr := newExpression(pos) + expr.Typ.Id = int32(types.T_varchar) + return expr +} + // create a new block based on the type information, ds[i] == true: in descending order func newBatch(ts []types.Type, proc *process.Process, rows int64) *batch.Batch { return testutil.NewBatch(ts, false, int(rows), proc.Mp()) diff --git a/pkg/sql/colexec/mergetop/types.go b/pkg/sql/colexec/mergetop/types.go index e4e855b6b25e0..bd22ff5bba263 100644 --- a/pkg/sql/colexec/mergetop/types.go +++ b/pkg/sql/colexec/mergetop/types.go @@ -59,12 +59,17 @@ type container struct { retainedAllocation *vector.AllocationAccountSelection expressionAllocation *vector.AllocationAccountSelection appendScratch *mpool.AccountedBuffer + + // stream is non-nil when MergeTop is the leaf of an ordered merge scope. + // The legacy child mode is retained for decoded plans produced by older CNs. + stream *streamContainer } type MergeTop struct { - Limit *plan.Expr // Limit store the number of mergeTop-operator - ctr container // ctr stores the attributes needn't do Serialization work - Fs []*plan.OrderBySpec // Fs store the order information + Limit *plan.Expr // Limit store the number of mergeTop-operator + ctr container // ctr stores the attributes needn't do Serialization work + Fs []*plan.OrderBySpec // Fs store the order information + OrderedStreams bool // consume one sorted stream per merge receiver vm.OperatorBase } @@ -104,6 +109,11 @@ func (mergeTop *MergeTop) WithFs(fs []*plan.OrderBySpec) *MergeTop { return mergeTop } +func (mergeTop *MergeTop) WithOrderedStreams() *MergeTop { + mergeTop.OrderedStreams = true + return mergeTop +} + func (mergeTop *MergeTop) Release() { if mergeTop != nil { reuse.Free(mergeTop, nil) @@ -111,10 +121,12 @@ func (mergeTop *MergeTop) Release() { } func (mergeTop *MergeTop) Reset(proc *process.Process, pipelineFailed bool, err error) { + mergeTop.ctr.resetStream(proc, pipelineFailed, err) mergeTop.ctr.reset(proc) } func (mergeTop *MergeTop) Free(proc *process.Process, pipelineFailed bool, err error) { + mergeTop.ctr.freeStream(proc) mergeTop.ctr.free(proc) } @@ -150,7 +162,7 @@ func (ctr *container) setAllocationAccount(account *mpool.AllocationAccount) err } return mpool.ErrAllocationAccountMismatch } - if ctr.bat != nil || ctr.limitExecutor != nil || ctr.appendScratch != nil || + if ctr.bat != nil || ctr.limitExecutor != nil || ctr.appendScratch != nil || ctr.stream != nil || len(ctr.executorsForOrderList) != 0 || cap(ctr.sels) != 0 { return mpool.ErrAllocationAccountInvariant } @@ -189,7 +201,7 @@ func (ctr *container) clearAllocationAccount(account *mpool.AllocationAccount) e if ctr.allocationAccount != account { return mpool.ErrAllocationAccountMismatch } - if ctr.bat != nil || ctr.limitExecutor != nil || ctr.appendScratch != nil || + if ctr.bat != nil || ctr.limitExecutor != nil || ctr.appendScratch != nil || ctr.stream != nil || len(ctr.executorsForOrderList) != 0 || cap(ctr.sels) != 0 { return mpool.ErrAllocationAccountInvariant } diff --git a/pkg/sql/colexec/top/allocation_account_test.go b/pkg/sql/colexec/top/allocation_account_test.go index 9d68af24f7e60..b96388e913b02 100644 --- a/pkg/sql/colexec/top/allocation_account_test.go +++ b/pkg/sql/colexec/top/allocation_account_test.go @@ -15,7 +15,6 @@ package top import ( - "bytes" "context" "testing" @@ -317,17 +316,11 @@ func TestAccountedTopPrepareExactCapacityBoundary(t *testing.T) { func TestAccountedTopRuntimeCapacityRejectionCleans(t *testing.T) { proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) - op := newAccountedTop(3) + // Fixed-width input retains the resident allocation-rejection contract; + // varlen payload now takes the spill-resource admission path. + op := newAccountedTop(8192) state := installTopTestAllocation(t, op, proc, 64<<10) - src := batch.NewWithSize(1) - src.Vecs[0] = vector.NewVec(types.T_text.ToType()) - require.NoError(t, vector.AppendBytes( - src.Vecs[0], - bytes.Repeat([]byte("x"), 1<<20), - false, - proc.Mp(), - )) - src.SetRowCount(1) + src := newInt64TopBatch(t, proc, make([]int64, 8192)) child := colexec.NewMockOperator().WithBatchs([]*batch.Batch{src}) op.AppendChild(child) require.NoError(t, op.Prepare(proc)) @@ -342,6 +335,43 @@ func TestAccountedTopRuntimeCapacityRejectionCleans(t *testing.T) { require.Zero(t, proc.Mp().CurrNB()) } +func TestAccountedTopSmallVarlenSpillRejectionCleans(t *testing.T) { + testAccountedTopSmallVarlenSpillRejectionCleans(t, false) +} + +func TestAccountedTopOrderedSmallVarlenSpillRejectionCleans(t *testing.T) { + testAccountedTopSmallVarlenSpillRejectionCleans(t, true) +} + +func testAccountedTopSmallVarlenSpillRejectionCleans(t *testing.T, ordered bool) { + t.Helper() + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + op := newAccountedTop(3) + op.OrderedOutput = ordered + state := installTopTestAllocation(t, op, proc, 64<<20) + // Exhaust disk before actual winner pressure triggers resident migration. + op.ctr.residentByteLimit = 512 << 10 + blocker, err := state.generation.ReserveSpillDisk(state.generation.SpillDiskCap()) + require.NoError(t, err) + t.Cleanup(func() { blocker.Release() }) + child := colexec.NewMockOperator().WithBatchs([]*batch.Batch{ + newNullableVarcharTopBatch(t, proc, 1, make([]byte, 1<<20), false), + }) + op.AppendChild(child) + require.NoError(t, op.Prepare(proc)) + require.False(t, op.ctr.spilling) + _, err = vm.Exec(op, proc) + var resourceErr *process.ExecutionResourceError + require.ErrorAs(t, err, &resourceErr) + require.Equal(t, process.ExecutionResourceComponentSpillDisk, resourceErr.Component) + child.Free(proc, true, err) + op.Free(proc, true, err) + blocker.Release() + finalizeTopTestAllocation(t, op, state) + proc.Free() + require.Zero(t, proc.Mp().CurrNB()) +} + func TestAccountedTopSpillResourceAdmissionCleans(t *testing.T) { tests := []struct { name string @@ -439,9 +469,10 @@ func TestAccountedTopCorruptSpillFailsClosed(t *testing.T) { require.NoError(t, err) op.ctr.sels[0] = 0 op.ctr.rowRefs[0] = rowRef{ - offset: record.offset, - size: record.size - 1, - rowIdx: 0, + offset: record.offset, + size: record.size - 1, + rowIdx: 0, + outputBytes: uint64(types.T_int64.ToType().TypeSize()), } op.ctr.spillOrdered = true var result vm.CallResult diff --git a/pkg/sql/colexec/top/resident_test.go b/pkg/sql/colexec/top/resident_test.go new file mode 100644 index 0000000000000..a58c64557d176 --- /dev/null +++ b/pkg/sql/colexec/top/resident_test.go @@ -0,0 +1,284 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package top + +import ( + "bytes" + "context" + "fmt" + "io" + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/vm" + "github.com/stretchr/testify/require" +) + +func TestTopOrderedResidentDoesNotRequireSpillResources(t *testing.T) { + for _, resource := range []string{"disk", "fd"} { + t.Run(resource, func(t *testing.T) { + expr := newExpression(0) + expr.Typ.Id = int32(types.T_varchar) + tc := newTestCase(t, mpool.MustNewZero(), []types.Type{types.T_varchar.ToType()}, 2, + []*plan.OrderBySpec{{Expr: expr}}) + tc.arg.WithOrderedOutput() + // Twelve improving keys cross this window; the two live winners fit. + // Reclamation, not input-sized replacement history, must bound memory. + tc.arg.ctr.residentByteLimit = 1024 + state := installTopTestAllocation(t, tc.arg, tc.proc, 4<<20) + t.Cleanup(func() { + if tc.arg.NumChildren() > 0 { + tc.arg.GetChildren(0).Free(tc.proc, false, nil) + } + tc.arg.Free(tc.proc, false, nil) + finalizeTopTestAllocation(t, tc.arg, state) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) + }) + if resource == "disk" { + blocker, err := state.generation.ReserveSpillDisk(state.generation.SpillDiskCap()) + require.NoError(t, err) + t.Cleanup(func() { blocker.Release() }) + } else { + blocker, err := state.generation.ReserveSpillFD(state.generation.SpillFDCap()) + require.NoError(t, err) + t.Cleanup(func() { blocker.Release() }) + } + for range 2 { + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_varchar.ToType()) + resetChildren(tc.arg, []*batch.Batch{bat}) + for key := 12; key > 0; key-- { + value := fmt.Sprintf("%02d%s", key, bytes.Repeat([]byte("x"), 62)) + require.NoError(t, vector.AppendBytes(bat.Vecs[0], []byte(value), false, tc.proc.Mp())) + } + bat.SetRowCount(12) + require.NoError(t, tc.arg.Prepare(tc.proc)) + result, err := vm.Exec(tc.arg, tc.proc) + require.NoError(t, err) + require.NotNil(t, result.Batch) + require.Equal(t, 2, result.Batch.RowCount()) + for row := range 2 { + require.Equal(t, fmt.Sprintf("%02d%s", row+1, bytes.Repeat([]byte("x"), 62)), + string(result.Batch.Vecs[0].GetBytesAt(row))) + } + require.False(t, tc.arg.ctr.spilling) + require.Nil(t, tc.arg.ctr.spillFile) + require.Zero(t, tc.arg.ctr.spillOffset) + require.LessOrEqual(t, tc.arg.ctr.residentBytes, uint64(1024)) + result, err = vm.Exec(tc.arg, tc.proc) + require.NoError(t, err) + require.Nil(t, result.Batch) + tc.arg.GetChildren(0).Free(tc.proc, false, nil) + tc.arg.Reset(tc.proc, false, nil) + require.Zero(t, state.account.Snapshot().Used) + } + }) + } +} + +func TestTopResidentMigrationFailureKeepsOwner(t *testing.T) { + for _, failure := range []string{"cancel", "short write"} { + t.Run(failure, func(t *testing.T) { + tc := newTestCase(t, mpool.MustNewZero(), []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, 1, []*plan.OrderBySpec{{Expr: newExpression(0)}}) + state := installTopTestAllocation(t, tc.arg, tc.proc, 4<<20) + require.NoError(t, tc.arg.Prepare(tc.proc)) + first := newNullableVarcharTopBatch(t, tc.proc, 1, bytes.Repeat([]byte("x"), 64), false) + tc.arg.ctr.n = 2 + require.NoError(t, tc.arg.ctr.build(tc.arg, first, tc.proc, tc.arg.OpAnalyzer)) + owner := tc.arg.ctr.bat + if failure == "cancel" { + ctx, cancel := context.WithCancel(tc.proc.Ctx) + tc.proc.Ctx = ctx + cancel() + } else { + tc.arg.ctr.spillWriter = shortTopSpillWriter{} + } + err := tc.arg.ctr.startResidentSpill(tc.proc, tc.arg.OpAnalyzer) + if failure == "cancel" { + require.ErrorIs(t, err, context.Canceled) + } else { + require.ErrorIs(t, err, io.ErrShortWrite) + } + require.Same(t, owner, tc.arg.ctr.bat) + require.False(t, tc.arg.ctr.spilling) + first.Clean(tc.proc.Mp()) + tc.arg.Free(tc.proc, true, err) + finalizeTopTestAllocation(t, tc.arg, state) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) + }) + } +} + +func TestTopRejectedBatchesDoNotSpill(t *testing.T) { + for _, ordered := range []bool{false, true} { + t.Run(map[bool]string{false: "resident", true: "migrated ordered spill"}[ordered], func(t *testing.T) { + tc := newTestCase(t, mpool.MustNewZero(), []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, 1, []*plan.OrderBySpec{{Expr: newExpression(0)}}) + tc.arg.OrderedOutput = ordered + if ordered { + tc.arg.ctr.residentByteLimit = 32 + } + state := installTopTestAllocation(t, tc.arg, tc.proc, 4<<20) + require.NoError(t, tc.arg.Prepare(tc.proc)) + first := newNullableVarcharTopBatch(t, tc.proc, 1, bytes.Repeat([]byte("x"), 64), false) + tc.arg.ctr.n = 2 + require.NoError(t, tc.arg.ctr.build(tc.arg, first, tc.proc, tc.arg.OpAnalyzer)) + require.Equal(t, ordered, tc.arg.ctr.spilling) + written := tc.arg.ctr.spillOffset + for i := 0; i < 3; i++ { + loser := newNullableVarcharTopBatch(t, tc.proc, 2, bytes.Repeat([]byte("y"), 64), false) + require.NoError(t, tc.arg.ctr.build(tc.arg, loser, tc.proc, tc.arg.OpAnalyzer)) + loser.Clean(tc.proc.Mp()) + require.Equal(t, written, tc.arg.ctr.spillOffset) + } + if !ordered { + require.Zero(t, written) + require.Zero(t, state.generation.SpillFDUsed()) + } + first.Clean(tc.proc.Mp()) + tc.arg.Free(tc.proc, false, nil) + finalizeTopTestAllocation(t, tc.arg, state) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) + }) + } +} + +func TestTopResidentWinnerAdmission(t *testing.T) { + testTopResidentWinnerAdmission(t, false) +} + +func TestTopOrderedResidentWinnerAdmission(t *testing.T) { + testTopResidentWinnerAdmission(t, true) +} + +func testTopResidentWinnerAdmission(t *testing.T, ordered bool) { + t.Helper() + for _, scenario := range []struct { + name string + limit int64 + wideAt int + }{ + {name: "replacement history compacts", limit: 3, wideAt: -1}, + {name: "migrate full heap mid batch", limit: 3, wideAt: 8}, + {name: "migrate partial heap mid batch", limit: 16, wideAt: 3}, + } { + t.Run(scenario.name, func(t *testing.T) { + tc := newTestCase(t, mpool.MustNewZero(), []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, scenario.limit, []*plan.OrderBySpec{{Expr: newExpression(0)}}) + tc.arg.OrderedOutput = ordered + state := installTopTestAllocation(t, tc.arg, tc.proc, 4<<20) + tc.arg.ctr.residentByteLimit = 4096 + for attempt := 0; attempt < 2; attempt++ { + bat := batch.NewWithSize(2) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + bat.Vecs[1] = vector.NewVec(types.T_varchar.ToType()) + for row := 0; row < 128; row++ { + key := int64(128 - row) + size := 64 + if row == scenario.wideAt { + size = 4096 + } + require.NoError(t, vector.AppendFixed(bat.Vecs[0], key, false, tc.proc.Mp())) + require.NoError(t, vector.AppendBytes(bat.Vecs[1], bytes.Repeat([]byte{byte(key)}, size), false, tc.proc.Mp())) + } + bat.SetRowCount(128) + resetChildren(tc.arg, []*batch.Batch{bat}) + require.NoError(t, tc.arg.Prepare(tc.proc)) + var keys []int64 + for { + result, err := vm.Exec(tc.arg, tc.proc) + require.NoError(t, err) + if result.Batch == nil { + break + } + for row := 0; row < result.Batch.RowCount(); row++ { + key := vector.GetFixedAtWithTypeCheck[int64](result.Batch.Vecs[0], row) + keys = append(keys, key) + require.Equal(t, bytes.Repeat([]byte{byte(key)}, 64), result.Batch.Vecs[1].GetBytesAt(row)) + } + } + require.Len(t, keys, int(scenario.limit)) + for i, key := range keys { + require.Equal(t, int64(i+1), key) + } + if scenario.wideAt < 0 { + require.Zero(t, tc.arg.ctr.spillOffset) + require.LessOrEqual(t, tc.arg.ctr.residentBytes, uint64(4096)) + } else { + require.Positive(t, tc.arg.ctr.spillOffset) + } + tc.arg.GetChildren(0).Free(tc.proc, false, nil) + tc.arg.Reset(tc.proc, false, nil) + require.Zero(t, state.account.Snapshot().Used) + require.Zero(t, state.generation.SpillDiskUsed()) + require.Zero(t, state.generation.SpillFDUsed()) + } + tc.arg.Free(tc.proc, false, nil) + finalizeTopTestAllocation(t, tc.arg, state) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) + }) + } +} + +// Same workload as review 5127191041. Input setup/cleanup is not timed. +// Keep this a benchmark, not a million-row ordinary unit test. +func BenchmarkReviewSmallVarlenTop(b *testing.B) { + benchmarkSmallVarlenTop(b, false) +} + +func BenchmarkOrderedSmallVarlenTop(b *testing.B) { + benchmarkSmallVarlenTop(b, true) +} + +func benchmarkSmallVarlenTop(b *testing.B, ordered bool) { + for i := 0; i < b.N; i++ { + b.StopTimer() + tc := newTestCase(b, mpool.MustNewZero(), []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, 1, []*plan.OrderBySpec{{Expr: newExpression(0)}}) + tc.arg.OrderedOutput = ordered + bats := make([]*batch.Batch, 128) + payload := bytes.Repeat([]byte("x"), 64) + for k := range bats { + bat := batch.NewWithSize(2) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + bat.Vecs[1] = vector.NewVec(types.T_varchar.ToType()) + for j := 0; j < 8192; j++ { + require.NoError(b, vector.AppendFixed(bat.Vecs[0], int64(k*8192+j), false, tc.proc.Mp())) + require.NoError(b, vector.AppendBytes(bat.Vecs[1], payload, false, tc.proc.Mp())) + } + bat.SetRowCount(8192) + bats[k] = bat + } + resetChildren(tc.arg, bats) + b.StartTimer() + require.NoError(b, tc.arg.Prepare(tc.proc)) + result, err := vm.Exec(tc.arg, tc.proc) + require.NoError(b, err) + require.NotNil(b, result.Batch) + require.Equal(b, 1, result.Batch.RowCount()) + require.Equal(b, int64(0), vector.GetFixedAtWithTypeCheck[int64](result.Batch.Vecs[0], 0)) + b.StopTimer() + b.ReportMetric(float64(tc.arg.ctr.spillOffset), "spill-bytes/op") + tc.arg.Free(tc.proc, false, nil) + tc.arg.GetChildren(0).Free(tc.proc, false, nil) + tc.proc.Free() + require.Zero(b, tc.proc.Mp().CurrNB()) + } +} diff --git a/pkg/sql/colexec/top/top.go b/pkg/sql/colexec/top/top.go index feba897340a75..5924938d85a15 100644 --- a/pkg/sql/colexec/top/top.go +++ b/pkg/sql/colexec/top/top.go @@ -66,19 +66,25 @@ func growTopSlice[T any]( site mpool.AllocationSite, ) ([]T, error) { if length < len(values) || proc == nil { - return nil, mpool.ErrAllocationAccountInvalid + return values, mpool.ErrAllocationAccountInvalid } if length <= cap(values) { return values[:length], nil } if allocation != nil { - return spillutil.GrowAccountedSlice( + grown, err := spillutil.GrowAccountedSlice( values, length, proc.Mp(), allocation, site, ) + if err != nil { + // Callers assign the returned slice back to its owning field. Failed + // growth leaves the old allocation live and owned until Reset/Free. + return values, err + } + return grown, nil } values = slices.Grow(values, length-len(values)) return values[:length], nil @@ -151,6 +157,9 @@ func (top *Top) Prepare(proc *process.Process) (err error) { top.ctr.topValueZM = objectio.NewZM(types.T(typ.Id), typ.Scale) } + // OrderedOutput is a topology contract, not a storage policy. Both eval + // paths produce sorted rows. Small results use type/actual-byte admission + // in build and migrate only under payload pressure; large K still spills. if top.ctr.limit > topSpillThreshold { top.ctr.spilling = true } @@ -262,6 +271,22 @@ func (ctr *container) build(ap *Top, bat *batch.Batch, proc *process.Process, an } if len(ctr.cmps) == 0 { + // Types can prove the fixed-width fast path. Everything else uses + // actual winner-byte admission, not unconditional full-input spilling. + if !ctr.spilling { + remaining := uint64(evalSpillChunkBytes / 4) + if ctr.limit > 0 { + remaining /= ctr.limit + } + for _, vec := range bat.Vecs { + size := vec.GetType().TypeSize() + if size <= 0 || !vec.GetType().Oid.IsFixedLen() || uint64(size)+1 > remaining { + ctr.boundedResident = true + break + } + remaining -= uint64(size) + 1 + } + } mp := make(map[int]int) for i, pos := range ctr.poses { mp[int(pos)] = i @@ -342,9 +367,187 @@ func (ctr *container) build(ap *Top, bat *batch.Batch, proc *process.Process, an if ctr.spilling { return ctr.processBatchSpill(ap.ctr.limit, bat, proc, analyzer) } + if ctr.boundedResident { + return ctr.processBatchResident(bat, proc, analyzer) + } return ctr.processBatch(ap.ctr.limit, bat, proc) } +func (ctr *container) residentWindow() uint64 { + window := uint64(evalSpillChunkBytes / 4) + if ctr.residentByteLimit != 0 { + window = min(window, ctr.residentByteLimit) + } + return min(window, uint64(mpool.MaxAllocationSize()/8)) +} + +// compactResident preserves row positions (and hence the heap). Admit the new +// allocation while the old one is still owned, and publish only after all +// columns have copied successfully. UnionBatch copies live varlen values only. +func (ctr *container) compactResident(proc *process.Process) error { + next := batch.NewOffHeapWithSize(len(ctr.bat.Vecs)) + defer func() { + if next != nil { + next.Clean(proc.Mp()) + } + }() + for i, vec := range ctr.bat.Vecs { + next.Vecs[i] = vector.NewOffHeapVecWithType(*vec.GetType()) + } + if ctr.retainedAllocation != nil { + if err := next.SetAllocationAccount(ctr.retainedAllocation); err != nil { + return err + } + } + for i := range next.Vecs { + if err := next.Vecs[i].UnionBatch(ctr.bat.Vecs[i], 0, ctr.bat.RowCount(), nil, proc.Mp()); err != nil { + return err + } + } + next.SetRowCount(ctr.bat.RowCount()) + ctr.bat.Clean(proc.Mp()) + ctr.bat, next = next, nil + for i, cmp := range ctr.cmps { + cmp.Set(0, ctr.bat.Vecs[i]) + } + return nil +} + +// startResidentSpill transfers only surviving rows. The pending input row has +// not been mutated or consumed, so the caller resumes it exactly once. On any +// failure the query aborts and Reset/Free still owns all published resources. +func (ctr *container) startResidentSpill(proc *process.Process, analyzer process.Analyzer) error { + keys := batch.NewOffHeapWithSize(len(ctr.poses)) + defer func() { + if keys != nil { + keys.Clean(proc.Mp()) + } + }() + for i, pos := range ctr.poses { + keys.Vecs[i] = vector.NewOffHeapVecWithType(*ctr.bat.Vecs[pos].GetType()) + } + if ctr.retainedAllocation != nil { + if err := keys.SetAllocationAccount(ctr.retainedAllocation); err != nil { + return err + } + } + rows := ctr.bat.RowCount() + if rows > 0 { + record, err := ctr.spillBatch(ctr.bat, proc, analyzer) + if err != nil { + return err + } + ctr.rowRefs, err = growTopSlice(ctr.rowRefs, rows, proc, ctr.spillAllocation, topsites.TopRowReferences) + if err != nil { + return err + } + for i, pos := range ctr.poses { + if err := keys.Vecs[i].UnionBatch(ctr.bat.Vecs[pos], 0, rows, nil, proc.Mp()); err != nil { + return err + } + } + for i := range rows { + width, err := spillOutputRowBytes(ctr.bat, ctr.n, i) + if err != nil { + return err + } + ctr.rowRefs[i] = rowRef{offset: record.offset, size: record.size, rowIdx: int64(i), outputBytes: width} + } + } + keys.SetRowCount(rows) + cmps := make([]compare.Compare, len(ctr.poses)) + for i, pos := range ctr.poses { + cmps[i] = ctr.cmps[pos] + cmps[i].Set(0, keys.Vecs[i]) + } + ctr.bat.Clean(proc.Mp()) + ctr.bat, keys = keys, nil + ctr.cmps = cmps + ctr.spilling = true + ctr.residentBytes = 0 + return nil +} + +func (ctr *container) processBatchResident(bat *batch.Batch, proc *process.Process, analyzer process.Analyzer) error { + for i, cmp := range ctr.cmps { + cmp.Set(1, bat.Vecs[i]) + } + window := ctr.residentWindow() + full := uint64(len(ctr.sels)) == ctr.limit + for row := 0; row < bat.RowCount(); row++ { + if full { + // Reject contiguous losers with only the comparison hot path. Keep + // cancellation outside that loop but bound each scan segment. + if err, canceled := vm.CancelCheck(proc); canceled { + return err + } + end := min(row+evalSpillChunkSize, bat.RowCount()) + for row < end && ctr.compare(1, 0, int64(row), ctr.sels[0]) >= 0 { + row++ + } + if row == end { + row-- // the outer loop advances to the next unchecked row + continue + } + } + if err, canceled := vm.CancelCheck(proc); canceled { + return err + } + width, err := spillOutputRowBytes(bat, len(bat.Vecs), row) + if err != nil { + return err + } + if width > window || ctr.residentBytes > window-width { + var live uint64 + for i := 0; i < ctr.bat.RowCount(); i++ { + n, err := spillOutputRowBytes(ctr.bat, len(ctr.bat.Vecs), i) + if err != nil { + return err + } + live += n + } + if width <= window/2 && live <= window/2-width { + if err := ctr.compactResident(proc); err != nil { + return err + } + ctr.residentBytes = live + } else { + if err := ctr.startResidentSpill(proc, analyzer); err != nil { + return err + } + return ctr.processBatchSpillFrom(ctr.limit, bat, row, proc, analyzer) + } + } + if !full { + position := len(ctr.sels) + ctr.sels, err = growTopSlice(ctr.sels, position+1, proc, ctr.spillAllocation, topsites.TopSelections) + if err != nil { + return err + } + for i, vec := range ctr.bat.Vecs { + if err := vec.UnionOne(bat.Vecs[i], int64(row), proc.Mp()); err != nil { + return err + } + } + ctr.sels[position] = int64(position) + ctr.bat.AddRowCount(1) + if uint64(len(ctr.sels)) == ctr.limit { + ctr.sort() + full = true + } + } else { + for _, cmp := range ctr.cmps { + if err := cmp.Copy(1, 0, int64(row), ctr.sels[0], proc); err != nil { + return err + } + } + heap.Fix(ctr, 0) + } + ctr.residentBytes += width + } + return nil +} + func (ctr *container) processBatch(limit uint64, bat *batch.Batch, proc *process.Process) error { rowCount := bat.RowCount() processCount := rowsToFill(limit, len(ctr.sels), rowCount) @@ -544,15 +747,22 @@ func (ctr *container) spillBatch( } func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *process.Process, analyzer process.Analyzer) error { - record, err := ctr.spillBatch(bat, proc, analyzer) - if err != nil { - return err - } + return ctr.processBatchSpillFrom(limit, bat, 0, proc, analyzer) +} + +func (ctr *container) processBatchSpillFrom(limit uint64, bat *batch.Batch, start int, proc *process.Process, analyzer process.Analyzer) error { + // A complete loser batch must not create a spill file or consume disk. + var record spillRecordRef + var err error rowCount := bat.RowCount() - processCount := rowsToFill(limit, len(ctr.sels), rowCount) + processCount := rowsToFill(limit, len(ctr.sels), rowCount-start) if processCount > 0 { + record, err = ctr.spillBatch(bat, proc, analyzer) + if err != nil { + return err + } if processCount > math.MaxInt-len(ctr.sels) { return moerr.NewInvalidInputNoCtx("top selection count exceeds platform limit") } @@ -581,7 +791,7 @@ func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *pr for idx, pos := range ctr.poses { if err := ctr.bat.Vecs[idx].UnionBatch( bat.Vecs[pos], - 0, + int64(start), processCount, nil, proc.Mp(), @@ -590,12 +800,17 @@ func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *pr } } for i := range processCount { + outputBytes, err := spillOutputRowBytes(bat, ctr.n, start+i) + if err != nil { + return err + } position := baseSel + i ctr.sels[position] = int64(position) ctr.rowRefs[position] = rowRef{ - offset: record.offset, - size: record.size, - rowIdx: int64(i), + offset: record.offset, + size: record.size, + rowIdx: int64(start + i), + outputBytes: outputBytes, } } ctr.bat.AddRowCount(processCount) @@ -605,7 +820,7 @@ func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *pr } } - if processCount == rowCount { + if start+processCount == rowCount { return nil } @@ -613,18 +828,29 @@ func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *pr for idx, pos := range ctr.poses { ctr.cmps[idx].Set(1, bat.Vecs[pos]) } - for i, j := processCount, rowCount; i < j; i++ { + for i, j := start+processCount, rowCount; i < j; i++ { rowIdx := int64(i) if ctr.compare(1, 0, rowIdx, ctr.sels[0]) < 0 { + if record.size == 0 { + record, err = ctr.spillBatch(bat, proc, analyzer) + if err != nil { + return err + } + } + outputBytes, err := spillOutputRowBytes(bat, ctr.n, i) + if err != nil { + return err + } for idx := range ctr.cmps { if err := ctr.cmps[idx].Copy(1, 0, rowIdx, ctr.sels[0], proc); err != nil { return err } } ctr.rowRefs[ctr.sels[0]] = rowRef{ - offset: record.offset, - size: record.size, - rowIdx: int64(i), + offset: record.offset, + size: record.size, + rowIdx: int64(i), + outputBytes: outputBytes, } heap.Fix(ctr, 0) } @@ -632,6 +858,29 @@ func (ctr *container) processBatchSpill(limit uint64, bat *batch.Batch, proc *pr return nil } +func spillOutputRowBytes(bat *batch.Batch, columnCount, row int) (uint64, error) { + if bat == nil || columnCount < 0 || columnCount > len(bat.Vecs) || + row < 0 || row >= bat.RowCount() { + return 0, moerr.NewInternalErrorNoCtx("invalid top spill output row") + } + var total uint64 + for col := 0; col < columnCount; col++ { + vec := bat.Vecs[col] + if vec == nil || vec.GetType() == nil { + return 0, moerr.NewInternalErrorNoCtx("invalid top spill output vector") + } + rowBytes := uint64(vec.GetType().TypeSize()) + if vec.GetType().IsVarlen() && !vec.IsNull(uint64(row)) { + rowBytes += uint64(len(vec.GetBytesAt(row))) + } + if rowBytes > math.MaxUint64-total { + return 0, moerr.NewInvalidInputNoCtx("top spill output row size overflows") + } + total += rowBytes + } + return total, nil +} + func rowsToFill(limit uint64, currentRows int, batchRows int) int { if uint64(currentRows) >= limit { return 0 @@ -675,7 +924,48 @@ func (ctr *container) evalInMemory(limit uint64, n int, proc *process.Process, r return nil } -const evalSpillChunkSize = 8192 +const ( + evalSpillChunkSize = 8192 + evalSpillChunkBytes = 64 * mpool.MB +) + +func (ctr *container) nextSpillOutputChunkEnd(start int) (int, error) { + if start < 0 || start >= len(ctr.sels) { + return start, moerr.NewInternalErrorNoCtx("invalid top spill output cursor") + } + byteLimit := uint64(evalSpillChunkBytes) + if ctr.evalSpillOutputBytes != 0 { + byteLimit = ctr.evalSpillOutputBytes + } + // Leave growth/overlap slack under the actual allocator ceiling as well as + // the ordinary logical output window. A single oversized row still gets the + // existing resource/representation error instead of splitting a SQL row. + byteLimit = min(byteLimit, uint64(mpool.MaxAllocationSize()/4)) + endLimit := min(start+evalSpillChunkSize, len(ctr.sels)) + end := start + var outputBytes uint64 + for end < endLimit { + selection := ctr.sels[end] + if selection < 0 || selection >= int64(len(ctr.rowRefs)) { + return start, moerr.NewInternalErrorNoCtx("invalid top spill row reference") + } + rowBytes := ctr.rowRefs[selection].outputBytes + if rowBytes > math.MaxUint64-outputBytes { + return start, moerr.NewInvalidInputNoCtx("top spill output batch size overflows") + } + // One individually valid row must make progress even when it is larger + // than the normal batch budget. Subsequent rows wait for the next call. + if end > start && outputBytes+rowBytes > byteLimit { + break + } + outputBytes += rowBytes + end++ + if outputBytes >= byteLimit { + break + } + } + return end, nil +} func (ctr *container) evalSpill(limit uint64, n int, proc *process.Process, result *vm.CallResult) (bool, error) { if err, canceled := vm.CancelCheck(proc); canceled { @@ -726,7 +1016,10 @@ func (ctr *container) evalSpill(limit uint64, n int, proc *process.Process, resu } chunkStart := ctr.evalCursor - chunkEnd := min(chunkStart+evalSpillChunkSize, len(ctr.sels)) + chunkEnd, err := ctr.nextSpillOutputChunkEnd(chunkStart) + if err != nil { + return false, err + } chunkSize := chunkEnd - chunkStart type batchRow struct { diff --git a/pkg/sql/colexec/top/top_test.go b/pkg/sql/colexec/top/top_test.go index da8a87ea2898a..2dc5bac9430e3 100644 --- a/pkg/sql/colexec/top/top_test.go +++ b/pkg/sql/colexec/top/top_test.go @@ -113,6 +113,59 @@ func TestPrepare(t *testing.T) { } } +func TestTopPrepareSpillBoundary(t *testing.T) { + tests := []struct { + name string + limit int64 + orderedOutput bool + wantSpill bool + }{ + { + name: "resident threshold remains resident", + limit: int64(topSpillThreshold), + }, + { + name: "ordered threshold remains resident", + limit: int64(topSpillThreshold), + orderedOutput: true, + }, + { + name: "above threshold spills", + limit: int64(topSpillThreshold + 1), + wantSpill: true, + }, + { + name: "ordered above threshold spills", + limit: int64(topSpillThreshold + 1), + orderedOutput: true, + wantSpill: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tc := newTestCase( + t, + mpool.MustNewZero(), + []types.Type{types.T_int64.ToType()}, + test.limit, + []*plan.OrderBySpec{{Expr: newExpression(0)}}, + ) + if test.orderedOutput { + tc.arg.WithOrderedOutput() + } + t.Cleanup(func() { + tc.arg.Free(tc.proc, false, nil) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) + }) + + require.NoError(t, tc.arg.Prepare(tc.proc)) + require.Equal(t, test.wantSpill, tc.arg.ctr.spilling) + }) + } +} + func TestTop(t *testing.T) { for _, tc := range genTestCases(t) { err := tc.arg.Prepare(tc.proc) @@ -305,6 +358,101 @@ func TestTopSpill(t *testing.T) { } } +func TestTopSpillOutputUsesRowAndByteBounds(t *testing.T) { + testTopSpillOutputUsesRowAndByteBounds(t, int(topSpillThreshold+1), false) +} + +func TestTopOrderedOutputUsesSpillRowAndByteBounds(t *testing.T) { + testTopSpillOutputUsesRowAndByteBounds(t, 37, true) +} + +func TestTopCompatibleVarlenOutputUsesSpillRowAndByteBounds(t *testing.T) { + testTopSpillOutputUsesRowAndByteBounds(t, 37, false) +} + +func TestTopSmallLimitPayloadExceedsAllocationCeiling(t *testing.T) { + previous := mpool.CapLimit + mpool.CapLimit = 1 << 20 + t.Cleanup(func() { mpool.CapLimit = previous }) + // Each input and output batch fits, but the 8192 winning payloads do not + // fit in one vector. This must succeed without an OrderedOutput plan hint. + testTopSpillOutputUsesRowAndByteBounds(t, 8192, false) +} + +func testTopSpillOutputUsesRowAndByteBounds(t *testing.T, limit int, orderedOutput bool) { + t.Helper() + const ( + payloadBytes = 200 + batchRows = 128 + outputBytes = 8 * 1024 + ) + inputRows := limit + 257 + tc := newTestCase( + t, + mpool.MustNewZero(), + []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, + int64(limit), + []*plan.OrderBySpec{{Expr: newExpression(0)}}, + ) + if orderedOutput { + tc.arg.WithOrderedOutput() + } + require.NoError(t, tc.arg.Prepare(tc.proc)) + require.Equal(t, uint64(limit) > topSpillThreshold, tc.arg.ctr.spilling) + tc.arg.ctr.evalSpillOutputBytes = outputBytes + // Force actual resident pressure independently of output reconstruction. + tc.arg.ctr.residentByteLimit = outputBytes / 2 + + input := make([]*batch.Batch, 0, (inputRows+batchRows-1)/batchRows+1) + for highest := inputRows; highest > 0; { + rows := min(batchRows, highest) + bat := batch.NewWithSize(2) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + bat.Vecs[1] = vector.NewVec(types.T_varchar.ToType()) + for i := range rows { + key := int64(highest - i) + require.NoError(t, vector.AppendFixed(bat.Vecs[0], key, false, tc.proc.Mp())) + payload := bytes.Repeat([]byte{byte('a' + key%26)}, payloadBytes+int(key%7)) + require.NoError(t, vector.AppendBytes(bat.Vecs[1], payload, false, tc.proc.Mp())) + } + bat.SetRowCount(rows) + input = append(input, bat) + highest -= rows + } + input = append(input, batch.EmptyBatch) + resetChildren(tc.arg, input) + + got := make([]int64, 0, limit) + outputBatches := 0 + for { + result, err := vm.Exec(tc.arg, tc.proc) + require.NoError(t, err) + if result.Batch == nil || result.Status == vm.ExecStop { + break + } + outputBatches++ + require.LessOrEqual(t, uint64(result.Batch.Size()), uint64(outputBytes)) + for _, vec := range result.Batch.Vecs { + require.Less(t, int64(vec.Allocated()), mpool.MaxAllocationSize()) + } + keys := vector.MustFixedColWithTypeCheck[int64](result.Batch.Vecs[0]) + got = append(got, keys...) + for row := range result.Batch.RowCount() { + require.GreaterOrEqual(t, len(result.Batch.Vecs[1].GetBytesAt(row)), payloadBytes) + } + } + require.Greater(t, outputBatches, 1) + require.Len(t, got, limit) + for i, key := range got { + require.Equal(t, int64(i+1), key) + } + + tc.arg.Free(tc.proc, false, nil) + tc.arg.GetChildren(0).Free(tc.proc, false, nil) + tc.proc.Free() + require.Zero(t, tc.proc.Mp().CurrNB()) +} + func TestTopSpillPrepareParamMetadata(t *testing.T) { mp := mpool.MustNewZero() proc := testutil.NewProcessWithMPool(t, "", mp) @@ -523,9 +671,10 @@ func TestTopSpillEvalCancellationCheckpoints(t *testing.T) { require.NoError(t, err) arg.ctr.sels = []int64{0} arg.ctr.rowRefs = []rowRef{{ - offset: record.offset, - size: record.size, - rowIdx: 0, + offset: record.offset, + size: record.size, + rowIdx: 0, + outputBytes: uint64(types.T_int64.ToType().TypeSize()), }} arg.ctr.spillOrdered = true src.Clean(proc.Mp()) diff --git a/pkg/sql/colexec/top/types.go b/pkg/sql/colexec/top/types.go index 1496b72230b3c..801c1853e205b 100644 --- a/pkg/sql/colexec/top/types.go +++ b/pkg/sql/colexec/top/types.go @@ -42,9 +42,10 @@ var _ interface { const topSpillThreshold uint64 = 8192 * 2 type rowRef struct { - offset int64 - size int64 - rowIdx int64 + offset int64 + size int64 + rowIdx int64 + outputBytes uint64 } type spillRecordRef struct { @@ -81,25 +82,32 @@ type container struct { spillAllocation *spillutil.SpillAllocationAccount budget *process.ExecutionResourceGeneration - spilling bool - spillFile *os.File - spillWriter spillWriteFlusher - spillOffset int64 - spillFDToken *process.ExecutionSpillFDReservation - spillDiskToken *process.ExecutionSpillDiskReservation - rowRefs []rowRef + spilling bool + boundedResident bool // actual schema needs byte admission and reclamation + residentBytes uint64 // logical bytes including dead replacement history + residentByteLimit uint64 // zero uses the production window; instance test seam + spillFile *os.File + spillWriter spillWriteFlusher + spillOffset int64 + spillFDToken *process.ExecutionSpillFDReservation + spillDiskToken *process.ExecutionSpillDiskReservation + rowRefs []rowRef // streaming eval state for spill mode spillOrdered bool // sels backing contains the final ascending order evalCursor int // next row index to output in sels spillOutBat *batch.Batch // current chunk output batch, freed on next call + // evalSpillOutputBytes overrides the production byte ceiling in focused + // tests. Zero selects evalSpillChunkBytes. + evalSpillOutputBytes uint64 } type Top struct { - Limit *plan.Expr - TopValueTag int32 - ctr container - Fs []*plan.OrderBySpec + Limit *plan.Expr + TopValueTag int32 + OrderedOutput bool + ctr container + Fs []*plan.OrderBySpec vm.OperatorBase } @@ -139,6 +147,11 @@ func (top *Top) WithFs(fs []*plan.OrderBySpec) *Top { return top } +func (top *Top) WithOrderedOutput() *Top { + top.OrderedOutput = true + return top +} + func (top *Top) Release() { if top != nil { reuse.Free(top, nil) @@ -347,6 +360,8 @@ func (ctr *container) cleanupSpill(proc *process.Process) { ctr.sels = nil ctr.rowRefs = nil ctr.spilling = false + ctr.boundedResident = false + ctr.residentBytes = 0 ctr.spillOffset = 0 ctr.spillOrdered = false ctr.evalCursor = 0 diff --git a/pkg/sql/compile/allocation_account_lifecycle_test.go b/pkg/sql/compile/allocation_account_lifecycle_test.go index 480306f8fed9f..9782bcbcc9894 100644 --- a/pkg/sql/compile/allocation_account_lifecycle_test.go +++ b/pkg/sql/compile/allocation_account_lifecycle_test.go @@ -41,6 +41,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/colexec/product" "github.com/matrixorigin/matrixone/pkg/sql/colexec/top" "github.com/matrixorigin/matrixone/pkg/sql/internal/materialized" + plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" "github.com/matrixorigin/matrixone/pkg/testutil" "github.com/matrixorigin/matrixone/pkg/vm" "github.com/matrixorigin/matrixone/pkg/vm/engine" @@ -644,6 +645,49 @@ func TestParallelRuntimeClonesJoinAllocationAttempt(t *testing.T) { template.Release() } +func TestOrderedTopRuntimeGatherJoinsAllocationAttempt(t *testing.T) { + registry, err := mpool.NewAllocationAccountRegistry(1, 32) + require.NoError(t, err) + c := newTestAllocationLifecycleCompile(t, registry, + func(mpool.AllocationAccountTerminalSnapshot) {}) + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(100) + orderBy := []*plan.OrderBySpec{{Expr: &plan.Expr{ + Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}, + Typ: plan.Type{Id: int32(types.T_int64)}, + }}} + template := top.NewArgument().WithLimit(limitExpr).WithFs(orderBy).WithOrderedOutput() + source := &Scope{ + RootOp: template, + Proc: c.proc, + NodeInfo: engine.Node{ + Mcpu: 2, + }, + } + c.scopes = []*Scope{source} + _, err = c.beginAllocationAccountAttempt() + require.NoError(t, err) + + parallel, workers := newParallelScope(source) + require.Len(t, workers, 2) + require.NoError(t, c.attachRuntimeAllocationOwners([]*Scope{parallel})) + gather := parallel.RootOp.(*mergetop.MergeTop) + require.NoError(t, gather.Prepare(parallel.Proc)) + for _, worker := range workers { + workerTop := worker.RootOp.(*connector.Connector). + GetOperatorBase().GetChildren(0).(*top.Top) + require.NoError(t, workerTop.Prepare(worker.Proc)) + workerTop.Free(worker.Proc, false, nil) + } + for _, reg := range parallel.Proc.Reg.MergeReceivers { + require.True(t, reg.SendEnd()) + } + gather.Free(parallel.Proc, false, nil) + require.NoError(t, c.finishAllocationAccountAttempt()) + + parallel.release() + template.Release() +} + func TestStatementAllocationAttemptOwnerTeardownFailureExportsFailure(t *testing.T) { registry, err := mpool.NewAllocationAccountRegistry(1, 1) require.NoError(t, err) diff --git a/pkg/sql/compile/compile.go b/pkg/sql/compile/compile.go index 19e25ebcbc4b7..3943ccad03e07 100644 --- a/pkg/sql/compile/compile.go +++ b/pkg/sql/compile/compile.go @@ -6369,7 +6369,8 @@ func (c *Compile) compileSort(node *plan.Node, ss []*Scope) []*Scope { overflow = true } if !overflow && topN <= mergeTopResidentPlanThreshold { - // if n is small, convert `order by col limit m offset n` to `top m+n offset n` + // Spilling Top still retains K keys/references per worker. Keep + // external Order for large prefixes, even with a tiny final LIMIT. return c.compileOffset(node, c.compileTop(node, plan2.MakePlan2Uint64ConstExprWithType(topN), ss)) } } @@ -6394,26 +6395,138 @@ func (c *Compile) compileSort(node *plan.Node, ss []*Scope) []*Scope { const mergeTopResidentPlanThreshold uint64 = 8192 * 2 -// canUseResidentMergeTop limits the resident-only global MergeTop to small, -// statically bounded plans. Large or runtime limits use the existing spill-capable -// Top and MergeOrder operators instead. +// Streaming has an extra k-way merge at each CN boundary. Keep the existing +// MergeOrder path while its estimated candidates fit in a few spill windows; +// beyond that point bounded hierarchical merging avoids materializing and +// repeatedly spilling a much larger P*K candidate set. +const distributedTopNStreamingThresholdBytes = 4 * 128 * mpool.MB + +// canUseResidentMergeTop limits the collection-based global MergeTop fallback +// to small, statically bounded plans. When ordered streams are unavailable, +// larger or runtime limits retain the spill-capable Top and MergeOrder path. func canUseResidentMergeTop(topN *plan.Expr) bool { - if topN == nil { + value, ok := staticTopNValue(topN) + return ok && value <= mergeTopResidentPlanThreshold +} + +// residentTopPayloadFits proves a bound from types, never from statistics. +// Varlen replacement can append dead area even with a fixed survivor count. +// Leave room for headers, input copies and growth/shuffle overlap. +func residentTopPayloadFits(node *plan.Node, topN *plan.Expr) bool { + rows, ok := staticTopNValue(topN) + if !ok || node == nil || len(node.ProjectList) == 0 { + return false + } + // These nodes run compileSort before their projection (and TIME_WINDOW + // before aggregation). Their ProjectList does not describe Top's input. + if node.NodeType == plan.Node_SORT || node.NodeType == plan.Node_TIME_WINDOW { return false } + if rows == 0 { + return true + } + remaining := uint64(64*mpool.MB/4) / rows + check := func(expr *plan.Expr) bool { + if expr == nil { + return false + } + typ := types.T(expr.Typ.Id) + if typ.TypeLen() <= 0 || !typ.IsFixedLen() { + return false + } + bytes := uint64(typ.TypeLen()) + 1 // null bitmap, conservatively per row + if bytes > remaining { + return false + } + remaining -= bytes + return true + } + for _, expr := range node.ProjectList { + if !check(expr) { + return false + } + } + for _, spec := range node.OrderBy { + if spec == nil || !check(spec.Expr) { + return false + } + } + return true +} + +func staticTopNValue(topN *plan.Expr) (uint64, bool) { + if topN == nil { + return 0, false + } literal, ok := topN.Expr.(*plan.Expr_Lit) if !ok || literal.Lit == nil { - return false + return 0, false } value, ok := literal.Lit.Value.(*plan.Literal_U64Val) - return ok && value.U64Val <= mergeTopResidentPlanThreshold + if !ok { + return 0, false + } + return value.U64Val, true +} + +func shouldUseDistributedOrderedTop( + node *plan.Node, + topN *plan.Expr, + ss []*Scope, +) bool { + if topN == nil { + return false + } + staticLimit, hasStaticLimit := staticTopNValue(topN) + if hasStaticLimit && staticLimit == 0 { + return false + } + // Large static limits already use spill-capable Top + MergeOrder below + // the performance threshold. Small/runtime limits may otherwise retain + // payload, so lack of a type-based bound must select ordered streams. + if (!hasStaticLimit || staticLimit <= mergeTopResidentPlanThreshold) && + !residentTopPayloadFits(node, topN) { + return true + } + if node == nil || node.Stats == nil || + node.Stats.Cost <= 0 || node.Stats.Rowsize <= 0 || + math.IsNaN(node.Stats.Cost) || math.IsInf(node.Stats.Cost, 0) || + math.IsNaN(node.Stats.Rowsize) || math.IsInf(node.Stats.Rowsize, 0) { + // Missing or invalid estimates must choose the bounded path. + return true + } + fanout := 0 + for _, scope := range ss { + if scope == nil { + continue + } + fanout += max(1, scope.NodeInfo.Mcpu) + } + if fanout == 0 { + return true + } + candidateRows := node.Stats.Cost + if hasStaticLimit { + candidateRows = math.Min( + candidateRows, + float64(staticLimit)*float64(fanout), + ) + } + candidateBytes := candidateRows * node.Stats.Rowsize + return math.IsInf(candidateBytes, 0) || + candidateBytes > float64(distributedTopNStreamingThresholdBytes) } func (c *Compile) compileTop(node *plan.Node, topN *plan.Expr, ss []*Scope) []*Scope { + useOrderedStreams := supportsDistributedOrderedTop(c.proc.GetService()) && + hasMaterializedTopOrderColumns(node.OrderBy) && + shouldUseDistributedOrderedTop(node, topN, ss) // use topN TO make scope. if c.IsSingleScope(ss) { currentFirstFlag := c.anal.isFirst op := constructTop(node, topN) + // No ordered receiver consumes this single-worker result. Top owns + // actual payload admission and can retain fitting small results. op.SetAnalyzeControl(c.anal.curNodeIdx, currentFirstFlag) ss[0].setRootOperator(op) c.anal.isFirst = false @@ -6424,27 +6537,36 @@ func (c *Compile) compileTop(node *plan.Node, topN *plan.Expr, ss []*Scope) []*S for i := range ss { //c.anal.isFirst = currentFirstFlag op := constructTop(node, topN) + if useOrderedStreams { + op.WithOrderedOutput() + } op.SetAnalyzeControl(c.anal.curNodeIdx, currentFirstFlag) ss[i].setRootOperator(op) } c.anal.isFirst = false + if useOrderedStreams { + rs := c.newMergeTopScope(node, topN, ss) + c.anal.isFirst = false + return []*Scope{rs} + } + + // During a rolling upgrade an older CN can still interleave its DOP + // workers. Keep the collection-based path until every participant supports + // the ordered-stream boundary. ss = c.mergeShuffleScopesIfNeeded(ss, false) rs := c.newMergeScope(ss) - currentFirstFlag = c.anal.isFirst - if canUseResidentMergeTop(topN) { + if canUseResidentMergeTop(topN) && residentTopPayloadFits(node, topN) { arg := constructMergeTop(node, topN) arg.SetAnalyzeControl(c.anal.curNodeIdx, currentFirstFlag) rs.setRootOperator(arg) c.anal.isFirst = false return []*Scope{rs} } - mergeOrder := constructMergeOrder(node) mergeOrder.SetAnalyzeControl(c.anal.curNodeIdx, currentFirstFlag) rs.setRootOperator(mergeOrder) c.anal.isFirst = false - globalLimit := constructLimit(&plan.Node{Limit: topN}) globalLimit.SetAnalyzeControl(c.anal.curNodeIdx, c.anal.isFirst) rs.setRootOperator(globalLimit) @@ -6452,6 +6574,21 @@ func (c *Compile) compileTop(node *plan.Node, topN *plan.Expr, ss []*Scope) []*S return []*Scope{rs} } +func hasMaterializedTopOrderColumns(orderBy []*plan.OrderBySpec) bool { + if len(orderBy) == 0 { + return false + } + for _, spec := range orderBy { + if spec == nil || spec.Expr == nil { + return false + } + if _, ok := spec.Expr.Expr.(*plan.Expr_Col); !ok { + return false + } + } + return true +} + func (c *Compile) compileOrder(node *plan.Node, ss []*Scope) []*Scope { if c.IsSingleScope(ss) { currentFirstFlag := c.anal.isFirst @@ -7194,6 +7331,19 @@ func supportsRemoteGroupingSetExpansion(service string) bool { return ok && protocolVersion >= defines.MORPCVersion49 } +func supportsDistributedOrderedTop(service string) bool { + rt := moruntime.ServiceRuntime(service) + if rt == nil { + return false + } + version, ok := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + if !ok { + return false + } + protocolVersion, ok := version.(int64) + return ok && protocolVersion >= defines.MORPCVersion53 +} + func (c *Compile) canCompileShuffleGroup(node *plan.Node) bool { return node.Stats.HashmapStats != nil && node.Stats.HashmapStats.Shuffle && @@ -7977,6 +8127,69 @@ func (c *Compile) newMergeScope(ss []*Scope) *Scope { return rs } +// newMergeTopScope connects one ordered stream from every input scope to a +// leaf MergeTop. Runtime DOP is consolidated inside each input CN before its +// connector writes this edge, so every receiver has exactly one producer. +func (c *Compile) newMergeTopScope(node *plan.Node, topN *plan.Expr, ss []*Scope) *Scope { + rs := c.newEmptyMergeScope() + ss = c.groupOrderedTopRemoteRunDependenciesByCNIfNeeded(node, topN, ss, rs.NodeInfo) + rs.PreScopes = ss + rs.Proc = c.proc.NewNoContextChildProc(len(ss)) + if len(ss) > 0 { + rs.Proc.Base.LoadTag = ss[0].Proc.Base.LoadTag + } + + arg := constructMergeTop(node, topN).WithOrderedStreams() + c.hasMergeOp = true + arg.SetAnalyzeControl(c.anal.curNodeIdx, c.anal.isFirst) + rs.setRootOperator(arg) + + for i := range ss { + reg := rs.Proc.Reg.MergeReceivers[i] + reg.ResetForReuse(1, 1) + reg.OrderedStream = true + connArg := connector.NewArgument().WithReg(reg) + connArg.SetAnalyzeControl(c.anal.curNodeIdx, false) + ss[i].setRootOperator(connArg) + } + return rs +} + +// newMergeTopScopeByCN keeps all same-CN receiver dependencies in one remote +// execution tree while preserving one ordered stream per child. A plain Merge +// cannot be used here because interleaving sorted child batches would invalidate +// the stream contract consumed by the coordinator MergeTop. +func (c *Compile) newMergeTopScopeByCN( + node *plan.Node, + topN *plan.Expr, + ss []*Scope, + nodeinfo engine.Node, +) *Scope { + rs := newScope(Remote) + rs.NodeInfo = scopeNodeWithMcpu(nodeinfo, 1) + rs.PreScopes = ss + rs.Proc = c.proc.NewNoContextChildProc(len(ss)) + if len(ss) > 0 { + rs.Proc.Base.LoadTag = ss[0].Proc.Base.LoadTag + } + + arg := constructMergeTop(node, topN).WithOrderedStreams() + c.hasMergeOp = true + arg.SetAnalyzeControl(c.anal.curNodeIdx, false) + rs.setRootOperator(arg) + + for i := range ss { + reg := rs.Proc.Reg.MergeReceivers[i] + reg.ResetForReuse(1, 1) + reg.OrderedStream = true + connArg := connector.NewArgument().WithReg(reg) + connArg.SetAnalyzeControl(c.anal.curNodeIdx, false) + ss[i].setRootOperator(connArg) + ss[i].IsEnd = true + } + return rs +} + // newScopeListOnSingleWorkerStage builds a single-worker stage. If query // placement already collapsed to one worker, inherit that worker; otherwise // keep the legacy current-CN coordinator for multi-CN stages. @@ -8206,18 +8419,55 @@ func (c *Compile) groupRemoteRunDependenciesByCNIfNeeded( ss []*Scope, mergeNode engine.Node, ) []*Scope { - stageNodes := shuffleBucketStageNodes(ss) - if len(ss) <= len(stageNodes) { + stageNodes, needed := remoteRunDependenciesNeedGroupingByCN(ss, mergeNode) + if !needed { return ss } + return c.mergeScopesByStageNodes(ss, stageNodes) +} +func remoteRunDependenciesNeedGroupingByCN( + ss []*Scope, + mergeNode engine.Node, +) (engine.Nodes, bool) { + stageNodes := shuffleBucketStageNodes(ss) + if len(ss) <= len(stageNodes) { + return stageNodes, false + } for _, scope := range ss { if !sameExecutionNode(scope.NodeInfo, mergeNode) && findPipelineExternalLocalReceiver(scope) != nil { - return c.mergeScopesByStageNodes(ss, stageNodes) + return stageNodes, true } } - return ss + return stageNodes, false +} + +func (c *Compile) groupOrderedTopRemoteRunDependenciesByCNIfNeeded( + node *plan.Node, + topN *plan.Expr, + ss []*Scope, + mergeNode engine.Node, +) []*Scope { + stageNodes, needed := remoteRunDependenciesNeedGroupingByCN(ss, mergeNode) + if !needed { + return ss + } + + rs := make([]*Scope, 0, len(stageNodes)) + for i := range stageNodes { + cn := stageNodes[i] + currentSS := make([]*Scope, 0, cn.Mcpu) + for j := range ss { + if sameExecutionNode(ss[j].NodeInfo, cn) { + currentSS = append(currentSS, ss[j]) + } + } + if len(currentSS) > 0 { + rs = append(rs, c.newMergeTopScopeByCN(node, topN, currentSS, cn)) + } + } + return rs } // shuffleBucketsNeedPerCNGrouping reports whether a dispatch in one top-level diff --git a/pkg/sql/compile/compile_test.go b/pkg/sql/compile/compile_test.go index 37f2d8c30e2b6..485f6bf8b1083 100644 --- a/pkg/sql/compile/compile_test.go +++ b/pkg/sql/compile/compile_test.go @@ -881,7 +881,7 @@ func (w *Ws) PPString() string { return "" } -func NewMockCompile(t *testing.T) *Compile { +func NewMockCompile(t testing.TB) *Compile { return &Compile{ proc: testutil.NewProcess(t), ncpu: system.GoMaxProcs(), diff --git a/pkg/sql/compile/merge_scope_placement_test.go b/pkg/sql/compile/merge_scope_placement_test.go index 718ab0a557b84..11954a2b7fdcb 100644 --- a/pkg/sql/compile/merge_scope_placement_test.go +++ b/pkg/sql/compile/merge_scope_placement_test.go @@ -24,6 +24,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/colexec/connector" "github.com/matrixorigin/matrixone/pkg/sql/colexec/dispatch" "github.com/matrixorigin/matrixone/pkg/sql/colexec/merge" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/mergetop" plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" "github.com/matrixorigin/matrixone/pkg/sql/plan/function" "github.com/matrixorigin/matrixone/pkg/vm" @@ -114,6 +115,92 @@ func TestNewMergeScopePreservesIndependentRemoteInputs(t *testing.T) { "independent remote pipelines should not pay for an extra per-CN merge") } +func TestNewMergeTopScopeGroupsRemoteCrossTreeDependenciesByCN(t *testing.T) { + nodes := engine.Nodes{ + {Id: "cn-local", Addr: "cn-local:6001", Mcpu: 2}, + {Id: "cn-remote", Addr: "cn-remote:6001", Mcpu: 2}, + } + + for _, operator := range []vm.OpType{vm.Dispatch, vm.Connector} { + t.Run(operator.String(), func(t *testing.T) { + c := newCompileForShuffleJoinTest(t, nodes) + enableDistributedOrderedTopForTest(t, c.proc) + owner := newRemoteMergeInputForTest(c, nodes[1], 1) + producer := newRemoteMergeInputForTest(c, nodes[1], 0) + dependency := &Scope{ + Magic: Remote, + NodeInfo: scopeNodeWithMcpu(nodes[1], 1), + Proc: c.proc.NewNoContextChildProc(0), + } + switch operator { + case vm.Dispatch: + op := dispatch.NewArgument() + op.LocalRegs = []*process.WaitRegister{owner.Proc.Reg.MergeReceivers[0]} + dependency.setRootOperator(op) + case vm.Connector: + dependency.setRootOperator(connector.NewArgument().WithReg(owner.Proc.Reg.MergeReceivers[0])) + } + producer.PreScopes = append(producer.PreScopes, dependency) + + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(20_000) + node := &plan.Node{ + Limit: limitExpr, + OrderBy: []*plan.OrderBySpec{{Expr: &plan.Expr{ + Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}, + Typ: plan.Type{Id: int32(types.T_int64)}, + }}}, + } + result := c.compileTop(node, limitExpr, []*Scope{producer, owner}) + require.Len(t, result, 1) + require.Len(t, result[0].PreScopes, 1) + + cnGroup := result[0].PreScopes[0] + require.Len(t, cnGroup.PreScopes, 2) + cnGroup.Proc.Base.TxnOperator = fakeTxnOperator{} + require.True(t, checkPipelineStandaloneExecutableAtRemote(cnGroup)) + out, ok := cnGroup.RootOp.(*connector.Connector) + require.True(t, ok) + require.True(t, out.Reg.OrderedStream) + cnTop, ok := out.GetOperatorBase().GetChildren(0).(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, cnTop.OrderedStreams) + require.Len(t, cnGroup.Proc.Reg.MergeReceivers, 2) + for _, reg := range cnGroup.Proc.Reg.MergeReceivers { + require.True(t, reg.OrderedStream) + require.Equal(t, 1, reg.NilBatchCnt) + require.Equal(t, 1, cap(reg.Ch2)) + } + }) + } +} + +func TestNewMergeTopScopePreservesIndependentRemoteInputs(t *testing.T) { + nodes := engine.Nodes{ + {Id: "cn-local", Addr: "cn-local:6001", Mcpu: 2}, + {Id: "cn-remote", Addr: "cn-remote:6001", Mcpu: 2}, + } + c := newCompileForShuffleJoinTest(t, nodes) + enableDistributedOrderedTopForTest(t, c.proc) + inputs := []*Scope{ + newRemoteMergeInputForTest(c, nodes[1], 0), + newRemoteMergeInputForTest(c, nodes[1], 0), + } + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(20_000) + node := &plan.Node{ + Limit: limitExpr, + OrderBy: []*plan.OrderBySpec{{Expr: &plan.Expr{ + Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}, + Typ: plan.Type{Id: int32(types.T_int64)}, + }}}, + } + + result := c.compileTop(node, limitExpr, inputs) + + require.Len(t, result, 1) + require.Len(t, result[0].PreScopes, 2, + "independent ordered remote pipelines should not pay for an extra per-CN gather") +} + func TestCompileWindowKeepsDistributedShuffleJoinTreesStandalone(t *testing.T) { const dop = int32(2) nodes := engine.Nodes{ diff --git a/pkg/sql/compile/merge_top_fallback_test.go b/pkg/sql/compile/merge_top_fallback_test.go index edb6e7062387f..2487f243081e6 100644 --- a/pkg/sql/compile/merge_top_fallback_test.go +++ b/pkg/sql/compile/merge_top_fallback_test.go @@ -17,6 +17,13 @@ package compile import ( "testing" + "github.com/google/uuid" + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/common/runtime" + "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/defines" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/colexec" "github.com/matrixorigin/matrixone/pkg/sql/colexec/connector" @@ -25,10 +32,82 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/colexec/mergetop" "github.com/matrixorigin/matrixone/pkg/sql/colexec/top" plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" + "github.com/matrixorigin/matrixone/pkg/vm" "github.com/matrixorigin/matrixone/pkg/vm/engine" + "github.com/matrixorigin/matrixone/pkg/vm/process" "github.com/stretchr/testify/require" ) +func TestCompileLargeOffsetUsesExternalOrder(t *testing.T) { + runLargeOffsetMemory(t, int(mergeTopResidentPlanThreshold)+1) +} + +// Scale-sensitive memory acceptance from review 5127191041 belongs outside +// the ordinary UT fixture. The UT above pins the minimum routing boundary. +func BenchmarkReviewLargeOffsetMemory(b *testing.B) { + for i := 0; i < b.N; i++ { + runLargeOffsetMemory(b, 4*1024*1024) + } +} + +func runLargeOffsetMemory(t testing.TB, n int) { + c := newMergeTopFallbackTestCompile(t) + c.proc.Base.Lim.Size = 160 << 20 + generation, err := c.proc.GetExecutionResourceBudget() + require.NoError(t, err) + registry, err := mpool.NewAllocationAccountRegistry(1, 1<<14) + require.NoError(t, err) + account, err := registry.OpenWithController(160<<20, generation) + require.NoError(t, err) + var bats []*batch.Batch + for start := 0; start < n; start += 8192 { + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + values := make([]int64, min(8192, n-start)) + for j := range values { + values[j] = int64(start + j) + } + require.NoError(t, vector.AppendFixedList(bat.Vecs[0], values, nil, c.proc.Mp())) + bat.SetRowCount(len(values)) + bats = append(bats, bat) + } + s := newMergeTopFallbackTestScope(c) + s.Proc = c.proc + s.RootOp = colexec.NewMockOperator().WithBatchs(bats) + node := newMergeTopFallbackTestNode(plan2.MakePlan2Uint64ConstExprWithType(1)) + node.Offset = plan2.MakePlan2Uint64ConstExprWithType(uint64(n - 1)) + node.NodeType = plan.Node_SORT + scopes := c.compileSort(node, []*Scope{s}) + require.Len(t, scopes, 1) + op := scopes[0].RootOp + var owners []executionAllocationAccountOwner + defer func() { + require.NoError(t, vm.HandleAllOp(op, func(_ vm.Operator, o vm.Operator) error { o.Free(c.proc, false, nil); return nil })) + for _, owner := range owners { + require.NoError(t, owner.ClearAllocationAccount(account)) + } + require.Zero(t, account.Snapshot().Used) + require.Zero(t, generation.SpillDiskUsed()) + require.Zero(t, generation.SpillFDUsed()) + c.proc.Free() + require.Zero(t, c.proc.Mp().CurrNB()) + }() + require.NoError(t, vm.HandleAllOp(op, func(_ vm.Operator, o vm.Operator) error { + require.NotEqual(t, vm.Top, o.OpType()) + if owner, ok := o.(executionAllocationAccountOwner); ok { + require.NoError(t, owner.SetAllocationAccount(account)) + owners = append(owners, owner) + } + return o.Prepare(c.proc) + })) + result, err := vm.Exec(op, c.proc) + t.Logf("rows=%d peak accounted bytes=%d", n, account.Snapshot().Peak) + require.NoError(t, err) + require.NotNil(t, result.Batch) + require.Equal(t, 1, result.Batch.RowCount()) + require.Equal(t, int64(n-1), vector.GetFixedAtWithTypeCheck[int64](result.Batch.Vecs[0], 0)) +} + func TestCanUseResidentMergeTop(t *testing.T) { dynamicLimit := &plan.Expr{Expr: &plan.Expr_P{P: &plan.ParamRef{Pos: 0}}} tests := []struct { @@ -59,22 +138,34 @@ func TestCanUseResidentMergeTop(t *testing.T) { func TestCompileTopRoutesMultiScopeLimits(t *testing.T) { tests := []struct { - name string - limit *plan.Expr - wantMergeTop bool + name string + limit *plan.Expr + stats *plan.Stats + ordered bool }{ { - name: "resident threshold", - limit: plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold), - wantMergeTop: true, + name: "resident threshold", + limit: plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold), + stats: &plan.Stats{Cost: 1_000, Rowsize: 100}, + }, + { + name: "resident row threshold with wide candidates", + limit: plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold), + stats: &plan.Stats{ + Cost: float64(mergeTopResidentPlanThreshold * 2), + Rowsize: float64(distributedTopNStreamingThresholdBytes)/(float64(mergeTopResidentPlanThreshold)*2) + 1, + }, + ordered: true, }, { - name: "large literal", - limit: plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold + 1), + name: "large literal", + limit: plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold + 1), + ordered: true, }, { - name: "dynamic", - limit: &plan.Expr{Expr: &plan.Expr_P{P: &plan.ParamRef{Pos: 0}}}, + name: "dynamic", + limit: &plan.Expr{Expr: &plan.Expr_P{P: &plan.ParamRef{Pos: 0}}}, + ordered: true, }, } @@ -82,6 +173,7 @@ func TestCompileTopRoutesMultiScopeLimits(t *testing.T) { t.Run(test.name, func(t *testing.T) { c := newMergeTopFallbackTestCompile(t) node := newMergeTopFallbackTestNode(test.limit) + node.Stats = test.stats result := c.compileTop(node, test.limit, newMergeTopFallbackTestScopes(c, 2)) require.Len(t, result, 1) require.Len(t, result[0].PreScopes, 2) @@ -90,19 +182,25 @@ func TestCompileTopRoutesMultiScopeLimits(t *testing.T) { require.True(t, ok) localTop, ok := connectorOp.GetOperatorBase().GetChildren(0).(*top.Top) require.True(t, ok) + require.Equal(t, test.ordered, localTop.OrderedOutput) require.Same(t, test.limit, localTop.Limit) } - if test.wantMergeTop { - globalTop, ok := result[0].RootOp.(*mergetop.MergeTop) - require.True(t, ok) - require.Same(t, test.limit, globalTop.Limit) + globalTop, ok := result[0].RootOp.(*mergetop.MergeTop) + require.True(t, ok) + require.Equal(t, test.ordered, globalTop.OrderedStreams) + if test.ordered { + require.Zero(t, globalTop.GetOperatorBase().NumChildren()) } else { - globalLimit, ok := result[0].RootOp.(*limit.Limit) - require.True(t, ok) - require.Same(t, test.limit, globalLimit.LimitExpr) - _, ok = globalLimit.GetOperatorBase().GetChildren(0).(*mergeorder.MergeOrder) - require.True(t, ok) + require.Equal(t, 1, globalTop.GetOperatorBase().NumChildren()) + } + require.Same(t, test.limit, globalTop.Limit) + for _, reg := range result[0].Proc.Reg.MergeReceivers { + require.Equal(t, test.ordered, reg.OrderedStream) + if test.ordered { + require.Equal(t, 1, reg.NilBatchCnt) + require.Equal(t, 1, cap(reg.Ch2)) + } } result[0].FreeOperator(c) @@ -112,6 +210,61 @@ func TestCompileTopRoutesMultiScopeLimits(t *testing.T) { } } +func TestShouldUseDistributedOrderedTopUsesCandidateBytes(t *testing.T) { + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(500_000) + scopes := []*Scope{{NodeInfo: engine.Node{Mcpu: 16}}} + tests := []struct { + name string + cost float64 + rowSize float64 + expected bool + }{ + {name: "one spill window shape", cost: 1_000_000, rowSize: 146}, + {name: "many spill windows shape", cost: 10_000_000, rowSize: 146, expected: true}, + {name: "missing cardinality is bounded", rowSize: 146, expected: true}, + {name: "missing row size is bounded", cost: 10_000_000, expected: true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + node := &plan.Node{Stats: &plan.Stats{Cost: test.cost, Rowsize: test.rowSize}} + require.Equal(t, test.expected, + shouldUseDistributedOrderedTop(node, limitExpr, scopes)) + }) + } + require.True(t, shouldUseDistributedOrderedTop( + &plan.Node{}, + &plan.Expr{Expr: &plan.Expr_P{P: &plan.ParamRef{Pos: 0}}}, + scopes)) + dynamicLimit := &plan.Expr{Expr: &plan.Expr_P{P: &plan.ParamRef{Pos: 0}}} + require.True(t, shouldUseDistributedOrderedTop( + &plan.Node{Stats: &plan.Stats{Cost: 1_000_000, Rowsize: 146}}, + dynamicLimit, + scopes)) + require.True(t, shouldUseDistributedOrderedTop( + &plan.Node{Stats: &plan.Stats{Cost: 10_000_000, Rowsize: 146}}, + dynamicLimit, + scopes)) + require.False(t, shouldUseDistributedOrderedTop( + &plan.Node{}, plan2.MakePlan2Uint64ConstExprWithType(0), scopes)) + + residentLimit := plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold) + residentCandidates := float64(mergeTopResidentPlanThreshold * 16) + exactThresholdRowSize := float64(distributedTopNStreamingThresholdBytes) / residentCandidates + residentNode := newMergeTopFallbackTestNode(residentLimit) + residentNode.Stats = &plan.Stats{Cost: residentCandidates, Rowsize: exactThresholdRowSize} + require.False(t, shouldUseDistributedOrderedTop( + residentNode, + residentLimit, + scopes)) + residentNode.Stats.Rowsize++ + require.True(t, shouldUseDistributedOrderedTop( + residentNode, + residentLimit, + scopes)) + require.True(t, shouldUseDistributedOrderedTop( + &plan.Node{}, residentLimit, scopes)) +} + func TestCompileTopKeepsSingleScopeLargeLimitLocal(t *testing.T) { c := newMergeTopFallbackTestCompile(t) limitExpr := plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold + 1) @@ -123,6 +276,7 @@ func TestCompileTopKeepsSingleScopeLargeLimitLocal(t *testing.T) { require.Same(t, scope, result[0]) localTop, ok := result[0].RootOp.(*top.Top) require.True(t, ok) + require.False(t, localTop.OrderedOutput, "single-worker results have no ordered receiver edge") require.Same(t, limitExpr, localTop.Limit) result[0].FreeOperator(c) @@ -130,19 +284,149 @@ func TestCompileTopKeepsSingleScopeLargeLimitLocal(t *testing.T) { c.proc.Free() } -func newMergeTopFallbackTestCompile(t *testing.T) *Compile { +func TestCompileTopStaleStatisticsCannotAdmitVarlenPayload(t *testing.T) { + for _, k := range []uint64{16383, 16384, 16385} { + for _, version := range []int64{defines.MORPCVersion52, defines.MORPCLatestVersion} { + c := newMergeTopFallbackTestCompile(t) + runtime.ServiceRuntime(c.proc.GetService()).SetGlobalVariables(runtime.MOProtocolVersion, version) + node := newMergeTopFallbackTestNode(plan2.MakePlan2Uint64ConstExprWithType(k)) + node.Stats = &plan.Stats{Cost: 1, Rowsize: 1} + node.ProjectList = append(node.ProjectList, &plan.Expr{Typ: plan.Type{Id: int32(types.T_varchar)}}) + result := c.compileTop(node, node.Limit, newMergeTopFallbackTestScopes(c, 2)) + if version >= defines.MORPCVersion53 && k <= mergeTopResidentPlanThreshold { + gather, ok := result[0].RootOp.(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, gather.OrderedStreams) + } else { + globalLimit, ok := result[0].RootOp.(*limit.Limit) + require.True(t, ok) + _, ok = globalLimit.GetChildren(0).(*mergeorder.MergeOrder) + require.True(t, ok) + } + result[0].FreeOperator(c) + result[0].release() + c.proc.Free() + } + } +} + +func TestResidentTopPayloadProofUsesPhysicalInputSchema(t *testing.T) { + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(16384) + node := newMergeTopFallbackTestNode(limitExpr) + require.True(t, residentTopPayloadFits(node, limitExpr)) + for _, kind := range []plan.Node_NodeType{plan.Node_SORT, plan.Node_TIME_WINDOW} { + node.NodeType = kind + // These projections run after Top; a narrow final projection says + // nothing about the width of retained child payload. + require.False(t, residentTopPayloadFits(node, limitExpr)) + } + node.NodeType = plan.Node_PROJECT + node.ProjectList = nil + require.False(t, residentTopPayloadFits(node, limitExpr)) + node.ProjectList = []*plan.Expr{{Typ: plan.Type{Id: int32(types.T_any)}}} + require.False(t, residentTopPayloadFits(node, limitExpr)) + node.ProjectList = []*plan.Expr{{Typ: plan.Type{Id: int32(types.T_tuple)}}} + require.False(t, residentTopPayloadFits(node, limitExpr)) + node.ProjectList = []*plan.Expr{{Typ: plan.Type{Id: int32(types.T_int64)}}} + require.False(t, residentTopPayloadFits(node, plan2.MakePlan2Uint64ConstExprWithType(^uint64(0)))) + // Widen the fixed-width payload beyond the resident allowance without + // allocating rows or relying on optimizer estimates. + for range 128 { + node.ProjectList = append(node.ProjectList, node.ProjectList[0]) + } + require.False(t, residentTopPayloadFits(node, limitExpr)) +} + +func TestCompileTopFallsBackDuringRollingUpgrade(t *testing.T) { + c := newMergeTopFallbackTestCompile(t) + rt := runtime.ServiceRuntime(c.proc.GetService()) + previous, hadPrevious := rt.GetGlobalVariables(runtime.MOProtocolVersion) + rt.SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCVersion52) + t.Cleanup(func() { + if hadPrevious { + rt.SetGlobalVariables(runtime.MOProtocolVersion, previous) + } else { + rt.CompareAndDeleteGlobalVariables(runtime.MOProtocolVersion, defines.MORPCVersion52) + } + }) + + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold + 1) + node := newMergeTopFallbackTestNode(limitExpr) + result := c.compileTop(node, limitExpr, newMergeTopFallbackTestScopes(c, 2)) + require.Len(t, result, 1) + globalLimit, ok := result[0].RootOp.(*limit.Limit) + require.True(t, ok) + _, ok = globalLimit.GetOperatorBase().GetChildren(0).(*mergeorder.MergeOrder) + require.True(t, ok) + orderedReg := process.NewPipelineEdge(1, 1) + orderedReg.OrderedStream = true + orderedOutput := connector.NewArgument().WithReg(orderedReg) + _, _, _, _, err := prepareRemoteRunSendingData( + "select 1 order by 1 limit 1", + &Scope{RootOp: orderedOutput, Proc: c.proc}, + c.proc, + nil, + uuid.Nil, + ) + require.Error(t, err) + orderedOutput.Release() + + result[0].FreeOperator(c) + result[0].release() + c.proc.Free() +} + +func TestCompileTopFallsBackWhenOrderKeyIsNotMaterialized(t *testing.T) { + c := newMergeTopFallbackTestCompile(t) + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(mergeTopResidentPlanThreshold + 1) + node := newMergeTopFallbackTestNode(limitExpr) + node.OrderBy[0].Expr = plan2.MakePlan2Int64ConstExprWithType(7) + + result := c.compileTop(node, limitExpr, newMergeTopFallbackTestScopes(c, 2)) + require.Len(t, result, 1) + globalLimit, ok := result[0].RootOp.(*limit.Limit) + require.True(t, ok) + _, ok = globalLimit.GetOperatorBase().GetChildren(0).(*mergeorder.MergeOrder) + require.True(t, ok) + + result[0].FreeOperator(c) + result[0].release() + c.proc.Free() +} + +func newMergeTopFallbackTestCompile(t testing.TB) *Compile { c := NewMockCompile(t) + enableDistributedOrderedTopForTest(t, c.proc) c.anal = &AnalyzeModule{curNodeIdx: 1, isFirst: true} c.execType = plan2.ExecTypeAP_ONECN c.isPrepare = true return c } +func enableDistributedOrderedTopForTest(t testing.TB, proc *process.Process) { + t.Helper() + rt := runtime.ServiceRuntime(proc.GetService()) + previous, hadPrevious := rt.GetGlobalVariables(runtime.MOProtocolVersion) + rt.SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCLatestVersion) + t.Cleanup(func() { + if hadPrevious { + rt.SetGlobalVariables(runtime.MOProtocolVersion, previous) + } else { + rt.CompareAndDeleteGlobalVariables(runtime.MOProtocolVersion, defines.MORPCLatestVersion) + } + }) +} + func newMergeTopFallbackTestNode(limitExpr *plan.Expr) *plan.Node { + key := &plan.Expr{ + Typ: plan.Type{Id: int32(types.T_int64)}, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}, + } return &plan.Node{ - Limit: limitExpr, + Limit: limitExpr, + ProjectList: []*plan.Expr{key}, OrderBy: []*plan.OrderBySpec{{ - Expr: &plan.Expr{Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}}, + Expr: key, }}, } } diff --git a/pkg/sql/compile/operator.go b/pkg/sql/compile/operator.go index f77cabec42f03..b78aadd5d6425 100644 --- a/pkg/sql/compile/operator.go +++ b/pkg/sql/compile/operator.go @@ -331,6 +331,7 @@ func dupOperatorWithContext(sourceOp vm.Operator, index int, maxParallel int, du t := sourceOp.(*top.Top) op := top.NewArgument() op.Limit = t.Limit + op.OrderedOutput = t.OrderedOutput if t.TopValueTag > 0 { op.TopValueTag = t.TopValueTag + int32(index)<<16 } @@ -362,6 +363,7 @@ func dupOperatorWithContext(sourceOp vm.Operator, index int, maxParallel int, du op := mergetop.NewArgument() op.Limit = t.Limit op.Fs = t.Fs + op.OrderedStreams = t.OrderedStreams op.SetInfo(&info) return op case vm.MergeOrder: diff --git a/pkg/sql/compile/remoterun.go b/pkg/sql/compile/remoterun.go index 9005412771862..5e30cc142f392 100644 --- a/pkg/sql/compile/remoterun.go +++ b/pkg/sql/compile/remoterun.go @@ -120,6 +120,9 @@ func encodeRemoteScope(s *Scope, proc *process.Process) ([]byte, error) { if err = validateRemoteGroupingSetPipelineProtocol(proc, p); err != nil { return nil, err } + if err = validateRemoteDistributedOrderedTopPipelineProtocol(proc, p); err != nil { + return nil, err + } if err = validateRemoteODKUAffectedRowsPipelineProtocol(proc, p); err != nil { return nil, err } @@ -217,6 +220,9 @@ func decodeScope(data []byte, proc *process.Process, isRemote bool, eng engine.E if err = validateRemoteGroupingSetPipelineProtocol(proc, p); err != nil { return nil, err } + if err = validateRemoteDistributedOrderedTopPipelineProtocol(proc, p); err != nil { + return nil, err + } } else if err = plan.ValidateStringLiteralFormsInOwner(p); err != nil { return nil, err } @@ -257,7 +263,16 @@ func encodeProcessInfo( } func appendWriteBackOperator(c *Compile, s *Scope) *Scope { + orderedTop, isTop := s.RootOp.(*top.Top) rs := c.newMergeScope([]*Scope{s}) + if isTop && orderedTop.OrderedOutput { + // The wire promises one ordered stream, not interleaved DOP batches. + // Preserve that promise on the new local edge so runtime expansion + // inserts the ordered worker gather before this write-back merge. + reg := rs.Proc.Reg.MergeReceivers[0] + reg.ResetForReuse(1, 1) + reg.OrderedStream = true + } op := output.NewArgument(). WithFunc(c.fill) op.SetIdx(-1) @@ -337,8 +352,17 @@ func generatePipeline(s *Scope, ctx *scopeContext, ctxId int32) (*pipeline.Pipel for i := range s.Proc.Reg.MergeReceivers { p.ChannelBufferSize = append(p.ChannelBufferSize, int32(cap(s.Proc.Reg.MergeReceivers[i].Ch2))) p.NilBatchCnt = append(p.NilBatchCnt, int32(s.Proc.Reg.MergeReceivers[i].NilBatchCnt)) + if s.Proc.Reg.MergeReceivers[i].OrderedStream { + if p.OrderedStream == nil { + p.OrderedStream = make([]bool, len(s.Proc.Reg.MergeReceivers)) + } + p.OrderedStream[i] = true + } ctx.regs[s.Proc.Reg.MergeReceivers[i]] = int32(i) } + for len(p.OrderedStream) > 0 && !p.OrderedStream[len(p.OrderedStream)-1] { + p.OrderedStream = p.OrderedStream[:len(p.OrderedStream)-1] + } } // DataSource if s.DataSource != nil { // if select 1, DataSource is nil @@ -506,6 +530,11 @@ func generateScope(proc *process.Process, p *pipeline.Pipeline, ctx *scopeContex s.NodeInfo.Data = relData } s.Proc = proc.NewNoContextChildProcWithChannel(int(p.ChildrenCount), p.ChannelBufferSize, p.NilBatchCnt) + for i := range s.Proc.Reg.MergeReceivers { + if i < len(p.OrderedStream) { + s.Proc.Reg.MergeReceivers[i].OrderedStream = p.OrderedStream[i] + } + } ctx.scope = s { for i := range s.Proc.Reg.MergeReceivers { @@ -800,6 +829,7 @@ func convertToPipelineInstruction(op vm.Operator, proc *process.Process, ctx *sc case *top.Top: in.Limit = t.Limit in.OrderBy = t.Fs + in.TopOrderedOutput = t.OrderedOutput case *intersect.Intersect: in.SetOp = &pipeline.SetOp{KeyExprs: t.KeyExprs} case *minus.Minus: @@ -832,6 +862,7 @@ func convertToPipelineInstruction(op vm.Operator, proc *process.Process, ctx *sc case *mergetop.MergeTop: in.Limit = t.Limit in.OrderBy = t.Fs + in.MergeTopOrderedStreams = t.OrderedStreams case *mergeorder.MergeOrder: in.OrderBy = t.OrderBySpecs in.SpillMem = t.SpillThreshold @@ -1418,9 +1449,13 @@ func convertToVmOperator(opr *pipeline.Instruction, ctx *scopeContext, eng engin arg.IsAssert = opr.FilterIsAssert op = arg case vm.Top: - op = top.NewArgument(). + topArg := top.NewArgument(). WithLimit(opr.Limit). WithFs(opr.OrderBy) + if opr.TopOrderedOutput { + topArg.WithOrderedOutput() + } + op = topArg // should change next day? case vm.Intersect: arg := intersect.NewArgument() @@ -1470,9 +1505,13 @@ func convertToVmOperator(opr *pipeline.Instruction, ctx *scopeContext, eng engin op = arg DecodeMergeGroup(op.(*group.MergeGroup), opr.Agg) case vm.MergeTop: - op = mergetop.NewArgument(). + arg := mergetop.NewArgument(). WithLimit(opr.Limit). WithFs(opr.OrderBy) + if opr.MergeTopOrderedStreams { + arg.WithOrderedStreams() + } + op = arg case vm.MergeOrder: arg := mergeorder.NewArgument() arg.OrderBySpecs = opr.OrderBy @@ -1985,6 +2024,40 @@ func validateRemoteMongoUserQueryPipelineProtocol( return nil } +func validateRemoteDistributedOrderedTopPipelineProtocol( + proc *process.Process, + p *pipeline.Pipeline, +) error { + if p == nil { + return nil + } + usesOrderedTop := false + for _, ordered := range p.OrderedStream { + if ordered { + usesOrderedTop = true + break + } + } + if !usesOrderedTop { + for _, instruction := range p.InstructionList { + if instruction.TopOrderedOutput || instruction.MergeTopOrderedStreams { + usesOrderedTop = true + break + } + } + } + if usesOrderedTop && (proc == nil || !supportsDistributedOrderedTop(proc.GetService())) { + return moerr.NewNotSupportedNoCtx( + "distributed ordered Top-N requires MORPC protocol version 53") + } + for _, child := range p.Children { + if err := validateRemoteDistributedOrderedTopPipelineProtocol(proc, child); err != nil { + return err + } + } + return nil +} + func validateRemoteRightDedupInputKeysUniquePipelineProtocol( proc *process.Process, p *pipeline.Pipeline, diff --git a/pkg/sql/compile/remoterunClient.go b/pkg/sql/compile/remoterunClient.go index 12a232b16a7d4..38371bec672c1 100644 --- a/pkg/sql/compile/remoterunClient.go +++ b/pkg/sql/compile/remoterunClient.go @@ -232,6 +232,12 @@ func prepareRemoteRunSendingData( remoteFragmentCounts map[string]uint32, remoteExecutionID uuid.UUID, ) (scopeData []byte, withoutOutput bool, processData []byte, folded bool, err error) { + if output, ok := s.RootOp.(*connector.Connector); ok && + output.Reg != nil && output.Reg.OrderedStream && + !supportsDistributedOrderedTop(proc.GetService()) { + return nil, false, nil, false, moerr.NewNotSupportedNoCtx( + "distributed ordered Top-N requires MORPC protocol version 53") + } encodedScope, withoutOutput := getScopeForRemoteRunEncoding(s) encodedScope = copyBlockFiltersForRemoteRun(encodedScope) encodedScope, folded, err = foldVarExprsInRemoteRunScope(encodedScope, proc) diff --git a/pkg/sql/compile/remoterun_test.go b/pkg/sql/compile/remoterun_test.go index 30b1068e7a489..df5bf4c982a3d 100644 --- a/pkg/sql/compile/remoterun_test.go +++ b/pkg/sql/compile/remoterun_test.go @@ -83,6 +83,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/colexec/runtimefilter" "github.com/matrixorigin/matrixone/pkg/sql/colexec/shuffle" "github.com/matrixorigin/matrixone/pkg/sql/colexec/table_function" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/table_scan" "github.com/matrixorigin/matrixone/pkg/sql/colexec/top" "github.com/matrixorigin/matrixone/pkg/sql/colexec/value_scan" sqlmongodb "github.com/matrixorigin/matrixone/pkg/sql/mongodb" @@ -201,6 +202,58 @@ func Test_refactorScope(t *testing.T) { require.Equal(t, vm.GetLeafOpParent(nil, rs.RootOp).GetOperatorBase().Idx, -1) } +func TestRemoteOrderedTopWriteBackPreservesDOPBoundary(t *testing.T) { + for _, ordered := range []bool{false, true} { + t.Run(fmt.Sprintf("ordered=%t", ordered), func(t *testing.T) { + c := newMergeTopFallbackTestCompile(t) + defer c.proc.Free() + limitExpr := plan.MakePlan2Uint64ConstExprWithType(20_000) + orderBy := []*planpb.OrderBySpec{{Expr: &planpb.Expr{ + Expr: &planpb.Expr_Col{Col: &planpb.ColRef{ColPos: 0}}, + Typ: planpb.Type{Id: int32(types.T_int64)}, + }}} + localTop := top.NewArgument().WithLimit(limitExpr).WithFs(orderBy) + localTop.OrderedOutput = ordered + localTop.AppendChild(table_scan.NewArgument()) + source := &Scope{ + Magic: Remote, NodeInfo: engine.Node{Mcpu: 4}, + Proc: c.proc.NewNoContextChildProc(0), RootOp: localTop, + } + defer source.release() + data, err := encodeRemoteScope(source, c.proc) + require.NoError(t, err) + decoded, err := decodeScope(data, c.proc, true, nil) + require.NoError(t, err) + writeBack := appendWriteBackOperator(c, decoded) + defer writeBack.release() + decoded.Proc.BuildPipelineContext(c.proc.Ctx) + defer decoded.Proc.Cancel(nil) + reg := writeBack.Proc.Reg.MergeReceivers[0] + require.Equal(t, ordered, reg.OrderedStream) + gather, workers := newParallelScope(decoded) + require.Len(t, workers, 4) + if ordered { + require.Equal(t, 1, reg.NilBatchCnt) + require.Equal(t, Merge, gather.Magic) + out, ok := gather.RootOp.(*connector.Connector) + require.True(t, ok) + require.Same(t, reg, out.Reg) + merged, ok := out.GetChildren(0).(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, merged.OrderedStreams) + for i, worker := range workers { + workerOut := worker.RootOp.(*connector.Connector) + require.NotSame(t, reg, workerOut.Reg) + require.Same(t, gather.Proc.Reg.MergeReceivers[i], workerOut.Reg) + require.True(t, workerOut.Reg.OrderedStream) + } + } else { + require.Equal(t, Normal, gather.Magic) + } + }) + } +} + func Test_convertPipelineUuid(t *testing.T) { id, _ := uuid.NewV7() p := &pipeline.Pipeline{ @@ -531,6 +584,44 @@ func TestRemoteRunOperatorCodecRoundTrip(t *testing.T) { require.True(t, ownsAllocation) }) + t.Run("TopOrderedOutput", func(t *testing.T) { + original := top.NewArgument(). + WithLimit(plan.MakePlan2Uint64ConstExprWithType(17)). + WithFs([]*planpb.OrderBySpec{{ + Expr: &planpb.Expr{ + Expr: &planpb.Expr_Col{Col: &planpb.ColRef{ColPos: 0}}, + Typ: planpb.Type{Id: int32(types.T_int64)}, + }, + }}). + WithOrderedOutput() + restored := roundTrip(t, original) + defer restored.Release() + restoredTop, ok := restored.(*top.Top) + require.True(t, ok) + require.True(t, restoredTop.OrderedOutput) + require.Equal(t, original.Limit, restoredTop.Limit) + require.Equal(t, original.Fs, restoredTop.Fs) + }) + + t.Run("MergeTopOrderedStreams", func(t *testing.T) { + original := mergetop.NewArgument(). + WithLimit(plan.MakePlan2Uint64ConstExprWithType(17)). + WithFs([]*planpb.OrderBySpec{{ + Expr: &planpb.Expr{ + Expr: &planpb.Expr_Col{Col: &planpb.ColRef{ColPos: 0}}, + Typ: planpb.Type{Id: int32(types.T_int64)}, + }, + }}). + WithOrderedStreams() + restored := roundTrip(t, original) + defer restored.Release() + restoredMergeTop, ok := restored.(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, restoredMergeTop.OrderedStreams) + require.Equal(t, original.Limit, restoredMergeTop.Limit) + require.Equal(t, original.Fs, restoredMergeTop.Fs) + }) + t.Run("GroupMetadata", func(t *testing.T) { original := group.NewArgument() original.GroupByHashKey = []int32{0, 2} @@ -618,6 +709,132 @@ func TestRemoteRunOperatorCodecRoundTrip(t *testing.T) { }) } +func TestRemoteRunOrderedPipelineEdgeRoundTrip(t *testing.T) { + proc := testutil.NewProcess(t) + rt := moruntime.ServiceRuntime(proc.GetService()) + oldVersion, hadVersion := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + rt.SetGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCLatestVersion) + t.Cleanup(func() { + if hadVersion { + rt.SetGlobalVariables(moruntime.MOProtocolVersion, oldVersion) + } else { + rt.CompareAndDeleteGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCLatestVersion) + } + }) + scope := &Scope{ + Magic: Remote, + Proc: proc.NewNoContextChildProc(2), + RootOp: mergetop.NewArgument().WithOrderedStreams(), + } + scope.Proc.Reg.MergeReceivers[0].ResetForReuse(1, 1) + scope.Proc.Reg.MergeReceivers[0].OrderedStream = true + scope.Proc.Reg.MergeReceivers[1].ResetForReuse(3, 2) + + data, err := encodeRemoteScope(scope, proc) + require.NoError(t, err) + wire := new(pipeline.Pipeline) + require.NoError(t, wire.Unmarshal(data)) + require.Equal(t, []bool{true}, wire.OrderedStream, + "trailing unordered receivers should retain the legacy zero-value wire representation") + rt.SetGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCVersion52) + _, err = encodeRemoteScope(scope, proc) + require.ErrorContains(t, err, "requires MORPC protocol version 53") + _, err = decodeScope(data, proc, true, nil) + require.ErrorContains(t, err, "requires MORPC protocol version 53") + rt.SetGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCLatestVersion) + restored, err := decodeScope(data, proc, true, nil) + require.NoError(t, err) + t.Cleanup(func() { + restored.release() + scope.release() + proc.Free() + }) + + require.Len(t, restored.Proc.Reg.MergeReceivers, 2) + require.True(t, restored.Proc.Reg.MergeReceivers[0].OrderedStream) + require.False(t, restored.Proc.Reg.MergeReceivers[1].OrderedStream) + require.Equal(t, 1, cap(restored.Proc.Reg.MergeReceivers[0].Ch2)) + require.Equal(t, 1, restored.Proc.Reg.MergeReceivers[0].NilBatchCnt) + require.Equal(t, 3, cap(restored.Proc.Reg.MergeReceivers[1].Ch2)) + require.Equal(t, 2, restored.Proc.Reg.MergeReceivers[1].NilBatchCnt) + restoredTop, ok := restored.RootOp.(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, restoredTop.OrderedStreams) +} + +func TestRemoteRunOrderedMergeTopHierarchyRoundTrip(t *testing.T) { + nodes := engine.Nodes{ + {Id: "cn-local", Addr: "cn-local:6001", Mcpu: 2}, + {Id: "cn-remote", Addr: "cn-remote:6001", Mcpu: 2}, + } + c := newCompileForShuffleJoinTest(t, nodes) + rt := moruntime.ServiceRuntime(c.proc.GetService()) + oldVersion, hadVersion := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + rt.SetGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCLatestVersion) + t.Cleanup(func() { + if hadVersion { + rt.SetGlobalVariables(moruntime.MOProtocolVersion, oldVersion) + } else { + rt.CompareAndDeleteGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCLatestVersion) + } + }) + + limitExpr := plan.MakePlan2Uint64ConstExprWithType(20_000) + orderBy := []*planpb.OrderBySpec{{Expr: &planpb.Expr{ + Expr: &planpb.Expr_Col{Col: &planpb.ColRef{ColPos: 0}}, + Typ: planpb.Type{Id: int32(types.T_int64)}, + }}} + node := &planpb.Node{Limit: limitExpr, OrderBy: orderBy} + inputs := []*Scope{ + newRemoteMergeInputForTest(c, nodes[1], 0), + newRemoteMergeInputForTest(c, nodes[1], 0), + } + inputs[0].NodeInfo.Mcpu = 2 + for _, input := range inputs { + input.setRootOperator(top.NewArgument(). + WithLimit(limitExpr). + WithFs(orderBy). + WithOrderedOutput()) + } + group := c.newMergeTopScopeByCN(node, limitExpr, inputs, nodes[1]) + + data, err := encodeRemoteScope(group, c.proc) + require.NoError(t, err) + restored, err := decodeScope(data, c.proc, true, nil) + require.NoError(t, err) + t.Cleanup(func() { + restored.release() + group.release() + c.proc.Free() + }) + + require.Len(t, restored.PreScopes, 2) + require.Len(t, restored.Proc.Reg.MergeReceivers, 2) + restoredTop, ok := restored.RootOp.(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, restoredTop.OrderedStreams) + for i, input := range restored.PreScopes { + out, ok := input.RootOp.(*connector.Connector) + require.True(t, ok) + require.Same(t, restored.Proc.Reg.MergeReceivers[i], out.Reg) + require.True(t, out.Reg.OrderedStream) + localTop, ok := out.GetOperatorBase().GetChildren(0).(*top.Top) + require.True(t, ok) + require.True(t, localTop.OrderedOutput) + } + + restored.PreScopes[0].Proc.Ctx = context.Background() + ordered, workers := newParallelScope(restored.PreScopes[0]) + require.Equal(t, Merge, ordered.Magic) + require.True(t, ordered.ConcurrentPreScopes) + require.Len(t, workers, 2) + orderedOut, ok := ordered.RootOp.(*connector.Connector) + require.True(t, ok) + gatherTop, ok := orderedOut.GetOperatorBase().GetChildren(0).(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, gatherTop.OrderedStreams) +} + func TestTargetAwareUpdateRemoteProtocolValidation(t *testing.T) { ctx := &scopeContext{id: 1, root: &scopeContext{}, parent: &scopeContext{}} proc := testutil.NewProcess(t) diff --git a/pkg/sql/compile/scope.go b/pkg/sql/compile/scope.go index c7e6bc49697e3..374bd7635ad9d 100644 --- a/pkg/sql/compile/scope.go +++ b/pkg/sql/compile/scope.go @@ -40,11 +40,14 @@ import ( "github.com/matrixorigin/matrixone/pkg/perfcounter" "github.com/matrixorigin/matrixone/pkg/sql/colexec" "github.com/matrixorigin/matrixone/pkg/sql/colexec/aggexec" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/connector" "github.com/matrixorigin/matrixone/pkg/sql/colexec/dispatch" "github.com/matrixorigin/matrixone/pkg/sql/colexec/filter" "github.com/matrixorigin/matrixone/pkg/sql/colexec/group" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/mergetop" "github.com/matrixorigin/matrixone/pkg/sql/colexec/output" "github.com/matrixorigin/matrixone/pkg/sql/colexec/table_scan" + "github.com/matrixorigin/matrixone/pkg/sql/colexec/top" "github.com/matrixorigin/matrixone/pkg/sql/colexec/vectorscan" "github.com/matrixorigin/matrixone/pkg/sql/colexec/window" plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" @@ -492,7 +495,7 @@ func (s *Scope) MergeRun(c *Compile) (err error) { defer s.ScopeAnalyzer.Stop() // specific case. - if c.IsTpQuery() && !c.hasMergeOp { + if c.IsTpQuery() && !c.hasMergeOp && !s.ConcurrentPreScopes { for i := len(s.PreScopes) - 1; i >= 0; i-- { err := s.PreScopes[i].MergeRun(c) if err != nil { @@ -959,7 +962,7 @@ func buildLoadParallelRun(s *Scope, c *Compile) (*Scope, error) { return nil, err } } - if err := c.attachRuntimeAllocationOwners(ss); err != nil { + if err := c.attachRuntimeAllocationOwners([]*Scope{ms}); err != nil { s.discardParallelGeneration(ms) return nil, err } @@ -1014,7 +1017,7 @@ func buildScanParallelRun(s *Scope, c *Compile) (*Scope, error) { RecvMsgList: recvMsgList, } } - if err := c.attachRuntimeAllocationOwners(ss); err != nil { + if err := c.attachRuntimeAllocationOwners([]*Scope{ms}); err != nil { s.discardParallelGeneration(ms) return nil, err } @@ -1255,6 +1258,9 @@ func newParallelScope(s *Scope) (*Scope, []*Scope) { panic("pipeline end with dispatch should have been merged in multi CN!") } } + if ordered, workers, ok := newOrderedTopParallelScope(s); ok { + return ordered, workers + } // fake scope is used to merge parallel scopes, and do nothing itself rs := newScope(Normal) @@ -1284,6 +1290,87 @@ func newParallelScope(s *Scope) (*Scope, []*Scope) { return rs, parallelScopes } +// newOrderedTopParallelScope preserves the stream boundary required by a +// distributed MergeTop. Each DOP worker first produces an ordered local Top-K +// stream on its own edge; a single leaf MergeTop consolidates those streams +// before anything is returned to another CN or to the coordinator. +func newOrderedTopParallelScope(s *Scope) (*Scope, []*Scope, bool) { + var ( + sourceRoot vm.Operator + externalReg *process.WaitRegister + externalOp *connector.Connector + topOp *top.Top + ) + switch root := s.RootOp.(type) { + case *top.Top: + sourceRoot = root + topOp = root + case *connector.Connector: + if root.Reg == nil || root.Reg.NilBatchCnt != 1 || !root.Reg.OrderedStream { + return nil, nil, false + } + if root.GetOperatorBase().NumChildren() != 1 { + return nil, nil, false + } + child, ok := root.GetOperatorBase().GetChildren(0).(*top.Top) + if !ok { + return nil, nil, false + } + sourceRoot = child + topOp = child + externalReg = root.Reg + externalOp = root + default: + return nil, nil, false + } + if !topOp.OrderedOutput || !hasMaterializedTopOrderColumns(topOp.Fs) { + return nil, nil, false + } + + workerCount := s.NodeInfo.Mcpu + gather := newScope(Merge) + gather.ConcurrentPreScopes = true + gather.NodeInfo = s.NodeInfo + gather.NodeInfo.Mcpu = 1 + gather.Proc = s.Proc.NewContextChildProc(workerCount) + gather.TxnOffset = s.TxnOffset + + workers := make([]*Scope, workerCount) + dupCtx := newOperatorDupContext() + for i := 0; i < workerCount; i++ { + reg := gather.Proc.Reg.MergeReceivers[i] + reg.ResetForReuse(1, 1) + reg.OrderedStream = true + worker := newScope(Normal) + worker.NodeInfo = s.NodeInfo + worker.NodeInfo.Mcpu = 1 + worker.Proc = gather.Proc.NewContextChildProc(0) + worker.TxnOffset = s.TxnOffset + workerRoot := dupOperatorRecursivelyWithContext( + sourceRoot, i, workerCount, dupCtx) + conn := connector.NewArgument().WithReg(reg) + conn.SetAnalyzeControl(topOp.GetIdx(), false) + conn.AppendChild(workerRoot) + worker.setRootOperator(conn) + workers[i] = worker + } + + gatherTop := mergetop.NewArgument().WithLimit(topOp.Limit).WithFs(topOp.Fs).WithOrderedStreams() + gatherTop.SetAnalyzeControl(topOp.GetIdx(), false) + if externalReg == nil { + gather.setRootOperator(gatherTop) + } else { + out := connector.NewArgument().WithReg(externalReg) + out.SetAnalyzeControl(externalOp.GetIdx(), false) + out.AppendChild(gatherTop) + gather.setRootOperator(out) + } + gather.PreScopes = workers + s.PreScopes = append(s.PreScopes, gather) + s.parallelGenerations = append(s.parallelGenerations, gather) + return gather, workers, true +} + func (s *Scope) doSetRootOperator(op vm.Operator) { if s.RootOp != nil { op.AppendChild(s.RootOp) diff --git a/pkg/sql/compile/scope_test.go b/pkg/sql/compile/scope_test.go index b18fb1357d1b2..3bbdad95cbefd 100644 --- a/pkg/sql/compile/scope_test.go +++ b/pkg/sql/compile/scope_test.go @@ -391,48 +391,80 @@ func TestScopeSerialization(t *testing.T) { func TestCompileOrderByLimitOffsetUsesTopCandidateBudget(t *testing.T) { catalog.SetupDefines("") - scope := generateScopeCases(t, []string{ - "select n_regionkey from nation order by n_regionkey limit 2 + 3 offset 0 + 2", - })[0] - - var topLimits []uint64 - var offsets []uint64 - var opTypes []vm.OpType - var visitOperator func(vm.Operator) - visitOperator = func(operator vm.Operator) { - if operator == nil { - return - } - base := operator.GetOperatorBase() - for i := 0; i < base.NumChildren(); i++ { - visitOperator(base.GetChildren(i)) - } - opTypes = append(opTypes, operator.OpType()) - switch op := operator.(type) { - case *top.Top: - topLimits = append(topLimits, op.Limit.GetLit().GetU64Val()) - case *mergetop.MergeTop: - topLimits = append(topLimits, op.Limit.GetLit().GetU64Val()) - case *offset.Offset: - offsets = append(offsets, op.OffsetExpr.GetLit().GetU64Val()) - } - } - var visitScope func(*Scope) - visitScope = func(current *Scope) { - visitOperator(current.RootOp) - for _, preScope := range current.PreScopes { - visitScope(preScope) - } + tests := []struct { + name string + sql string + candidateLimit uint64 + offset uint64 + expectResidentGather bool + }{ + { + name: "resident candidate prefix", + sql: "select n_regionkey from nation order by n_regionkey limit 2 + 3 offset 0 + 2", + candidateLimit: 7, + offset: 2, + expectResidentGather: true, + }, + { + name: "candidate prefix above resident threshold", + sql: "select n_regionkey from nation order by n_regionkey limit 8193 offset 8192", + candidateLimit: mergeTopResidentPlanThreshold + 1, + offset: 8192, + }, } - visitScope(scope) - require.NotEmpty(t, topLimits) - for _, candidateLimit := range topLimits { - require.Equal(t, uint64(7), candidateLimit) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + scope := generateScopeCases(t, []string{test.sql})[0] + var topLimits []uint64 + var offsets []uint64 + var opTypes []vm.OpType + var visitOperator func(vm.Operator) + visitOperator = func(operator vm.Operator) { + if operator == nil { + return + } + base := operator.GetOperatorBase() + for i := 0; i < base.NumChildren(); i++ { + visitOperator(base.GetChildren(i)) + } + opTypes = append(opTypes, operator.OpType()) + switch op := operator.(type) { + case *top.Top: + topLimits = append(topLimits, op.Limit.GetLit().GetU64Val()) + case *mergetop.MergeTop: + topLimits = append(topLimits, op.Limit.GetLit().GetU64Val()) + case *offset.Offset: + offsets = append(offsets, op.OffsetExpr.GetLit().GetU64Val()) + } + } + var visitScope func(*Scope) + visitScope = func(current *Scope) { + visitOperator(current.RootOp) + for _, preScope := range current.PreScopes { + visitScope(preScope) + } + } + visitScope(scope) + + if !test.expectResidentGather { + require.Empty(t, topLimits) + require.Contains(t, opTypes, vm.Order) + require.Contains(t, opTypes, vm.MergeOrder) + require.Contains(t, offsets, test.offset) + return + } + require.NotEmpty(t, topLimits) + for _, candidateLimit := range topLimits { + require.Equal(t, test.candidateLimit, candidateLimit) + } + require.Contains(t, offsets, test.offset) + require.NotContains(t, opTypes, vm.Order) + if test.expectResidentGather { + require.NotContains(t, opTypes, vm.MergeOrder) + } + }) } - require.Contains(t, offsets, uint64(2)) - require.NotContains(t, opTypes, vm.Order) - require.NotContains(t, opTypes, vm.MergeOrder) } func checkScopeRoot(t *testing.T, s *Scope) { @@ -776,6 +808,57 @@ func TestNewParallelScope(t *testing.T) { } } +func TestNewParallelScopeConsolidatesOrderedTopStreams(t *testing.T) { + c := NewMockCompile(t) + external := process.NewPipelineEdge(1, 1) + external.OrderedStream = true + limitExpr := plan2.MakePlan2Uint64ConstExprWithType(100000) + orderBy := []*plan.OrderBySpec{{ + Expr: &plan.Expr{ + Expr: &plan.Expr_Col{Col: &plan.ColRef{ColPos: 0}}, + Typ: plan.Type{Id: int32(types.T_int64)}, + }, + }} + localTop := top.NewArgument().WithLimit(limitExpr).WithFs(orderBy).WithOrderedOutput() + localTop.AppendChild(table_scan.NewArgument()) + out := connector.NewArgument().WithReg(external) + out.AppendChild(localTop) + s := &Scope{ + Magic: Normal, + NodeInfo: engine.Node{Mcpu: 4}, + Proc: c.proc, + RootOp: out, + } + + ordered, workers := newParallelScope(s) + require.Equal(t, Merge, ordered.Magic) + require.True(t, ordered.ConcurrentPreScopes) + require.Equal(t, 1, ordered.NodeInfo.Mcpu) + require.Len(t, workers, 4) + require.Len(t, ordered.PreScopes, 4) + require.Len(t, ordered.Proc.Reg.MergeReceivers, 4) + + orderedOut, ok := ordered.RootOp.(*connector.Connector) + require.True(t, ok) + require.Same(t, external, orderedOut.Reg) + globalTop, ok := orderedOut.GetOperatorBase().GetChildren(0).(*mergetop.MergeTop) + require.True(t, ok) + require.True(t, globalTop.OrderedStreams) + require.Same(t, limitExpr, globalTop.Limit) + for i, worker := range workers { + workerOut, ok := worker.RootOp.(*connector.Connector) + require.True(t, ok) + require.Same(t, ordered.Proc.Reg.MergeReceivers[i], workerOut.Reg) + require.Equal(t, 1, workerOut.Reg.NilBatchCnt) + require.True(t, workerOut.Reg.OrderedStream) + _, ok = workerOut.GetOperatorBase().GetChildren(0).(*top.Top) + require.True(t, ok) + } + + s.release() + c.proc.Free() +} + func TestParallelScopeGenerationsReleasedAtCompileResetBoundary(t *testing.T) { testCompile := NewMockCompile(t) testCompile.isPrepare = true diff --git a/pkg/sql/compile/types.go b/pkg/sql/compile/types.go index 6f6171acbf81b..03830328d848f 100644 --- a/pkg/sql/compile/types.go +++ b/pkg/sql/compile/types.go @@ -199,6 +199,10 @@ type Scope struct { // branch receiver is exhausted, so an outer LIMIT can leave later branches // completely unstarted. LazyPreScopes bool + // ConcurrentPreScopes forces producer/consumer concurrency for runtime + // scope trees whose bounded receiver channels would deadlock under the TP + // query's sequential fast path. + ConcurrentPreScopes bool // parallelGenerations are execution-created scope trees retained only so // post-run physical-plan analysis can observe their real DOP and stats. // Compile.Reset releases the previous execution's trees before the template diff --git a/pkg/tests/dml/dml_test.go b/pkg/tests/dml/dml_test.go index e422819d35c1f..ac1694c9b3fa8 100644 --- a/pkg/tests/dml/dml_test.go +++ b/pkg/tests/dml/dml_test.go @@ -29,8 +29,10 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/require" + "github.com/matrixorigin/matrixone/pkg/clusterservice" "github.com/matrixorigin/matrixone/pkg/embed" "github.com/matrixorigin/matrixone/pkg/objectio" + "github.com/matrixorigin/matrixone/pkg/pb/metadata" "github.com/matrixorigin/matrixone/pkg/sql/plan" "github.com/matrixorigin/matrixone/pkg/tests/testutils" "github.com/matrixorigin/matrixone/pkg/util/executor" @@ -44,6 +46,23 @@ func TestForcedMultiCNDeleteAndInsertIgnore(t *testing.T) { cn, err := c.GetCNService(0) require.NoError(t, err) + // Service startup does not imply that the query coordinator's cached + // inventory contains both workers. Establish that precondition before + // asserting the distributed Top topology. + cluster := clusterservice.GetMOCluster(cn.ServiceID()) + refresher, ok := cluster.(clusterservice.AuthoritativeRefresher) + require.True(t, ok) + require.Eventually(t, func() bool { + if refresher.Refresh(ctx) != nil { + return false + } + workers := 0 + cluster.GetCNService(clusterservice.NewSelector(), func(metadata.CNService) bool { + workers++ + return true + }) + return workers == 2 + }, 30*time.Second, 100*time.Millisecond, "both CN workers must be discoverable") internalExec := testutils.GetSQLExecutor(cn) port := cn.GetServiceConfig().CN.Frontend.Port db, err := sql.Open("mysql", fmt.Sprintf("dump:111@tcp(127.0.0.1:%d)/", port)) @@ -74,6 +93,8 @@ func TestForcedMultiCNDeleteAndInsertIgnore(t *testing.T) { execSQLDB(t, ctx, db, "create table forced_src (v int)") execSQLDB(t, ctx, db, "insert into forced_src values (31),(31),(31),(31)") execSQLDB(t, ctx, db, "create table forced_dst (b bit(4))") + execSQLDB(t, ctx, db, "create table forced_top_src (id bigint, k bigint, payload varchar(64))") + execSQLDB(t, ctx, db, "insert into forced_top_src select result, 99999-result, repeat('x',64) from generate_series(0,99999) g") // Force only the operations under test. Applying this process-wide test // hook to fixture DDL would exercise an unrelated execution path and can @@ -81,6 +102,74 @@ func TestForcedMultiCNDeleteAndInsertIgnore(t *testing.T) { defer plan.SetForceScanOnMultiCN(false) plan.SetForceScanOnMultiCN(true) + t.Run("remote top gathers workers before write back", func(t *testing.T) { + execSQLDB(t, ctx, db, "use `"+castDB+"`") + // Varlen payload selects the ordered hierarchy. Assert the actual + // gather topology, not just the MULTICN execution-mode header. + query := "select id,k,payload from forced_top_src order by k limit 1000" + physical, planErr := testutils.QueryTextResult(ctx, db, "explain phyplan "+query) + require.NoError(t, planErr) + require.Contains(t, strings.ToUpper(physical.ColumnName), "PHYPLAN ON MULTICN(") + require.Contains(t, physical.Text, "Magic: Remote") + require.Contains(t, strings.ToLower(physical.Text), "merge top") + rows, queryErr := db.QueryContext(ctx, query) + require.NoError(t, queryErr) + defer rows.Close() + count := 0 + for rows.Next() { + var id, key int64 + var payload string + require.NoError(t, rows.Scan(&id, &key, &payload)) + require.Equal(t, int64(count), key) + require.Equal(t, int64(99999-count), id) + require.Equal(t, strings.Repeat("x", 64), payload) + count++ + } + require.NoError(t, rows.Err()) + require.NoError(t, rows.Close()) + require.Equal(t, 1000, count) + }) + + t.Run("bounded top preserves remote order and prepared reuse", func(t *testing.T) { + execSQLDB(t, ctx, db, "use `"+deleteDB+"`") + query := "select a,b from " + deleteTable + " order by a desc limit 2" + physical, planErr := testutils.QueryTextResult(ctx, db, "explain phyplan "+query) + require.NoError(t, planErr) + require.Contains(t, strings.ToUpper(physical.ColumnName), "PHYPLAN ON MULTICN(") + require.Contains(t, physical.Text, "Magic: Remote") + // A tiny source can collapse to one remote worker. The larger case + // above proves the gather topology; this control proves exact rows + // and prepared reuse without requiring an unnecessary merge. + readRows := func(rows *sql.Rows) [][2]string { + t.Helper() + var got [][2]string + for rows.Next() { + var row [2]string + require.NoError(t, rows.Scan(&row[0], &row[1])) + got = append(got, row) + } + return got + } + rows, queryErr := db.QueryContext(ctx, query+" offset 1") + require.NoError(t, queryErr) + defer rows.Close() + require.Equal(t, [][2]string{{"7", "7"}, {"3", "3"}}, readRows(rows)) + require.NoError(t, rows.Err()) + require.NoError(t, rows.Close()) + stmt, prepareErr := db.PrepareContext(ctx, + "select a,b from "+deleteTable+" order by a desc limit ?") + require.NoError(t, prepareErr) + defer stmt.Close() + for range 2 { + rows, queryErr = stmt.QueryContext(ctx, 2) + require.NoError(t, queryErr) + defer rows.Close() + require.Equal(t, [][2]string{{"8", "8"}, {"7", "7"}}, readRows(rows)) + require.NoError(t, rows.Err()) + require.NoError(t, rows.Close()) + } + }) + t.Run("delete and select retain exact rows", func(t *testing.T) { execSQLDB(t, ctx, db, "use `"+deleteDB+"`") planResult, planErr := testutils.QueryTextResult(ctx, db, diff --git a/pkg/vm/process/pipeline_edge.go b/pkg/vm/process/pipeline_edge.go index b59d1f29e134a..fed3167e30141 100644 --- a/pkg/vm/process/pipeline_edge.go +++ b/pkg/vm/process/pipeline_edge.go @@ -43,6 +43,11 @@ type PipelineEdge struct { // channel must receive before it is considered done. 0 defaults to 1. NilBatchCnt int + // OrderedStream marks an edge whose single producer must preserve total + // ordering across batch boundaries. It is compile/runtime topology metadata, + // not a data signal property. + OrderedStream bool + // --- terminal state --- done chan struct{} abrt chan struct{} @@ -104,6 +109,7 @@ func (e *PipelineEdge) ResetForReuse(channelBufferSize int, nilBatchCnt int) { done: e.Ch2 = make(chan PipelineSignal, channelBufferSize) e.NilBatchCnt = nilBatchCnt + e.OrderedStream = false e.resetTerminalStateLocked() } diff --git a/pkg/vm/process/pipeline_edge_test.go b/pkg/vm/process/pipeline_edge_test.go index 0233b0cbc4cf9..e70bde8ad38c0 100644 --- a/pkg/vm/process/pipeline_edge_test.go +++ b/pkg/vm/process/pipeline_edge_test.go @@ -145,6 +145,15 @@ func TestWaitPipelineSignalCapacityWaitsUntilChannelDrains(t *testing.T) { } } +func TestPipelineEdgeResetClearsOrderedStreamContract(t *testing.T) { + edge := NewPipelineEdge(1, 1) + edge.OrderedStream = true + edge.ResetForReuse(2, 2) + if edge.OrderedStream { + t.Fatal("ResetForReuse retained a stale ordered-stream contract") + } +} + func TestWaitPipelineSignalCapacityReturnsOnContextCancel(t *testing.T) { edge := NewPipelineEdge(1, 1) edge.Ch2 <- NewPipelineSignalToDirectly(nil, nil, nil) diff --git a/proto/pipeline.proto b/proto/pipeline.proto index 03f252095ba3f..5d7088f4eae44 100644 --- a/proto/pipeline.proto +++ b/proto/pipeline.proto @@ -644,6 +644,12 @@ message Instruction { // gated by MORPCVersion49 until every participant understands them. repeated bool projection_grouping_flags = 60; int32 projection_grouping_set_count = 61; + // TOP only: every runtime worker must be consolidated into one stream that + // remains ordered across batch boundaries before leaving its CN. + bool top_ordered_output = 62; + // MERGE_TOP only: consume one independently ordered stream from every + // receiver. This is serialized for per-CN ordered gathers sent by RemoteRun. + bool merge_top_ordered_streams = 63; } message AnalysisList { @@ -790,6 +796,9 @@ message Pipeline { repeated int32 nil_batch_cnt = 13; repeated UuidToRegIdx uuids_to_reg_idx = 14; + // Per receiver, preserve the single-producer ordered-stream contract across + // RemoteRun. Empty means the legacy unordered default. + repeated bool ordered_stream = 15; } message WrapNode { diff --git a/test/distributed/cases/dml/select/limit.result b/test/distributed/cases/dml/select/limit.result index ff39ad27265b1..5b97a7a02ce6b 100644 --- a/test/distributed/cases/dml/select/limit.result +++ b/test/distributed/cases/dml/select/limit.result @@ -65,4 +65,26 @@ drop table if exists t1; create table t1 (a int primary key, b varchar); insert into t1 select result, repeat("abcdefg",500) from generate_series (1, 30000)g; select a, left(b,3) from t1 order by a desc limit 32000, 2; -a left(b, 3) \ No newline at end of file +a left(b, 3) +select a, left(b,3) from t1 order by a desc limit 29999, 1; +a left(b, 3) +1 abc +select a, left(b,3) from t1 order by a desc limit 2, 3; +a left(b, 3) +29998 abc +29997 abc +29996 abc +prepare bounded_top from 'select a, left(b,3) from t1 order by a desc limit ?'; +set @bounded_k = 3; +execute bounded_top using @bounded_k; +a left(b, 3) +30000 abc +29999 abc +29998 abc +execute bounded_top using @bounded_k; +a left(b, 3) +30000 abc +29999 abc +29998 abc +deallocate prepare bounded_top; +drop table t1; diff --git a/test/distributed/cases/dml/select/limit.sql b/test/distributed/cases/dml/select/limit.sql index dd78c04e03526..59ca2778e5427 100644 --- a/test/distributed/cases/dml/select/limit.sql +++ b/test/distributed/cases/dml/select/limit.sql @@ -24,4 +24,15 @@ select * from t1 order by a limit 700000, 2; drop table if exists t1; create table t1 (a int primary key, b varchar); insert into t1 select result, repeat("abcdefg",500) from generate_series (1, 30000)g; -select a, left(b,3) from t1 order by a desc limit 32000, 2; \ No newline at end of file +select a, left(b,3) from t1 order by a desc limit 32000, 2; +-- A large OFFSET retains the external-sort path and returns the exact tail. +select a, left(b,3) from t1 order by a desc limit 29999, 1; +-- Small K with varlen payload must select bounded output independently of +-- estimated cardinality/width. Exact order and prepared reuse are public oracles. +select a, left(b,3) from t1 order by a desc limit 2, 3; +prepare bounded_top from 'select a, left(b,3) from t1 order by a desc limit ?'; +set @bounded_k = 3; +execute bounded_top using @bounded_k; +execute bounded_top using @bounded_k; +deallocate prepare bounded_top; +drop table t1;