feat: make the hash input cast datatype configurable per adapter - #497
Open
tkirschke wants to merge 1 commit into
Open
feat: make the hash input cast datatype configurable per adapter#497tkirschke wants to merge 1 commit into
tkirschke wants to merge 1 commit into
Conversation
Before hashing, both a single input column and the fully concatenated payload are casted to a string datatype. These datatypes were hardcoded per adapter. On SQL Server the hardcoded value was VARCHAR(MAX), which is a large-value type: it is stored off-row and blocks several optimizations for the REPLACE(), UPPER() and HASHBYTES() calls wrapped around it. A user report measured a satellite load dropping from over a minute to a few seconds after switching to VARCHAR(8000). Both casts are now configurable through the new global variables `datavault4dbt.hash_input_attribute_dtype` and `datavault4dbt.hash_input_concat_dtype`. Both are adapter-keyed mapping dictionaries, following `datavault4dbt.beginning_of_all_times`. The current hardcoded values are kept as defaults, so the generated SQL is unchanged unless a user overrides them. Multi Active Satellites are deliberately excluded and keep their hardcoded datatype. Their payload is aggregated across all active records of one group before it is hashed, and on SQL Server STRING_AGG() only returns VARCHAR(MAX) if its input is VARCHAR(MAX) - with a shorter input it raises an error once a group exceeds 8000 bytes. Excluding them lets users shorten the cast for all other entities without that risk. Note that a datatype shorter than the actual hash input truncates it silently on most adapters, which changes the resulting hash values. This is documented on the global variables page and in the SQL Server notes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
A user profiling a slow full refresh on MS SQL Server reported that changing the
VARCHAR(MAX)casts inhash_standardization.sqltoVARCHAR(8000)took a single satellite load from over a minute to a few seconds.The report holds up.
VARCHAR(MAX)is a large-value type: stored off-row, not handled in memory like a regularVARCHAR(n), and it blocks several optimizations for theREPLACE()/UPPER()/HASHBYTES()chain wrapped around it. There was no design reason forMAXspecifically — Synapse and Fabric, which share the T-SQL codepath, already usedVARCHAR(4000). SQL Server was the outlier.Changes
Two casts happen before hashing, and they are now configurable independently:
datavault4dbt.hash_input_attribute_dtypeattribute_standardisedatavault4dbt.hash_input_concat_dtypeconcattenated_standardiseBoth are adapter-keyed mapping dictionaries following the
datavault4dbt.beginning_of_all_timesconvention, resolved by the newmacros/supporting/hash_input_dtype.sql. That macro follows the lighterfirst_day_of_week.sqlpattern (singledefault__implementation usingtarget.type) rather than the 11-macro-per-adapterstring_default_dtype.sqlpattern.The current hardcoded values are kept as defaults:
STRINGSTRINGVARCHAR(20000) UTF8VARCHAR(2000000) UTF8VARCHARVARCHARVARCHARVARCHAR(4000)VARCHAR(4000)VARCHAR2(2000)VARCHAR2(2000)VARCHAR(MAX)VARCHAR(MAX)Multi Active Satellites are deliberately excluded
multi_active_concattenated_standardisekeeps its hardcoded datatype for all adapters and is untouched by this PR.The reason is
STRING_AGG(), which aggregates the payload of all active records of one group before hashing. On SQL Server it only returnsVARCHAR(MAX)if its input expression isVARCHAR(MAX); with a shorter input it returnsVARCHAR(8000)and raises an error once a group's aggregate exceeds 8000 bytes. Since that limit applies to a whole group rather than a single record, it is far easier to hit than the per-record limit of a regular satellite.Excluding MA sats gives the property we want: a user can shorten the cast for hubs, links and regular satellites and get the speedup, with no way to accidentally break their MA sats through a global variable.
Truncation caveat
A datatype shorter than the actual hash input truncates it silently on most adapters. Truncated input changes the resulting hashes, and two rows differing only past the truncation point collapse into the same hashkey or hashdiff. Any later change to these variables is a full reload of the affected entities. This is called out on the global variables page and in the SQL Server adapter notes.
Verification
No dbt profile was available, so this was verified statically:
standardise_prefix/standardise_suffix/exprexpression was extracted from bothmainand this branch, evaluated with the shipped defaults, and compared — all 33 macros render byte-identical tomain. The generated SQL does not change for anyone who does not override the variables.multi_activeblocks are textually identical tomain, not just render-equivalent.dbt_project.ymlparses as YAML; both macro files pass a Jinja parse.hash_input_dtypewas render-tested across all branches: dict hit, dict missing the own adapter (warning + fallback), scalar override, and variable absent entirely.Note
redshift__attribute_standardisehas no cast at all — it trims the raw expression. Rather than introduce one and change Redshift behavior, it was left as is, soredshifthas no key inhash_input_attribute_dtype. The macro is never called withtype='attribute'on Redshift, so no warning fires in practice.🤖 Generated with Claude Code