ken_payne
01/17/2023, 10:59 PMalexander_butler
01/18/2023, 7:15 AMtap-salesforce | target-singer-jsonl would produce a tap-salesforce.singerlake or some such metadata file. The subsequent tap-singer-jsonl only needs to point to this file to replicate a specific taps output instead of pointing to a path like raw/<tap_id>/<stream_name>/*.jsonl. A few reasons for this are listed below:
Generally speaking the problem with parallelization would be that you don't know when the schema changes. We can only parallelize messages sharing the same schema safely since there would definitely be some sort of contention parallelizing conflicting schemas.
This metadata files solves for that. The below illustrates the general idea.
{
"schema_hashes": [...], // chronologically sorted array of md5(schema_message) hashes, ie. append only updates
"files": {
"<hash>": [...] // files that can be massively parallelized
"<hash>": [...]
}
}
A state file then looks like this which incidentally solves for not needing the scrape bucket metadata or recursively list files in order to parse timestamp ranges whilst keeping the state message tame.
{
"bookmarks": {
"current-hash": "<hash>", // the last processed md5(schema) hash
"last-index": 123 // the last index of the associated array representing a file
}
}
You can with a single file read + the state message, immediately start reading in parallel with a known queue size.alexander_butler
01/18/2023, 7:20 AMSven Balnojan
01/18/2023, 10:55 AMken_payne
01/18/2023, 11:47 AMraw/<tap_id>/<stream_name>/<schema version>/.... I imagine this schema version would be relative rather than absolute (i.e. the first schema observed is v1 and the first change is v2 etc.). This is also common in API design.
β’ For even more parallelism, we could encode the year/month/day into the prefix too. This essentially pre-chunks the data, as we already know the dimensions will be 'observed date' based: raw/<tap_id>/<stream_name>/<schema version>/<YYYY>/<MM>/<DD>/<HH>/.... I'd say the <HH> extent of chunking could be configurable - not every use case will necessitate <HH> or even <DD> granularity π
It means slightly more work for the target (as it has to start new files on the chunk boundaries) but saves the tap having to deal with files that contain records in the middle of its preferred chunk
Both of these combined would reduce the metadata/inventory file to a flat list of file paths, from which parallelism decisions can be made π WDYT?ken_payne
01/18/2023, 11:50 AMalexander_butler
01/18/2023, 3:46 PM{
"versions": {"<hash>": "v1", "<hash>": "v2", ...},
"files": [...]
}alexander_butler
01/18/2023, 3:48 PMfiles array.ken_payne
01/18/2023, 4:08 PMken_payne
01/18/2023, 4:12 PMsingerlake package is a best next step? This would implement the file and prefix creation logic, and present a high-level API for .write_message() and .get_messages(). This could later be imported by Tap/Target implementations down the line π Ambitiously, it could be called python-singerlake on the assumption that we nail the spec and other languages adopt it π
WDYT?alexander_butler
01/18/2023, 4:41 PMsomething is bound to fail before updating the metadata file. I.e. the metadata is an accelerator, but the spec can fall back onto a "full scan" or list in its absence.Agree with this π―. Metadata is an accelerator rather than a point-of-failure. βοΈ One difference though is I was thinking one singerlake file to one tap. This means one
tap-salesforce.singerlake, one tap-github.singerlake, and so on. This reduces the chance of multiple taps writing to the same file. An implication of this which I did not mention is that the file path + a stem could double as a lock. We only need to add a tap-salesforce.singerlake.lock to the bucket to ensure another target-jsonl-singer pointing at the exact same metadata file will not try to sync concurrently. I think a locking mechanism is important.
EDIT: Also by virtue of one tap to one metadata file, tap-jsonl-singer only needs to point to a specific file to replicate an instance of a tap vs a consolidated metadata file of all taps which may contend for updates.
This means, ideally, updates are fairly safe in 99% of situations. That being said, the "full scan" when needed and the ability to reconstruct the metadata file if needed are both very important pieces. nod
Lastly on this point,
raw/tap-salesforce.singerlake
raw/tap-salesforce.singerlake.lock
raw/tap-salesforce/account/<FarmHash_A>/...
raw/tap-salesforce/account/<FarmHash_B>/...
raw/tap-salesforce/contact/<FarmHash_B>/...
The lake might look like this. The raw (base path) should be configurable and based on environment ideally. This means prod / dev route to different places and to different metadata files / different locks. π
Googles' FarmHash has a fingerprint function that will reduce a string to an unsigned int64, which when base85 encoded yields 7 characters.Wow thats awesome. The implications are you dont need the hashmap in a metadata file because the correct write path is known. So the file is again just a list of files which is nice. π₯
I wonder if a newI think this is a good idea. With that, in theory even without a tap or target could just have a BigQuery/Snowflake/or whatever loader dependent onpackage is a best next step?singerlake
python-singerlake that uses .get_messages() directly, manages its own state, and do away with the "single file descriptor everything must pass through (stdout)". thinkspinalexander_butler
01/18/2023, 5:04 PMgo
Also preemptive yielding to me seems more no fuss than cooperative yieldingalexander_butler
01/18/2023, 5:06 PMken_payne
01/18/2023, 5:07 PM