This browser is blocking local storage, so progress will be kept in memory only and lost on reload. Open the file directly rather than through a restricted context, or allow storage for this page.
Cost and correctness.
Everything else
is
vocabulary.
A complete preparation guide for Google Cloud data engineering interviews, covering BigQuery, Dataflow, Pub/Sub, Dataproc and Composer, plus the Databricks and Snowflake questions that come with them. Built around the two things every round actually tests: whether you can reason about the bill out loud, and whether you know where the duplicates go.
What a GCP data engineering loop actually grades
Five rounds, and one question underneath all of them: can you make a warehouse cheap and correct at the same time.
Current GCP data engineer postings converge on a short list. Five services appear in nearly every one: BigQuery, Dataflow, Pub/Sub, Cloud Storage and Composer. Dataproc appears in roughly two thirds. Python and SQL are stated as non negotiable. A second tier shows up in senior postings: dbt, Terraform, CI/CD, IAM, and cost optimisation named as an achievement rather than a skill.
Many postings also pair Google Cloud with Databricks or Snowflake or both, which is why this guide covers all three. If a posting mentions only one, read that platform's modules and skim the others, because the comparison questions still come.
One posting put the bar better than any study guide: "Ability to explain real world architecture, scalability, and performance optimization scenarios, not just tool definitions." That is the whole exam.
Where the weight actually sits
| Area | Share | Why |
|---|---|---|
| BigQuery storage, compute, cost, query craft | ~35% | It is the warehouse, the compute engine, the bill and the interview |
| Streaming: Pub/Sub, Dataflow, Beam semantics | ~15% | Where correctness questions live |
| Databricks: Delta, Unity Catalog, DLT | ~15% | Wherever the lakehouse or the ML teams are |
| Snowflake: micro-partitions, warehouses, Streams | ~15% | Wherever the governed consumption layer is |
| Modeling, orchestration, governance, SQL | ~20% | Cheap points, and the part most candidates under-prepare |
Compare that to the official Professional Data Engineer blueprint, which weights its five domains 22 / 25 / 20 / 15 / 18. The certification spreads evenly; real interviews do not. If you are optimising for a job rather than a badge, weight your hours toward cost and correctness.
The three questions underneath everything
- Where does the data live and in what shape. Partition, cluster, format, open or native.
- Who is paying and how do you know. Bytes scanned, slot seconds, credits, DBUs, dollars per run.
- What happens when it is wrong. Idempotency, backfill, replay, late data, dedup.
Every case in module 21 is one of those three wearing a costume.
The five rounds
| Round | Runs | The real question |
|---|---|---|
| Recruiter screen | 20 to 30 min | Can you compress your career into two minutes and land one number |
| Hiring manager | 45 min | Did you own the cost and the correctness, or just write the pipelines |
| Technical deep dive | 60 min | Do you know one platform at production depth, or all of them at brochure depth |
| System design | 45 to 60 min | Can you place a service, name a failure mode, and end on money |
| Behavioral | 45 min | Evidence, not adjectives |
How to use this guide
- Follow the path. The left nav is ordered. Each module has a read time, retrieval cards and a checkpoint that unlocks once you mark it read.
- Recall mode (press r) blurs the answers so you have to produce them rather than recognise them.
- The retrieval cards are the part that works. Reading a module moves almost nothing. Grading a card honestly schedules the next review, and grading everything Easy just deletes the feature.
- The session timer (press space) is there so practice is timed. Interview answers are length constrained and untimed practice does not teach that.
- Everything is stored in this browser only. Export your progress before clearing site data.
If you have no production experience on one of these platforms, do not hide it. Module 01 has the honest framing, and it works better than a bluff that collapses on the second question.
Cost and correctness. If I can reason about bytes and slot time out loud, and name the delivery guarantee at every hop, I can hold any round in this loop.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Audit your own resume before someone else does
Every noun on a resume is a question you invited. This is the method for finding the ones you cannot defend.
Dense, keyword rich resumes pass the applicant tracking system and then create a problem in the room. Every named feature is a question you have invited. If a resume says liquid clustering, Search Optimization Service, Change Data Feed or Dynamic Tables, any of those can be picked out and probed for five minutes. The ATS rewards breadth; the interview punishes breadth without depth.
The three way audit
Print your resume. Mark every technical noun with one of three marks:
- Own it. You can talk for three minutes and hold two follow ups.
- Study it. You have used it but could not defend it under pressure. This is your study list, and it should be short enough to finish.
- Cut it. You cannot defend it at all. A term you cannot defend is worse than a term that is absent, because it converts a good interview into a credibility conversation.
Be honest about the third category. Removing four terms costs you almost nothing on the ATS and removes the four worst moments available to an interviewer.
The claims that get probed hardest
| If your resume says | Expect | Have ready |
|---|---|---|
| A number of years on a cloud | So walk me through the earliest of those years. What did the estate look like then? | A concrete answer for the earliest period, with services that actually existed then. Check the general availability dates of anything you claim early. |
| A daily or monthly volume | How did you measure that, and what was it at trough? | Peak versus average, the unit, and the derivation. Module 03. |
| A percentage improvement | Measured how? | Pick one measure (wall clock, slot time or DBU, or cost per run) and name the mechanism that produced it. |
| A revenue figure | What did you personally build versus what did the product earn? | Separate your contribution from the business outcome yourself, before being asked. Claiming revenue you did not generate is the fastest way to lose a room. |
| A niche feature | How does it differ from the obvious alternative, and when would you not use it? | Two follow ups, or cut the term. |
| A platform you evaluated rather than ran | Tell me about running it in production | Say evaluated. Never say hands on about a prototype. |
| A team size or seniority claim | Name someone you mentored and what changed | One person, one thing they could not do, one thing they can do now. |
Check your timeline against product release dates. Candidates routinely claim a managed service during a period before it was generally available in that cloud, because they wrote the resume from the present backwards. An interviewer who knows the product history will catch it, and the damage is out of proportion to the error. Search the general availability date of anything you claim more than three years ago.
Separating contribution from outcome
Large numbers on a resume (revenue, user counts, transaction volume) belong to a product or a company, not to an engineer. Interviewers know this, and the candidate who says it first keeps the number. The candidate who has to be corrected loses it and some credibility with it.
The shape: "The product grew to [number]. I owned the data engineering area for it, which means the ingestion specs, the warehouse design, the metrics, and the reporting those decisions were made from."
That keeps the scale, attributes it honestly, and immediately redirects to what you actually did.
If you have no production experience on one of the platforms
This is common and it is survivable. What is not survivable is pretending. The four sentence answer:
"That is right, I have not run [platform] in production."
"I have run the equivalent at scale: [the real stack], and I owned its cost and correctness."
"So the concepts transfer directly. What I would be learning is [two specific things], not the fundamentals."
"I would expect that to take weeks rather than months, and I have been working through it against a real project rather than reading documentation."
Four sentences. Do not add a fifth. The fifth is where people start over explaining, and it reads as anxiety rather than honesty.
What genuinely transfers, and what does not
| Transfers unchanged | Genuinely different on a cloud warehouse |
|---|---|
| Pruning, predicate pushdown, join order | There is often no cluster to tune. The levers are partitioning, clustering and reservation shape. |
| Shuffle, skew, small files, plan reading | Cost is denominated in bytes, slot milliseconds, credits or DBUs rather than nodes and hours. |
| Event time, watermarks, late data, exactly once | Identity replaces cluster auth entirely, down to column and row level. |
| Dimensional modeling, grain, SCD, idempotency | You often cannot SSH anywhere. Debugging is logs, monitoring, system tables and the query plan. |
Volunteering the four differences in the right hand column is worth more than any claim of familiarity. It shows you have thought about the transition concretely. An interviewer hearing it is basically the same from someone without cloud production experience discounts everything that follows.
I have not run it in production. I ran the equivalent at scale and owned its cost and correctness, so the concepts transfer and what I would learn is the reservation model and the operational surface.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Twelve story scaffolds to fill with your own numbers
Every behavioral round and half of every technical round is one of these twelve. Build yours once, then rehearse them.
These are not stories to tell. They are shapes to fill. Write your version of each into a document, once, and then rehearse from that rather than improvising. Improvised answers ramble, bury the number, and explain the org chart.
Rules for all twelve
- Two minutes maximum. Time yourself. Almost everyone runs long and nobody notices while speaking.
- The number lands in the last fifteen seconds. Front loading it wastes it; burying it past two minutes loses it.
- Never explain the org chart. If your story needs three sentences of context to make sense, start at the problem instead.
- One concrete decision with a rejected alternative beats four vague accomplishments.
- Contribution separated from outcome, said by you, before being asked.
Do this once and it pays for the whole loop. Write all twelve, then record yourself delivering scaffolds 01, 02, 10 and 12. Play them back. You will hear yourself hedge, over explain and bury the number, and you cannot detect any of those live. Two passes is enough.
What situation this fits
Greenfield build. No existing data foundation, or an existing one that was fundamentally wrong.
What your version must contain
- What did not exist before, stated as a consequence rather than a gap. Not 'there was no pipeline' but 'decisions were being made on yesterday's picture'.
- The specification work: who you wrote the ingestion or logging spec with, and why doing it with them rather than after them mattered.
- One concrete design decision and the alternative you rejected.
- The breadth of what the same foundation ended up serving, because one pipeline feeding five consumers is a better story than five pipelines.
What the result has to be
What changed for the people who use it. Latency, or reach, or a decision that became possible.
How it lands
A scale or outcome number, with contribution separated from outcome.
What situation this fits
You saw a problem nobody was resourced to solve and you had no mandate to fix it.
What your version must contain
- The business case shape: what is at risk, what we currently cannot see, what it costs to find out.
- How you got sponsorship, and from whom. Name the level.
- That you then delivered it, because a pitch with no delivery is a story about talking.
- How it stayed alive afterwards. A standing review, an owner, a budget line.
What the result has to be
It moved from nobody looking to somebody accountable.
How it lands
The exposure or scope the work covers. Be precise that this is scope, not savings, unless you measured savings.
What situation this fits
Spend growing faster than the workload, which is the signature of behaviour rather than growth.
What your version must contain
-
That you measured before theorising. Name the system table:
INFORMATION_SCHEMA.JOBS,system.billing.usage, orACCOUNT_USAGE. - That you grouped by identity first, because a service account at the top is an automated leak worth ten times a human at the top.
- Two or three structural fixes with the mechanism named, not just 'optimised queries'.
- The control you added so it could not creep back. This is the part that separates a cleanup from an engineering change.
What the result has to be
Spend came down and stayed down.
How it lands
A percentage or absolute figure, plus the sentence: a report changes nothing, a quota changes behaviour.
What situation this fits
Legacy pipelines, some of which nobody fully understood, that had to move without breaking consumers.
What your version must contain
- How you ranked the work. By frequency and cost rather than alphabetically, so it paid for itself early.
- That you dual ran and reconciled rather than cutting over on faith.
- The tuning you did as part of the move, named specifically: partitioning, broadcast versus shuffle join selection, shuffle partition sizing, skew and small file handling, adaptive query execution.
- What you deliberately did not migrate, and why.
What the result has to be
Performance and reliability both improved, which is the unusual part, because migrations usually trade one for the other.
How it lands
A count and a percentage, with the measure named: wall clock, slot time, or cost per run.
What situation this fits
A regulator, an auditor, a privacy regime, or a contractual limit that set the boundaries before design started.
What your version must contain
- What the constraint actually changed day to day, concretely.
- That you agreed the constraints with the people who owned them before engineering, rather than building and reviewing after. That ordering is the story.
- How you proved correctness to someone who would not take your word: parallel runs, reconciliation against an incumbent, evidence from requirement through code to output.
- That quality checks were built into the flow rather than added afterwards as a report.
What the result has to be
The thing was approved on evidence rather than on confidence.
How it lands
The transferable principle: checks as deployment gates rather than dashboards.
What situation this fits
You were on call. Something failed in a way that was not obvious.
What your version must contain
- That you stabilised before diagnosing: tell the consumers, in the channel they watch, that the data is stale and when the next update will come.
- An ordered method rather than a list of guesses. Work outward from the symptom.
- The actual root cause, specifically. A vague incident story is worse than none.
- The permanent corrective action, and the assertion you added so the next occurrence goes red rather than green.
What the result has to be
Restored inside SLA, and the class of failure became detectable rather than discoverable.
How it lands
The line that lands: green means the process exited zero, it does not mean data moved.
What situation this fits
A requirement arrived as a given rather than as a decision, with a consequence nobody had priced.
What your version must contain
- That you asked what decision gets made on the output and how often, rather than arguing about the requirement abstractly.
- That you attached a cost or a risk number, which reframes preference into trade.
- One push, then commit. Say explicitly that you built what was asked when the answer came back unchanged.
- A case where you were the one who turned out to be wrong, if you have one. It is worth more than the case where you were right.
What the result has to be
The requirement either changed on evidence or stood on an informed decision. Both are good.
How it lands
Arguing twice makes you difficult. Never arguing makes you a contractor.
What situation this fits
Financial reconciliation, regulatory reporting, billing, or anything where a small error is a finding rather than a bug.
What your version must contain
- The declared grain of the model, stated in one sentence. An ambiguous grain is a reconciliation problem waiting.
- That you compared more than two legs where possible, because two systems agreeing proves less than three.
- That breaks were detected and categorized by the platform rather than found by a person under time pressure.
- What a break actually looks like and how it gets triaged.
What the result has to be
Discrepancies surfaced automatically, categorized, with an owner.
How it lands
The phrase: detected and categorized by the platform rather than found by an analyst during close.
What situation this fits
Checks on a dashboard get read after the bad data has already reached a report.
What your version must contain
- The four checks: freshness, volume, grain, distribution. Name that the first two catch the pipeline breaking and the last two catch it lying.
- That they run as deployment gates rather than dashboards.
- The tiering: grain and referential checks block because a wrong number is worse than a missing one; volume and distribution alert because they false positive on genuine business change.
- That every blocking check has a documented override with a name attached.
What the result has to be
A failing load stops rather than propagates.
How it lands
Most teams implement freshness and volume and get burned by grain and distribution.
What situation this fits
Anything where you did something beyond the standard pattern. New tooling, automation, an internal platform, an ML or AI system.
What your version must contain
- What problem it solved, framed as a recurring cost rather than a one off.
- The architecture in three or four sentences, not fifteen.
- How you measured whether it worked. This is the differentiator and almost nobody has it. Acceptance rates, labeled ground truth, before and after metrics.
- Whether anything changed because of it, organisationally. Adoption is a stronger result than completion.
What the result has to be
Someone else's behaviour changed, not just that the thing shipped.
How it lands
Lead with the evaluation, not the demo. Anyone can describe what they built.
What situation this fits
A real one. This is the answer most candidates fumble.
What your version must contain
- It is actually a failure, not a humble brag. Caring too much about quality is a non answer and interviewers hear it constantly.
- You own it without blaming a stakeholder, a deadline or a predecessor.
- The lesson changed a behaviour you can point at. Not 'I learned to communicate better' but 'I now write the ingestion spec with the consuming team before I build, because the first one I wrote alone was missing fields the metrics needed'.
- Ideally it is a mistake you made once and have since prevented structurally.
What the result has to be
A specific practice you now follow, and why.
How it lands
No number. This is the one story that does not need one.
What situation this fits
Any architecture with more than one processing engine or more than one warehouse.
What your version must contain
- The boundary in one sentence, not a feature list. Which workload goes where, and why.
- That the storage layer was shared, or if it was not, that you know that was the weakness.
- Volunteer the cost of the choice before they raise it: more engines means more cost models, more access models, and more places a metric definition can drift.
- The mitigation you actually had, and an honest answer to what you would consolidate if you started again.
What the result has to be
A defensible boundary rather than an accumulation.
How it lands
Never answer 'what would you consolidate' with 'nothing'. A candidate who defends every past decision reads as someone who does not revisit them.
The two minute open, which is not one of the twelve
Four paragraphs, roughly two minutes, and it opens every loop:
- Years, current or most recent scope, and the scale number. The number goes in sentence one. If it is not in the first two sentences it does not land.
- Two or three things you owned, named, each with its outcome. Not a job description.
- The stack in one sentence, framed as a boundary rather than a list. Which workload lives where.
- Everything before that in one sentence. Nobody is hiring you for a role you held ten years ago.
Then stop. The silence after two minutes is the interviewer deciding what to ask, not an invitation to continue.
The availability question
If you are between roles you will be asked why. One flat sentence, then stop. "My position was eliminated in a reorganisation." Do not explain, do not editorialise about the former employer, do not fill the silence. Every additional sentence makes it worse. If they follow up, answer plainly and stop again.
Questions to ask them, which are graded
- "What does the data platform cost, and who watches that number?" Almost nobody asks this and it lands every time, because it signals you treat the bill as an engineering concern.
- "Where does a metric definition live, and what happens when two teams disagree about one?"
- "What is the on-call rotation, and what was the last serious incident?"
- "What would you want the person in this role to have changed in six months?"
Do not ask about growth, culture or work life balance in a technical round. Ask the recruiter, where the question belongs and the answer is more honest.
Write all twelve once, rehearse from the document rather than improvising, and put the number in the last fifteen seconds.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Scale arithmetic, and deriving any number in thirty seconds
A figure you cannot break down is worse than no figure, because the follow up is always how did you get that.
Never state a number you cannot derive. The follow up to every impressive figure is how did you get that, and a candidate who cannot answer has just converted their best line into their worst moment.
The conversions worth having automatic
| From | To | The arithmetic |
|---|---|---|
| X TB per day | monthly volume | X x 30, so 100 TB/day is about 3 PB per month |
| X TB per day | sustained throughput | X x 1000 / 86,400 GB per second, so 100 TB/day is about 1.2 GB/s |
| X TiB scanned | BigQuery on demand cost | X x $6.25 |
| X TiB stored | BigQuery monthly storage | X x 1024 x $0.023, so 1 TiB is about $23.5 per month |
| N events per day at S bytes | daily volume | N x S. Sanity check it against the volume you claimed. |
| Monthly spend at $6.25 per TiB | equivalent slots | spend / ($0.06 x 730) on Enterprise pay as you go |
| Slot count | monthly capacity cost | slots x rate x 730 hours |
| Sliding window size over period | cost multiplier | A 24h window every 5 min puts each element in 288 windows |
A worked example, said out loud
"100 TB a day is about 3 PB a month.
Stored in BigQuery active logical at roughly $0.023 per GiB,
3 PB is about 3,000,000 GiB, so around $70,000 a month,
falling as partitions age past 90 days into long term.
If queries scan 5 percent of a day 200 times a day, that is
1 PB scanned daily, 30 PB a month, which on demand is
30,000 TiB x $6.25, about $187,500. At that steadiness I would
reserve: $187,500 divided by ($0.06 x 730) is roughly 4,300 slots."
That is the whole skill. Nothing in it is hard; it is entirely about having done it before so it comes out fluently rather than haltingly.
The cost unit per platform
INFORMATION_SCHEMA.
Defending a percentage
Decide which measure you meant and use it consistently:
- Wall clock per run. Easiest to defend, weakest signal, because it improves just from more parallelism.
- Slot time, DBUs or credits per run. Strongest signal, because the work itself got cheaper rather than the machine getting bigger.
- Cost per run. The one a hiring manager cares about most.
Then name the mechanism: partitioning and bucketing strategy, broadcast versus shuffle join selection, shuffle partition sizing, skew and small file handling, caching choices, adaptive query execution, executor sizing. A percentage with a named mechanism is credible. A percentage alone is decoration.
The platform numbers to have cold
| Fact | Value |
|---|---|
| BigQuery on demand | $6.25 per TiB, first 1 TiB per month free |
| BigQuery slot hour | Standard $0.04, Enterprise $0.06, Enterprise Plus $0.10 |
| BigQuery active storage | about $0.023 per GiB logical, about $0.040 physical |
| Physical storage breakeven | compression above about 1.74x, higher once time travel counts |
| Storage Write API | $0.025 per GiB gRPC, first 2 TiB free |
| Legacy streaming inserts | $0.05 per GiB, no free tier. Exactly 2x. |
| Pub/Sub throughput | $40 per TiB, first 10 GiB free |
| Snowflake warehouse sizing | X-Small is 1 credit per hour, doubling per size step |
| Snowflake billing granularity | per second, 60 second minimum on resume |
| Snowflake Time Travel | 0 to 90 days on Enterprise, then 7 days Fail-safe |
| Databricks billing | DBU rate by workload and tier, plus the cloud VM cost |
| Delta small file target | OPTIMIZE compacts toward roughly 1 GB files |
A number I cannot derive is a number I do not use. 100 TB a day is about 3 PB a month, about 1.2 GB a second, and one full scan of a day on BigQuery on demand is about $625.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
BigQuery: storage, partitioning, clustering
The phrase to be able to defend is partitioning and clustering chosen against real query patterns rather than defaults. This is where you prove it.
Capacitor is BigQuery's columnar format: each column stored
and compressed independently with per block metadata so the engine
skips blocks without reading them. If you have read ORC or Parquet
footers you have the model. Two consequences to state instantly:
you are billed on columns referenced, computed from declared
types, and LIMIT does not reduce bytes billed. " +
tag("c") + "
Partitioning
| Kind | Declared as | When |
|---|---|---|
| Time unit column | PARTITION BY DATE(event_ts) |
Default. Partition on the event time you filter on. |
| Ingestion time | PARTITION BY _PARTITIONDATE |
No natural time column, or append only logs |
| Integer range |
RANGE_BUCKET(customer_id,
GENERATE_ARRAY(0,100000,1000))
|
Multi tenant isolation, or an id you always filter on |
Granularity hour, day, month or year. Practical ceiling around 10,000 partitions per table, which is why hourly plus multi year retention does not work. Likely
Set require_partition_filter = true on every large
partitioned table. It converts the most expensive mistake on the
platform, an accidental full scan, from a silent bill into a loud
error. If you claim byte scanned cost controls, this is the
mechanism to name.
Clustering, and the rule
Up to four columns,
prefix ordered like a composite index. Clustering by
(country, device, user_id) prunes on
country, prunes well on
country AND device, and prunes not at all on
device alone.
The order rule: lowest cardinality and highest filter
frequency first, left to right. If you cannot say which column
your users filter on first, you cannot choose a clustering key,
and the honest answer is to go read
INFORMATION_SCHEMA.JOBS. That is exactly what
"against real query patterns rather than defaults" means and it
is the sentence to say.
Clustering is maintained automatically in the background at no query cost as new data arrives. Genuine difference from Hive bucketing, where you re-sort yourself.
A dry run on a clustered table reports the upper bound, not
the actual bytes, because block pruning cannot be known before the
query runs. Partition pruning is known statically and is exact. So
the console estimate is pessimistic for clustered tables. If
someone says clustering did nothing because the estimate did not
move, they read the wrong number. Check
total_bytes_billed in
INFORMATION_SCHEMA.JOBS.
Certain
Logical versus physical storage billing
| Mode | Per GiB per month | Ratio | Time travel and fail safe |
|---|---|---|---|
| Active logical | about $0.023 | 1.00 | free |
| Long term logical | about $0.016 | 1.00 | free |
| Active physical | about $0.040 | 1.74 | billed |
| Long term physical | about $0.020 | 1.25 | billed |
Rate card breakeven is 1.74x, not the 2x everyone repeats. The real breakeven is higher because time travel and fail safe are free under logical and billed under physical, so a high churn table carries a fat invisible tail.
SELECT table_name,
SUM(total_logical_bytes)/POW(1024,4) AS logical_tib,
SUM(total_physical_bytes)/POW(1024,4) AS physical_tib,
SAFE_DIVIDE(SUM(total_logical_bytes), SUM(total_physical_bytes)) AS ratio,
SUM(time_travel_physical_bytes)/POW(1024,4) AS tt_tib
FROM `region-us`.INFORMATION_SCHEMA.TABLE_STORAGE
WHERE table_schema = 'your_dataset'
GROUP BY table_name ORDER BY logical_tib DESC
Nested and repeated fields
ARRAY and STRUCT let a one to many relationship live inside the parent row. Nesting wins when the child is always read with the parent, because it removes a shuffle entirely: the join happened at write time. It loses when the child is queried independently, and updating one element rewrites the parent row. The phrase to use is "where the access pattern justified them", which is exactly the right framing.
Partition on the time column people filter on, cluster on the next columns by filter frequency in prefix order, turn on require_partition_filter, and check whether physical billing wins by querying TABLE_STORAGE against the 1.74 breakeven.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
BigQuery: slots, editions, cost engineering
Slot reservations versus on demand, and per project quotas, are the two claims interviewers probe hardest. This is the module that has to hold.
The two models
| On demand | Capacity (Editions) | |
|---|---|---|
| You pay for | Bytes scanned | Slot hours made available |
| Rate | $6.25 per TiB, first 1 TiB per month free | $0.04 to $0.10 per slot hour |
| Concurrency | Up to 2,000 shared slots per project | What you provisioned |
| A bad query costs | Money, immediately | Time, everyone queues |
| Best for | Spiky, exploratory, low volume | Steady scheduled pipelines |
| Standard | Enterprise | Enterprise Plus | |
|---|---|---|---|
| Pay as you go slot hour | $0.04 | $0.06 | $0.10 |
| BigQuery CUD 3 year | $0.032 | $0.048 | $0.08 |
| Resource CUD 3 year | n/a | $0.036 | $0.06 |
| BigQuery ML | no | yes | yes |
| CMEK | no | yes | yes |
| Column and row level security | no | yes | yes |
| Idle slot sharing | no | yes | yes |
Enterprise on a three year Resource CUD is $0.036, which is below Standard pay as you go at $0.040. The intuition that Standard is the budget tier is wrong. Also note two separate commitment families, BigQuery CUD and Resource CUD, at different rates, which almost nobody writing about this distinguishes.
CertainIf you claim CMEK, authorized views with row and column level security, or policy tags, note that column and row level security and CMEK both require Enterprise edition. If asked which edition you ran, Enterprise or above is the only consistent answer, because Standard cannot do any of it.
Sizing a reservation, which is what you claim you did
-- average slots consumed per hour, last 30 days
SELECT TIMESTAMP_TRUNC(creation_time, HOUR) hr,
SUM(total_slot_ms)/(1000*60*60) AS avg_slots
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type='QUERY' AND statement_type != 'SCRIPT'
GROUP BY hr ORDER BY hr;
Baseline at the p50, autoscale max near the p95. Baseline at peak means paying for peak all night. Baseline at zero means every morning starts cold. Commitments are 50 slot minimum in 50 slot increments, one or three year, regional and shareable org wide. The 100 slot minimum you may have read is legacy flat rate, closed to new purchase in July 2023. Certain
The breakeven, out loud
100 slots Enterprise PAYG = 100 x 0.06 x 730 = $4,380 / month
Same money on demand = $4,380 / $6.25 = 700 TiB / month
On a 3yr Resource CUD at $0.036, the line moves to ~420 TiB.
Then the answer that actually works in practice: run both. Predictable pipelines on the reservation, spiky ad hoc on demand. Projects without a reservation assignment fall back to on demand automatically, so a hybrid needs no extra machinery.
Autoscaling and the billing granularity that changed
Billing is per second with a one minute minimum by default, and you can now opt into BigQuery fluid scaling at the reservation level for per second billing with no minimum. Certain That matters because the classic trap was a four second burst billed as sixty. Autoscaling bills slots allocated, not slots consumed, which makes many short concurrent queries (exactly a BI workload) the worst possible shape.
The eight leaks, and the query that finds them
| Leak | Signature | Fix |
|---|---|---|
| Full scans from unusable partition filters | A few jobs with enormous bytes |
require_partition_filter, unwrap functions on
the partition column
|
SELECT * in scheduled work |
High bytes, low rows returned | Name columns. Often 10x to 50x on a wide table. |
| Nightly full rescan by a scheduled query | Same shape, same bytes, every night | Incremental predicate, or a materialized view |
| Dashboards missing the result cache | Thousands of near identical queries | Materialized view or BI Engine. Cache will never help. |
| Reservation sized for peak | Utilisation under 30 percent most hours | Baseline p50, autoscale p95, consider fluid scaling |
| Legacy streaming inserts | $0.05 per GiB with no free tier | Storage Write API gRPC: $0.025 with 2 TiB free |
| Time travel under physical billing | Physical bytes far above expectation | Shorten the window on high churn tables, or stay logical |
| Cross region reads and egress | A network line nobody owns | Colocate bucket, dataset and compute |
SELECT DATE(creation_time) d, user_email,
COUNT(*) jobs,
SUM(total_bytes_billed)/POW(1024,4) AS tib,
SUM(total_bytes_billed)/POW(1024,4)*6.25 AS est_usd,
SUM(total_slot_ms)/1000/3600 AS slot_hours
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type='QUERY' AND state='DONE'
GROUP BY d, user_email ORDER BY tib DESC
Group by user_email first.
A service account at the top is an automated leak and worth ten
times a human at the top.
Budget alerts notify. Only a custom quota on query bytes billed
and maximum_bytes_billed actually stop anything.
Google Cloud has no global hard spending cap for pay as you go
services. If asked how you prevent a runaway bill and you only say
budget alerts, you answered the wrong question. If you claim per
project cost controls, this is the mechanism to name.
I would leave analysts on demand and put scheduled pipelines on an Enterprise reservation, baseline at the p50 of total_slot_ms per hour and max at the p95, then set a per project bytes billed quota, because alerts notify and quotas stop.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Dataflow and Apache Beam
Claiming production depth here means claiming windowing, watermarks, triggers, side inputs, stateful timers and exactly once. All of it is probeable.
Current state, so you do not quote something dead
- Runner v2 is the recommended runner, required for multi language pipelines, custom containers and GPUs. Certain
- Dataflow Prime is GA and Google managed templates default to it. Adds vertical autoscaling, right fitting via Beam resource hints, dynamic thread scaling. Certain
- Streaming Engine is default for Python and Go streaming: state and shuffle move off worker VMs into the service backend. Certain
- Exactly once is the default streaming mode. At least once is opt in and forces resource based billing. Certain
- Dataflow SQL is dead, shut down January 2025. Do not mention it. Certain
The four knobs, and the one that produces the bug
| Knob | Controls | Get it wrong and |
|---|---|---|
| Window | Which bucket an element lands in | Fixed, sliding, session, global. Sliding multiplies cost by size over period. |
| Trigger | When a pane is emitted | Emit too often (cost, downstream churn) or too rarely (stale dashboards) |
| Allowed lateness | How long window state is kept after the watermark passes | Too short: late data silently dropped. Too long: state grows to the 60 GB per key wall. |
| Accumulation mode | What a later pane contains | This is the one. See below. |
ACCUMULATING means each pane contains the full window result so far, so the sink must overwrite by window key. DISCARDING means each pane contains only the delta, so the sink must sum. Pairing ACCUMULATING with an appending sink is the classic double count; pairing DISCARDING with an overwriting sink silently loses data. The bug is never in the trigger, it is in the mismatch between accumulation mode and sink semantics.
Operating it
| Metric | Read it as |
|---|---|
| Data freshness | Now minus the watermark. The number you alert on. Rising freshness with flat throughput means a stalled watermark. |
| System latency | How long the oldest in flight element has been in the pipeline. Rising means the pipeline is the bottleneck. |
| Backlog bytes and seconds | Unconsumed source. With freshness it tells you whether source or pipeline is behind. |
Drain stops reading, closes windows and lets in flight data finish, so use it for a code change. Cancel stops immediately and loses in flight data, so use it only for a runaway. Update does in place replacement with a compatibility check on the fused graph.
Flex Templates package a pipeline as a container so it is parameterized rather than copy pasted. That phrasing is the right answer: without them, every variation of a pipeline becomes a separate codebase, and you end up with fourteen nearly identical Dataflow jobs that drift. Classic templates staged a JSON graph; Flex Templates build the graph at launch time from parameters, which is why they support more dynamic pipelines.
The correctness questions in a Beam pipeline are always the same three: which clock the windows use, how long I keep state for late arrivals, and whether my accumulation mode matches whether the sink overwrites or appends.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Pub/Sub, Kafka and the ingestion path
Dead letter topics with automated replay, ordering keys, an Avro schema registry and seek based recovery. Each is a specific question.
Delivery semantics, stated exactly
| Guarantee | Where | Caveat |
|---|---|---|
| At least once | Default on every subscription type | The honest baseline. Design for it. |
| Ordering | Opt in per subscription plus an ordering key per message | Within a region only, and only for messages sharing a key |
| Exactly once | Pull subscriptions, single region | Not on push. Not on export subscriptions. |
A Pub/Sub BigQuery subscription is at least once only. There is no exactly once option on export subscriptions. The Dataflow Pub/Sub to BigQuery path gives exactly once by deduplicating in the pipeline. So 'can I skip Dataflow and write straight to BigQuery' has a precise answer: yes, and it costs the exactly once guarantee, so you need a downstream dedup or an idempotent target. Certain
Pricing trap: throughput is $40 per TiB with the first 10 GiB free, but export subscriptions are $50 per TiB with no free tier. Removing Dataflow to save money can raise the bill. Certain
The four mechanisms worth claiming
| Claim | What to say |
|---|---|
| Dead letter topic with automated replay |
Set maxDeliveryAttempts (5 to 100) and a DLT on
every subscription that does real work. Without one a poison
message redelivers until retention expires while the backlog
and bill grow.
And put a subscription on the dead letter topic,
otherwise you built a place for messages to die quietly.
Your Cloud Functions recovery mechanism auto replaying after
correction is the mature version of this.
|
| Ordering keys | Messages sharing a key in the same region are delivered in publish order. The cost is that delivery per key is serialised, so a hot key becomes a single threaded bottleneck. Only enable ordering if the consumer genuinely cannot reconstruct order from a sequence number in the payload. Nine times out of ten it can. |
| Avro schema registry with backward compatible evolution | A schema attached to the topic is the only place a bad producer gets caught before it reaches storage. Backward compatible means new consumers can read old data: add optional fields, never remove or retype. That is the mechanism that turns an upstream producer change from an outage into a rejected message. |
| Ack deadline tuning and seek | Ack deadline too short means redelivery of messages still being processed, which looks like duplicates. Too long means slow recovery from a crashed consumer. Seek rewinds a subscription to a timestamp or snapshot for replay, bounded by the retention window, which maxes at 31 days. |
Pub/Sub versus Kafka, which you ran both of
Dead, do not mention: Pub/Sub Lite, shut down 18 March 2026. Migration path was Pub/Sub or Google Cloud Managed Service for Apache Kafka.
CertainPub/Sub gives at least once by default, exactly once only on pull within a region, and never on the BigQuery subscription. So the design question is always where the deduplication lives, not whether I need it.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Dataproc, Composer and the orchestration layer
Ephemeral job scoped clusters, Airflow DAGs, Workflows and Eventarc. Plus the idempotency answer that every orchestration question is really about.
Dataproc and Dataproc Serverless are now Managed Service for
Apache Spark, unified into one product with cluster and serverless modes, as
of April 2026.
Cloud Composer is now Managed Service for Apache Airflow,
with Composer 3 as Managed Airflow Gen 3. APIs, client libraries
and gcloud dataproc and
gcloud composer commands are unchanged.
Live deadline: Composer 1 and 2.0.x reach end of life 15
September 2026.
Raising that unprompted signals you read release notes rather than
courses. Certain
Dataproc, and the claim to be able to defend
The right answer is ephemeral job scoped clusters with autoscaling policies, initialization actions and preemptible workers, keeping long lived clusters only where interactive exploration justifies them. The reason is billing: you pay for cluster uptime, not for the cluster's existence as a concept. The blueprint asks the persistent versus ephemeral question explicitly.
| Term | What it does | The follow up |
|---|---|---|
| Initialization actions | Scripts run on every node at cluster creation | What did yours install? Have a real answer: connectors, monitoring agents, python deps. |
| Preemptible or Spot workers | Cheap capacity that can vanish with short notice | Only secondary workers, never the primary. Fine for backfills, wrong for SLA bound paths. Say that distinction explicitly. |
| Autoscaling policy | Scales secondary workers on YARN pending memory | Scale up fast, scale down slow, and set a graceful decommission timeout so a shuffle is not killed mid stage. |
| Serverless batches | No cluster at all, per second on DCUs | Startup is 40 to 50 seconds, which rules out sub minute response. |
Composer, and why the Airflow bridge is your strongest
Managed Airflow is Airflow. Same software. Your DAGs, operators, hooks, sensors, XComs, pools and SLA callbacks port with no translation. What differs is the environment model, the fact that generation upgrades are side by side rather than in place, and that Google auto upgrades infrastructure components but not your Airflow version.
The classic Composer failure that produces no failed task: top level code in a DAG file. Anything outside an operator runs on every scheduler parse, every few seconds, for every DAG. It will rate limit you out of an API at 3am with nothing in the task logs to point at, or make the scheduler fall behind and delay everything. Everything expensive goes inside an operator or a callable.
The four orchestrators and their boundaries
| Tool | Right when | Wrong when |
|---|---|---|
| Managed Airflow | Real dependency graphs, backfills, a team that writes DAGs | One job on a timer. The environment carries a floor cost whether a DAG runs or not. |
| Cloud Workflows | Serverless step sequencing in YAML, calls APIs and Cloud Run, pay per step | Complex branching pipelines with backfill semantics |
| Eventarc | Trigger on arrival rather than on a clock | Anything needing a dependency graph |
| Cloud Scheduler | Cron firing an HTTP call or a Pub/Sub message | Anything with a dependency |
If a resume says Cloud Workflows and Eventarc triggering Cloud Functions and Dataflow on arrival rather than on a clock. That sentence is the senior answer: not everything belongs in Airflow, and knowing why is the point.
Idempotency, which is what the question is actually about
-
Partition replace, not append. Write to
table$20260821withWRITE_TRUNCATE, so a re-run replaces that partition atomically. Default answer. - MERGE on a natural key when the grain is per entity rather than per day.
-
Deterministic output paths.
gs://.../dt=2026-08-21/, neverrun_id=abc123. - A watermark table the pipeline reads and writes, so a restart resumes rather than restarts.
The checklist phrase is restartability, partition level recovery, idempotent writes, dependency aware scheduling, retries with backoff, clear SLA semantics and repeatable automated backfills. Those seven are the checklist; be able to give a mechanism for each.
Ephemeral job scoped clusters because you pay for uptime, preemptible only on secondary workers and only for backfills, and everything expensive inside an operator so the scheduler parse stays cheap.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Delta Lake and the medallion
ACID, MERGE, Change Data Feed, OPTIMIZE, Z-ORDER, liquid clustering and VACUUM. Every one is a probe target.
How it actually works, in one paragraph
A Delta table is
Parquet data files plus a _delta_log directory of
ordered JSON commits, checkpointed to Parquet every ten commits. Each commit lists
files added and removed. A reader resolves the current version by
replaying the log, so "delete a row" means write a new file without
it and commit a remove. That single design gives you ACID, time
travel, and the fact that a delete is a rewrite, not an edit,
which is the source of most Delta cost questions.
The maintenance trio, and what each is for
| Command | Does | The follow up you will get |
|---|---|---|
OPTIMIZE |
Compacts small files toward roughly 1 GB targets | Why do small files matter? Metadata and task overhead per file, and a shuffle that spends longer opening files than reading them. |
Z-ORDER BY |
Multi dimensional clustering by interleaving bits of column values, colocating related data | How many columns? Effectiveness degrades past three or four, because interleaving dilutes each dimension. |
| Liquid clustering | Replaces both partitioning and Z-ORDER. Incremental, no full rewrite, clustering keys changeable without rewriting the table. | The key question. See below. |
VACUUM |
Physically removes files no longer referenced, default retention 7 days | What does it break? Time travel beyond the retention window, and any concurrent long reader. Do not lower it below 7 days without understanding both. |
Liquid clustering versus Z-ORDER, the answer. Z-ORDER requires a full rewrite of the affected data every time and the columns are effectively fixed once queries depend on them. Liquid clustering is incremental, avoids the rewrite, handles skew and changing query patterns, and lets you change the clustering keys without rewriting history. It also replaces Hive style partitioning rather than sitting alongside it. Use liquid clustering on new tables; the reason to still know Z-ORDER is that existing tables are full of it. Likely
MERGE, which is the operation everything else leans on
MERGE INTO gold.orders t
USING (
SELECT * EXCEPT(rn) FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY order_id ORDER BY source_seq DESC) rn
FROM silver.orders_delta)
WHERE rn = 1 -- dedupe the source FIRST, always
) s
ON t.order_id = s.order_id
WHEN MATCHED AND s.op = 'D' THEN DELETE
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
Two things to say unprompted. One: the source must be deduplicated, because MERGE errors when more than one source row matches a target row and every CDC batch has that. Two: MERGE rewrites every file containing a matched row, so a MERGE that touches every partition rewrites the table. That is why the join predicate should include a partition or clustering predicate where possible, so the rewrite is bounded.
Schema enforcement versus evolution
- Enforcement is the default: a write with a mismatched schema fails. That is a feature, and it is why Delta is safer than raw Parquet.
-
Evolution is opt in via
mergeSchemaorautoMerge. Adds columns, and can widen types. It does not silently reconcile an incompatible retype. - The change no check catches is a column that keeps its type and changes meaning. Only a distribution check finds that.
Change Data Feed
delta.enableChangeDataFeed = true makes the table emit
row level changes with _change_type of insert,
update_preimage, update_postimage or delete, readable with
readChangeFeed. The use that matters is
downstream propagation: it turns a gold table into a source
for the next hop without a full rescan. Cost: extra data written per
commit, so enable it where something actually consumes it.
The medallion, and the honest version
| Layer | Contains | The discipline |
|---|---|---|
| Bronze | Raw as landed, plus ingest metadata. Auto Loader writes here. | Never fails a load. Append only. This is your replay source. |
| Silver | Cleaned, typed, deduplicated, conformed. DLT and Structured Streaming write here. | This is the contract. A rename upstream changes one place here, not fifty queries. |
| Gold | Business level aggregates and dimensional models, MERGE based upserts | Serves consumers. The property that matters: late corrections land in place rather than through a rebuild. |
Be ready to say the layering is a principle rather than a ceremony. The principle worth keeping is that consumers never read bronze. The ceremony worth dropping is insisting on exactly three layers for every table; some go raw to gold in one hop and that is fine. Saying that shows you have run it rather than read about it.
Delta is Parquet plus an ordered transaction log, so a delete is a rewrite rather than an edit. That single fact explains OPTIMIZE, VACUUM, MERGE cost and time travel all at once.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Unity Catalog and Databricks governance
Catalog and schema grants, row filters, column masks, column level lineage and system tables. All five get probed.
The three level namespace, which is the whole model
catalog.schema.table, with the
metastore above the catalog and shared across workspaces in a
region. That last part is the point: before Unity Catalog, permissions
were per workspace and drifted. Unity Catalog puts identity and
grants above the workspace so one policy covers everything.
| Object | What it is for |
|---|---|
| Metastore | Top level container, one per region, attached to many workspaces |
| Catalog | Usually an environment or a domain: prod, dev, or finance, marketing |
| Schema | Grouping inside a catalog. Grants here are how most estates are actually run. |
| External location and storage credential | Bind a cloud storage path to an identity, so table access is governed rather than bucket IAM. This is the mechanism behind governing a lake through the catalog rather than through bucket level IAM. |
| Volumes | Governed non tabular files, for the things that are not tables |
Fine grained access
| Control | Mechanism | Watch for |
|---|---|---|
| Row filter | A UDF returning boolean, attached to a table, evaluated per row against the caller | Row counts and aggregates differ per user, which breaks 'the dashboard shows a different number' expectations |
| Column mask | A UDF applied to a column, returning masked or real value by caller identity | Usually what people actually want, versus denying access outright |
| Dynamic views |
current_user() and
is_account_group_member() inside a view
|
The older pattern; still correct when the logic is complex |
| Column level lineage | Captured automatically from queries and jobs | Only for operations that go through Unity Catalog. Something reading the files directly is invisible to it. |
System tables, which almost nobody mentions
system.access.audit, system.billing.usage,
system.query.history, system.lakeflow and
the lineage tables.
This is the Databricks equivalent of
INFORMATION_SCHEMA.JOBS and it is where cost
attribution actually comes from.
If you are asked how you tracked Databricks spend per team, the
answer is system.billing.usage joined to cluster tags,
and cluster policies are what force the tags to exist. Cluster
policies enforcing tags, runtime version and maximum spend are what
make this work, so the two connect directly.
SELECT usage_metadata.cluster_id,
custom_tags.team,
SUM(usage_quantity) AS dbus
FROM system.billing.usage
WHERE usage_date >= current_date() - INTERVAL 30 DAYS
GROUP BY 1, 2 ORDER BY dbus DESC;
Delta Sharing
An open protocol for sharing live Delta tables across organisations without copying: the provider shares, the recipient reads current data through a short lived credential. Works with non Databricks recipients too. Compare it to Snowflake Secure Data Sharing, which does the same job inside Snowflake's own boundary and is tighter within it. That comparison is a good answer because it shows you know both.
Unity Catalog put grants above the workspace, which is the actual change. Before it, permissions were per workspace and drifted; after it, one metastore covers every workspace in the region and lineage is captured automatically.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Auto Loader, DLT, Workflows, Photon and cost
The ingestion and compute layer, plus the cost conversation behind the phrase 'Photon evaluated against cost per job'.
Auto Loader
cloudFiles, a Structured Streaming source that
incrementally discovers new files in cloud storage. Two discovery
modes and the difference is a real question:
| Mode | How | When |
|---|---|---|
| Directory listing | Lists the path, tracks what it has seen in RocksDB checkpoint state | Small to moderate file counts. Simple, no cloud setup. |
| File notification | Subscribes to cloud storage event notifications into a queue | Large directories. Listing millions of files gets slow and expensive; notifications do not care how many files already exist. |
Schema inference plus cloudFiles.schemaLocation gives
evolution, and a rescued data column captures fields that did
not fit the schema instead of failing or dropping them. That rescued
column is the detail that shows hands on use: it is how you avoid
choosing between a failed load and silent data loss.
Delta Live Tables
Declarative pipelines: you define the tables and the dependencies are inferred from the queries. What it gives you beyond a notebook:
-
Expectations, which are the quality gates:
EXPECT(log and pass),EXPECT ... ON VIOLATION DROP ROW, andEXPECT ... ON VIOLATION FAIL UPDATE. That three way choice is the same tiering argument as blocking versus alerting checks, and it is how quality becomes a gate rather than a dashboard. - Streaming tables versus materialized views. A streaming table processes each input row once and is append oriented; a materialized view recomputes a result and can handle arbitrary changes upstream. Choosing wrong is a correctness bug, not a performance one.
- APPLY CHANGES INTO, which handles CDC ordering and out of order records for you, including SCD Type 1 and Type 2.
- Automatic dependency resolution, retries, and lineage in the pipeline graph.
The honest limitation to volunteer: DLT is opinionated. It manages the tables it creates, which means you cede some control over the write path and maintenance. For a highly custom pipeline a plain Structured Streaming job with your own checkpointing is more flexible. Saying that is better than describing DLT as strictly better.
Cluster types, which decides half the bill
| Type | For | Cost note |
|---|---|---|
| Job cluster | Created for a job, terminated after | Cheaper DBU rate than all purpose. The default for anything scheduled. |
| All purpose cluster | Interactive notebooks, shared | Higher DBU rate, and it idles. Auto termination is not optional. |
| SQL warehouse | Databricks SQL, BI tools | Serverless, classic or pro. Serverless starts in seconds and idles down fast. |
| Instance pools | Pre warmed VMs shared across jobs | Cuts startup time; you pay cloud VM cost for idle pool instances but not DBUs. |
Databricks bills you twice. DBUs at a rate that varies by workload type and tier, plus the underlying cloud VM cost. Candidates routinely quote only the DBU rate. Naming both, and naming that a job cluster carries a lower DBU rate than an all purpose cluster, is a two sentence answer that separates you immediately.
Photon, and what 'evaluated against cost per job' should mean
Photon is a vectorized C++ execution engine. It carries a DBU multiplier, so it must earn its premium per workload. Directionally: Likely
- Helps most on scan heavy SQL, aggregations, joins, and Delta MERGE and writes.
- Helps least on Python UDF heavy work, RDD operations, and anything not expressible in the vectorized operators, because it falls back.
- The measurement: run the same job with and without, and compare DBU consumed times rate, plus VM hours, not wall clock. A job that finishes twice as fast at more than twice the rate has cost you money.
That last sentence is exactly what "evaluated against cost per job" means, and it is the answer to give.
Asset Bundles
Databricks Asset Bundles define jobs, pipelines, clusters and permissions as YAML in the repo, deployed through CI. The point is that they go into the same CI/CD path as the Dataflow and Dataproc jobs: one deployment discipline across three engines rather than three ways to ship.
Job clusters for anything scheduled because the DBU rate is lower and they terminate, all purpose only for interactive, and Photon evaluated per workload on DBUs plus VM hours rather than on wall clock.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Snowflake: micro-partitions and pruning
There is no partitioning statement. Understanding why is the whole first half of Snowflake.
Micro-partitions
Snowflake automatically divides a table into micro-partitions of roughly 50 to 500 MB of uncompressed data, stored columnar and compressed, immutable once written. Certain For each micro-partition it keeps metadata: the range of values per column, the number of distinct values, and null counts. A query with a predicate reads that metadata and skips any micro-partition whose range cannot satisfy it. That is pruning, and it is the entire performance story.
The consequence people miss: micro-partitions are immutable. An UPDATE does not edit a micro-partition; it writes new ones and marks the old as no longer current. That single fact explains Time Travel (the old ones are still there), zero copy cloning (a clone is a metadata pointer to the same micro-partitions), and why heavy DML on a large table costs more than you expect.
Natural clustering and clustering keys
Data arriving in a natural order is already well clustered on that
column, which is why
most tables need no clustering key at all. A clustering key
is worth defining when: the table is large (multi terabyte), queries
filter or join on a column that is not the natural load order, and
SYSTEM$CLUSTERING_INFORMATION shows significant
overlap.
SELECT SYSTEM$CLUSTERING_INFORMATION('sales.fact_orders', '(order_date, region)');
-- read average_overlaps and average_depth.
-- high depth means many micro-partitions share a value range, so pruning is poor.
Automatic Clustering is a background service you pay for in credits, and it runs continuously as data changes. On a table with heavy DML that reclustering cost can exceed the query saving. Defining a clustering key is a cost decision, not just a performance one, and saying so is the senior answer.
Table types, which controls your storage bill
| Type | Time Travel | Fail-safe | Use for |
|---|---|---|---|
| Permanent | 0 to 90 days (Enterprise), 0 to 1 on Standard | 7 days | Anything that matters |
| Transient | 0 to 1 day | none | Staging, intermediate, rebuildable tables |
| Temporary | 0 to 1 day | none | Session scoped only |
The lever is permanent, transient and temporary table classification controlling Time Travel and Fail-safe storage cost, and here is the number behind it: a permanent table carries up to 90 days of Time Travel plus 7 days of Fail-safe you cannot turn off or query. On a high churn staging table that tail can be several times the table itself. Making staging tables transient is one of the highest value single changes in a Snowflake estate.
Reading the Query Profile
Query Profile analysis isolating full table scans, spill to disk and exploding joins is a common claim. Be able to name what you look at:
| Signal in the profile | Means |
|---|---|
| Partitions scanned versus partitions total | The pruning ratio. Scanning all of them means the predicate did not prune, which is the first thing to check. |
| Bytes spilled to local storage | The warehouse ran out of memory for a sort, join or aggregate. Fix the query or size up. |
| Bytes spilled to remote storage | Much worse. Spilling to remote is a cliff, not a slope. |
| Exploding join | Rows out far exceeding rows in on a join node. Usually a missing predicate or a many to many relationship nobody declared. |
| Most expensive node percentage | Where to look first. If it is a TableScan, it is pruning; if it is a Join, it is cardinality; if it is Sort or Aggregate, it is spill. |
OPTIMIZE to compact and
file level statistics for skipping.
Micro-partitions are immutable, which is why an update is a rewrite, why time travel is nearly free to implement, and why zero copy cloning is instant. One fact explains four features.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Snowflake: warehouses, credits and tuning
Multi-cluster sizing, auto-suspend and resource monitors. The cost model is the differentiator here, not the SQL.
Warehouse sizing, and the counterintuitive part
| Size | Credits per hour | Note |
|---|---|---|
| X-Small | 1 | Doubling continues at every step |
| Small | 2 | |
| Medium | 4 | |
| Large | 8 | |
| X-Large | 16 | |
| 2X-Large | 32 | and so on |
Because size doubles the credit rate and roughly doubles the compute, a query that scales linearly costs the same on any size. A query that takes 8 minutes on Small costs 2 credits per hour x 8/60 = 0.27 credits. On Medium at 4 minutes it is 4 x 4/60 = 0.27 credits. Same cost, half the wall clock.
So the rule is: size up until the query stops scaling. It stops scaling when the work cannot be parallelised further, or when it was never spilling in the first place. Sizing up a query that was not memory constrained just wastes credits. That is the answer, and almost nobody gives the arithmetic.
The two axes people conflate
| Axis | Fixes | Control |
|---|---|---|
| Size (X-Small to 6X-Large) | One slow query. Memory, spill, single query throughput. | WAREHOUSE_SIZE |
| Multi-cluster (min and max clusters) | Many concurrent queries queueing. It does not make any individual query faster. |
MIN_CLUSTER_COUNT,
MAX_CLUSTER_COUNT, SCALING_POLICY
|
Scaling policy STANDARD starts another cluster as soon as a query queues, favouring latency. ECONOMY waits until there is enough backlog to keep a new cluster busy for several minutes, favouring cost. Naming that difference is a good, precise answer.
Auto-suspend, auto-resume and the minimum
Billing is
per second with a 60 second minimum each time a warehouse
resumes. Certain That produces the classic
mistake: AUTO_SUSPEND = 60 on a warehouse queried every
two minutes means you pay the 60 second minimum on every resume,
repeatedly. For bursty interactive use a longer auto-suspend
is often cheaper than a shorter one, which is the opposite of the
instinct.
The other side: warehouses hold a local disk cache of table data between queries. Suspending clears it, so the next query re-reads from remote storage. A very short auto-suspend on a dashboard warehouse destroys the cache and makes every query cold. Weigh idle credits against cache warmth, and say that you would look at the query arrival pattern rather than pick a number.
Resource monitors, which are the real control
CREATE RESOURCE MONITOR rm_bi WITH
CREDIT_QUOTA = 500
FREQUENCY = MONTHLY
START_TIMESTAMP = IMMEDIATELY
TRIGGERS
ON 75 PERCENT DO NOTIFY
ON 90 PERCENT DO NOTIFY
ON 100 PERCENT DO SUSPEND
ON 110 PERCENT DO SUSPEND_IMMEDIATE;
SUSPEND lets running queries finish; SUSPEND_IMMEDIATE kills them. Monitors attach to an account or to specific warehouses. This is Snowflake's answer to the same question as BigQuery custom quotas, and the same distinction applies: NOTIFY informs, SUSPEND stops.
Caching, three layers
| Layer | What | Invalidated by |
|---|---|---|
| Result cache | Identical query text, same role, unchanged underlying data. Free, 24 hours. |
Any change to the underlying data. Also broken by non
deterministic functions like
CURRENT_TIMESTAMP().
|
| Local disk cache | Table data cached on warehouse SSD between queries | Suspending the warehouse |
| Metadata cache |
Micro-partition statistics. Serves COUNT(*),
MIN, MAX with no warehouse at all.
|
DML on the table |
The metadata cache is a nice detail to drop:
SELECT COUNT(*) on a Snowflake table can return with
the warehouse suspended, because it is answered from metadata.
Search Optimization Service
A background maintenance service building a search access path for selective point lookups on high cardinality columns, plus substring and VARIANT search. It costs both storage and compute to maintain. The boundary: clustering suits range scans, SOS suits needle in a haystack equality lookups. If a query filters on a high cardinality id and returns a handful of rows out of billions, SOS. If it filters a date range, clustering.
Because each size step doubles both the credits and the compute, a linearly scaling query costs the same on any size. So I size up until it stops scaling, and use multi-cluster for concurrency rather than for speed.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Snowpipe, Streams, Tasks and Dynamic Tables
Four features that overlap. Knowing where the boundaries are is the question.
Snowpipe
Continuous micro batch loading from an external stage, triggered by cloud storage event notifications (auto ingest) or by a REST call. For Snowpipe auto-ingest from GCS external stages the mechanism is a GCS notification into Pub/Sub, and the pipe consuming it.
COPY INTO |
Snowpipe | |
|---|---|---|
| Compute | Your virtual warehouse | Snowflake managed, serverless, billed per compute second plus a per file overhead |
| Trigger | You run it | Event notification or REST |
| Latency | Whenever you run it | Typically about a minute |
| Best for | Large bulk loads, backfills | A steady stream of small to medium files |
Snowpipe charges a per file overhead on top of compute. Thousands of tiny files is therefore expensive twice: once in overhead and once because tiny files load inefficiently. The guidance is files roughly in the 100 to 250 MB compressed range. If your source produces tiny files, compact before loading, not after.
And note Snowpipe Streaming is a different thing: a rowset API writing rows directly with lower latency and no file staging at all, which is the closer analogue to BigQuery's Storage Write API.
Streams, which are the part most people describe wrongly
A Stream is not a copy of the changes. It is an offset: a bookmark on the table plus the metadata to compute what changed since that bookmark. Querying it shows the change set; consuming it inside a DML statement advances the offset. That is why reading a stream twice without a DML gives the same rows, and why two consumers need two streams.
| Stream type | Shows | Use for |
|---|---|---|
| Standard | Inserts, updates and deletes, with updates as a delete plus insert pair | Full CDC downstream |
| Append-only | Inserts only. Cheaper. | Append oriented sources where updates cannot happen |
| Insert-only (external tables) | New files only | External table change tracking |
Columns: METADATA$ACTION,
METADATA$ISUPDATE, METADATA$ROW_ID. The
ISUPDATE flag is how you tell a genuine delete from the
delete half of an update pair, and getting that wrong is a real
correctness bug.
Staleness is the operational risk. A stream becomes stale
if it is not consumed within the source table's data retention
period, and a stale stream cannot be read: you have to recreate it
and reconcile the gap. SYSTEM$STREAM_HAS_DATA is how
a task avoids running for nothing, and monitoring stream staleness
is the thing that stops a quiet CDC outage.
Tasks
Scheduled or triggered SQL, arranged in a DAG through
AFTER dependencies, with a root task and children. Two
compute modes: a user managed warehouse, or
serverless where Snowflake sizes it and bills per second. The
idiomatic CDC pattern:
CREATE TASK t_merge_orders
WAREHOUSE = wh_etl
SCHEDULE = '2 MINUTE'
WHEN SYSTEM$STREAM_HAS_DATA('str_orders') -- do not burn credits on nothing
AS
MERGE INTO gold.orders t USING str_orders s ON t.id = s.id
WHEN MATCHED AND s.METADATA$ACTION='DELETE'
AND s.METADATA$ISUPDATE=FALSE THEN DELETE
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED AND s.METADATA$ACTION='INSERT' THEN INSERT ...;
Dynamic Tables, and the boundary question
Declarative: you state the query and a TARGET_LAG, and
Snowflake works out the refresh, incrementally where it can and by
full refresh where it cannot. It replaces the Stream plus Task plus
MERGE pattern for most cases.
| Dynamic Table | Materialized View | Stream plus Task | |
|---|---|---|---|
| You declare | Query plus target lag | Query only | The whole procedure |
| Query complexity | Joins, aggregates, unions, most SQL | Single table only, limited aggregates | Anything |
| Refresh | Automatic, incremental where possible | Automatic, maintained by Snowflake | You schedule it |
| Compute | Warehouse or serverless | Serverless background, always on | Your warehouse |
| Chainable | Yes, DAG of dynamic tables | No | Yes |
| Use when | Declarative pipelines, most new work | Simple single table pre aggregation | Custom logic Dynamic Tables cannot express |
The one line answer: a materialized view is a single table pre aggregation Snowflake maintains for you. A Dynamic Table is a whole declarative pipeline stage that can join, chain and target a freshness lag. A Stream plus Task is what you use when you need imperative control the declarative options cannot express. Reach for Dynamic Tables first on new work and drop to Streams and Tasks only when you must.
A Stream is an offset, not a copy of changes, and consuming it in a DML advances it. That is why two consumers need two streams, and why a stream going stale is a quiet CDC outage.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Snowflake governance, cloning and sharing
RBAC hierarchy, masking, row access policies, Time Travel, zero-copy cloning and Secure Data Sharing. All six get asked.
RBAC, and the thing that makes it different
Privileges are granted to roles, and roles are granted to other
roles, forming a hierarchy where a parent role inherits everything its
children have. Users get roles, never privileges directly. The
system roles from the top: ORGADMIN,
ACCOUNTADMIN, then SYSADMIN and
SECURITYADMIN, then USERADMIN, then
PUBLIC.
The convention that shows you have run it: build
access roles that hold object privileges (read and write
per schema) and functional roles that map to jobs
(analyst, engineer, finance). Grant access roles to functional
roles, and functional roles to users. Then grant every custom
role up to SYSADMIN so the hierarchy stays
navigable. Without that last step you get orphan roles that
SYSADMIN cannot manage, which is the most common
real world Snowflake RBAC mess.
And: every object has an owner role, and ownership
carries the ability to grant.
MANAGED ACCESS schemas centralise that so only the
schema owner can grant on objects inside it, which is what you
want in a governed estate.
Masking and row access
CREATE MASKING POLICY mask_email AS (val STRING) RETURNS STRING ->
CASE WHEN CURRENT_ROLE() IN ('PII_READER') THEN val
ELSE REGEXP_REPLACE(val, '.+@', '*****@') END;
ALTER TABLE customers MODIFY COLUMN email SET MASKING POLICY mask_email;
CREATE ROW ACCESS POLICY rap_region AS (region STRING) RETURNS BOOLEAN ->
EXISTS (SELECT 1 FROM sec.role_region m
WHERE m.role_name = CURRENT_ROLE() AND m.region = region);
ALTER TABLE sales ADD ROW ACCESS POLICY rap_region ON (region);
Both are policy objects attached to columns or tables, so one policy governs many objects and a change applies everywhere at once. That centralisation is the point, and it is why this beats a view per audience.
Time Travel and Fail-safe
| Time Travel | Fail-safe | |
|---|---|---|
| Window | 0 to 90 days on Enterprise, 0 to 1 on Standard | 7 days, fixed |
| Queryable |
Yes, AT or BEFORE, and
UNDROP
|
No. Snowflake support only. |
| Configurable | Yes, DATA_RETENTION_TIME_IN_DAYS |
No |
| Billed | Yes, as storage | Yes, as storage |
SELECT * FROM orders BEFORE (STATEMENT => '01a2b3c4-...'); -- before that bad DML
CREATE TABLE orders_recovered CLONE orders AT (OFFSET => -3600);
UNDROP TABLE orders;
The GDPR consequence to volunteer: a deleted row remains recoverable for Time Travel plus 7 days of Fail-safe, and Fail-safe cannot be shortened or queried. Legal needs to be told that number, and knowing to tell them is the senior part.
Zero-copy cloning
CREATE TABLE ... CLONE creates a new object pointing at
the same micro-partitions. Instant, and free until one side
changes, at which point only the changed micro-partitions are new
storage. Clone a table, a schema or an entire database.
The use that impresses: clone production into a dev database to test a migration against real data with real volume, at no storage cost and no load time. That is a genuinely different capability from anything BigQuery or Databricks offers as cleanly, and it is worth naming as a reason to have Snowflake in the stack.
Secure Data Sharing
A provider creates a Share containing objects and grants it to a consumer account. The consumer sees a read only database over the provider's storage. No copy, no ETL, no staleness, and the consumer pays for their own compute. Across regions or clouds it requires replication, which does copy.
Reader accounts let you share with an organisation that has no Snowflake account, with you paying for their compute. That is the detail that shows you have actually used sharing rather than read about it.
Zero copy cloning means I can stand up a full production sized dev environment instantly at no storage cost, which is how you test a migration against real volume rather than against a sample.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
dbt and the transformation layer
Incremental models with merge logic, snapshots for SCD2, macros, and tests as deployment gates.
What dbt actually is
SQL SELECT statements in files, with
ref() between them. dbt builds the dependency graph
from the ref() calls, works out the execution order,
and wraps each model in the right DDL.
You never write CREATE TABLE. That is the whole idea and it
is why the answer to "what does dbt give you over scheduled queries"
is:
a graph, a materialization strategy, and tests, in version
control.
The four materializations
| Materialization | Does | Use when |
|---|---|---|
view |
Creates a view | Cheap, light transformations near the source |
table |
Full rebuild each run | Small to medium, or when correctness beats cost |
incremental |
Inserts or merges only new or changed rows | Large fact tables. The one you must be able to defend. |
ephemeral |
Inlined as a CTE, no object created | Intermediate logic used once |
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge',
on_schema_change='append_new_columns'
) }}
SELECT * FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})
{% endif %}
The incremental trap:
WHERE updated_at > MAX(updated_at) silently misses
late arriving records whose updated_at is older than
the current maximum. The fix is a lookback window,
> MAX(updated_at) - INTERVAL '3 days', combined
with a unique_key and merge strategy so the
re-processed rows update rather than duplicate.
Being able to name this unprompted is the single best dbt
signal you can give.
And know --full-refresh: it rebuilds the model from
scratch, ignoring the incremental predicate. It is the escape hatch
when the logic changes, and it is why an incremental model still
needs to be correct as a full build.
Snapshots, which is how dbt does SCD Type 2
{% snapshot snap_customers %}
{{ config(target_schema='snapshots', unique_key='customer_id',
strategy='timestamp', updated_at='updated_at') }}
SELECT * FROM {{ source('crm','customers') }}
{% endsnapshot %}
dbt maintains dbt_valid_from and
dbt_valid_to for you. Two strategies:
timestamp (use a reliable updated_at) and
check (compare a list of columns, for sources with no
reliable timestamp).
Snapshots run against the source, not against a model,
because they have to capture state before transformation loses it.
That ordering is a good detail to know.
Tests as gates
| Kind | What | Example |
|---|---|---|
| Generic | Declared in YAML on a column |
unique, not_null,
accepted_values, relationships
|
| Singular | A SQL file that returns failing rows | Any business rule: no negative revenue, sum of parts equals total |
| Package tests | dbt_utils, dbt_expectations |
expression_is_true, recency, distribution
checks
|
Severity is the tiering mechanism:
severity: error fails the run,
severity: warn logs. That maps exactly onto the
blocking versus alerting split:
uniqueness and referential integrity error; volume and
distribution warn.
Enforced as deployment gates with CI promotion from development
to production
means the test run is in the CI job, not in a dashboard.
The rest of the surface, briefly
-
Sources with
freshnessthresholds, so a stale upstream fails before the models run. - Macros, Jinja functions for repeated logic. The discipline: a macro that hides business logic makes the SQL unreadable, so keep macros structural.
- Seeds, small CSVs in the repo for lookup tables.
- Exposures, declaring downstream dashboards so lineage extends past dbt.
- docs generate, which produces the lineage graph people actually use.
The comparison question: dbt versus Dataform. Both are SQL transformation with dependencies, assertions and environments. Dataform is GCP native and integrated with BigQuery; dbt is engine agnostic and has by far the larger ecosystem. Using Dataform on the BigQuery side and dbt on the Snowflake side is a defensible split: the native tool where it is native, the portable tool where you need portability. Say it that way.
dbt gives you a dependency graph, a materialization strategy and tests, in version control. The incremental lookback window is the detail that decides whether it is correct, because a naive max watermark silently drops late arriving rows.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
The three way comparison
The question any multi platform architecture guarantees. Twelve comparators, then how to draw the boundary.
Turn on recall mode (r) and answer each comparator out loud before revealing. These are the highest frequency questions in a multi platform loop.
PARTITION BY and
CLUSTER BY at create time. Prefix ordered, four
columns. Enforceable with
require_partition_filter.
OPTIMIZE for file size.
RESTORE and DESCRIBE HISTORY. Lower
the retention and you lose the window.
UNDROP, plus
7 days of Fail-safe that is fixed and not queryable.
The live locator
Answer what you know. It narrows a candidate set, it does not decide for you, and saying that out loud is part of the answer.
The consolidation question, which always comes
Have an answer, and do not say "nothing". A candidate who defends every past decision reads as someone who does not revisit them.
A defensible version: "If the ML teams had not already been in notebooks, I would have argued for two rather than three: BigQuery for analytics and Snowflake for governed consumption, or BigQuery alone with a strict authorized view layer for the finance audience. The third engine bought us ML compute and Delta ingestion, and it cost us a third cost model and a third place a metric definition could drift. I would want that trade made explicitly rather than arrived at."
Then the mitigation you actually had: single copy storage on GCS, one catalog, and dbt owning the metric definitions.
The split was by audience and governance posture, not by capability: BigQuery for analysts, Databricks where the ML teams already lived, Snowflake as the governed surface finance and executives consumed.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Data modeling and warehouse design
Kimball, declared grain, SCD2, Data Vault and the semantic layer. The models an organisation treats as source of truth.
Declared grain, which is the whole discipline
The grain is what one row means. Not what the table contains, what a single row is. "One row per order line per shipment event." If you cannot say it in one sentence, the model is wrong and every aggregate on it will eventually be wrong too.
Declared fact grain is the right phrase, and the follow up is always: what is the grain of your payment fact? Have the sentence ready. Something like "one row per payment attempt per processor response, keyed on payment id and attempt sequence" is a real answer; "transaction level" is not.
Fact types
| Type | One row is | Aggregates how |
|---|---|---|
| Transaction | An event that happened | Fully additive across all dimensions. The default. |
| Periodic snapshot | A measurement at a regular interval | Semi additive. A balance sums across accounts but not across time. |
| Accumulating snapshot | A pipeline instance, updated as it progresses | Milestone dates fill in over time. Good for order to cash, claim to payment. |
| Factless | An occurrence with no measure | Counted, not summed. Attendance, coverage, eligibility. |
Financial position data is the periodic snapshot case. Cash, balance and float positions across payment methods are semi additive: you can sum a balance across accounts but summing it across days is meaningless. Being able to say semi additive about your own data is a precise, senior signal.
Slowly changing dimensions
| Type | Does | Cost |
|---|---|---|
| Type 1 | Overwrite. No history. | Cheapest. Historical facts silently re-attribute. |
| Type 2 | New row per change with valid_from, valid_to and a current flag | The default for anything audited. Row count grows with change rate. |
| Type 3 | A previous value column | Only one step of history. Rarely right. |
| Type 6 | Type 2 rows plus current value columns on every row | Convenience for 'as is' and 'as was' in one query |
The question that separates people: at scale, is MERGE the right way to maintain Type 2? Often not. MERGE rewrites every partition it touches, and dimension changes spread across all partitions, so a daily MERGE rewrites most of the table. Append only with a current view writes only the changed rows and turns point in time into the same query with one extra predicate:
SELECT * EXCEPT(rn) FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY customer_id ORDER BY valid_from DESC) rn
FROM dim.customer_history
WHERE valid_from <= @as_of) -- omit for current state
WHERE rn = 1 AND NOT is_deleted;
Materialize the current view if reads are hot. On Snowflake this is exactly what a dbt snapshot plus a view gives you; on Databricks it is a Delta table plus a gold view.
Star, snowflake and Data Vault
| Model | Shape | When |
|---|---|---|
| Star | Fact surrounded by denormalized dimensions | Default for analytics. Fewer joins, better pruning, readable. |
| Snowflake schema | Dimensions normalized into sub dimensions | Rarely worth it on columnar storage. Storage saving is negligible, join cost is not. |
| Data Vault | Hubs (business keys), Links (relationships), Satellites (attributes with history) | Many volatile sources, heavy audit requirement, parallel loading. It is an integration layer, not a consumption layer. |
| 3NF staging | Normalized landing | Before the dimensional layer, where source fidelity matters |
If asked about Data Vault, the answer that shows you understand it: Data Vault is built for auditability and parallel loading from many changing sources, and it is deliberately not queryable by analysts. You still build a star schema on top. Anyone presenting Data Vault as an alternative to Kimball for consumption has misunderstood it.
Conformed dimensions and the semantic layer
A conformed dimension means the same dimension table is used identically by multiple facts, so a metric sliced by it is comparable across subject areas. That is what makes "revenue by region" and "cost by region" addable. Canonical metric definitions consumed as reusable data marts is the same idea stated as a metric layer. The three platform risk is exactly here: three engines means three places a definition can drift, and dbt owning the definitions is the mitigation.
The grain is what one row means, in one sentence. If I cannot say it, the model is wrong and every aggregate on it will eventually be wrong too.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Streaming correctness across all three
Exactly once, idempotency, dedup and late data, across all three platforms.
Exactly once is a property of a boundary, not a system
No product gives end to end exactly once across a producer, a broker, a processor and a sink. What products give is exactly once within a boundary. Stitching those into an end to end guarantee requires the application to carry an idempotency key, at which point the guarantee is yours, not the vendor's.
So the answer to any exactly once question is: name the guarantee at every hop, then say where the deduplication lives. That single move handles the whole question family.
| Hop | Guarantee | Where dedup can live |
|---|---|---|
| Producer to Pub/Sub | At least once | Nowhere yet. Carry a business idempotency key in the payload. |
| Pub/Sub to consumer | At least once; exactly once on pull, single region only | Pub/Sub itself, if pull and single region |
| Dataflow processing | Exactly once by default |
Beam Deduplicate on a key over a bounded window
|
| Dataflow to BigQuery | Exactly once via Storage Write API offsets | The offset mechanism: a retry at the same offset is a no op |
| Pub/Sub to BigQuery direct | At least once only | Downstream, in BigQuery |
| Structured Streaming to Delta | Exactly once via checkpoint plus the Delta log |
foreachBatch with an idempotent MERGE, plus
txnAppId and txnVersion
|
| Snowpipe Streaming | Exactly once via channel offset tokens | The channel offset |
Three places dedup can live, and how to choose
| Where | Mechanism | When |
|---|---|---|
| In the stream processor |
Beam Deduplicate, or Structured Streaming
dropDuplicatesWithinWatermark
|
Duplicates arrive close together. Cheap, bounded state. |
| At the write |
Storage Write API offsets, Delta txnAppId plus
txnVersion, Snowpipe Streaming offset tokens
|
You control the writer and can assign a monotonic sequence |
| In the warehouse |
MERGE on a business key, or a view with
QUALIFY ROW_NUMBER() = 1
|
Duplicates can arrive arbitrarily far apart. The robust backstop. |
Do not dedup on the transport identifier. A Pub/Sub message id or a Kafka offset identifies a delivery, not a business event. Replay yesterday's file and every event gets a new message id, so message id dedup will not catch it. Dedup on a business idempotency key, and if the source does not have one, that is a conversation with the producer, not a problem to engineer around.
Late data, and the design that avoids the wall
The naive answer to "data can be 48 hours late" is
allowed_lateness = 48 hours. That is the trap.
Streaming Engine caps open window state at 60 GB per key, so
a hot key plus long lateness is a hard wall rather than a slope.
The design: handle a bounded window in the streaming path, say six hours, which covers the overwhelming majority of real lateness, and reconcile the rest with a daily batch over the archive that replaces the affected partitions. Bounded state, predictable cost, and the batch path reuses data you already have.
Say the reasoning: "I would rather pay for a predictable daily batch than for streaming state held on the chance that a small fraction of events are very late."
The idempotent write, per platform
# BigQuery: partition decorator, replaces atomically
bq load --replace 'project:ds.table$20260821' ...
# Delta: idempotent foreachBatch
def upsert(microBatchDF, batchId):
microBatchDF.sparkSession.conf.set("spark.databricks.delta.commitInfo.userMetadata", str(batchId))
(DeltaTable.forName(spark, "gold.orders").alias("t")
.merge(microBatchDF.alias("s"), "t.id = s.id")
.whenMatchedUpdateAll().whenNotMatchedInsertAll().execute())
# plus .option("txnAppId", "orders_job").option("txnVersion", batchId)
# so a replayed batch is recognised and skipped
-- Snowflake: MERGE from a stream, offset advances only on success
MERGE INTO gold.orders t USING str_orders s ON t.id = s.id ...;
Exactly once is a property of a boundary, not a system. I name the guarantee at every hop, then say where deduplication lives, and I key it on a business idempotency key rather than a transport identifier.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
SQL drills that come up in every loop
Twelve patterns. Write them, do not read them. Each has the follow up that separates people.
Claiming window functions, complex CTEs, grouping sets, UDFs, partition pruning, join strategy selection and execution plan tuning across several engines is a wide claim, and it invites a live coding round. These twelve patterns are the ones that come up.
Write them by hand, on paper or in a plain editor with no autocomplete. Recognising SQL and producing SQL under observation are different skills, and only the second one is being tested.
-- BigQuery and Snowflake and Databricks SQL all support QUALIFY
SELECT * FROM events
QUALIFY ROW_NUMBER() OVER (
PARTITION BY event_id ORDER BY ingested_at DESC) = 1;
QUALIFY filters on a window function without a subquery. Works on all three, which is worth knowing because it is the fastest correct answer in any of them.
If reads are hot, do you leave this as a view? No. Materialize it on a schedule, otherwise you pay the window function on every read.
SELECT d, k, x,
SUM(x) OVER (PARTITION BY k ORDER BY d
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running,
AVG(x) OVER (PARTITION BY k ORDER BY d
RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW) AS ma7
FROM daily;
Why RANGE and not ROWS for the moving average? ROWS counts rows, so a missing day silently shifts the window. RANGE counts values, so gaps are handled correctly.
WITH flagged AS (
SELECT *, IF(TIMESTAMP_DIFF(ts, LAG(ts) OVER (PARTITION BY user_id ORDER BY ts),
MINUTE) > 30 OR LAG(ts) OVER (PARTITION BY user_id ORDER BY ts) IS NULL,
1, 0) AS is_new
FROM events)
SELECT *, SUM(is_new) OVER (PARTITION BY user_id ORDER BY ts) AS session_id
FROM flagged;
Flag a gap, then a running sum of the flag is the session id. The classic.
How would you do this in Beam? Session windows with a gap duration, natively. In Structured Streaming, flatMapGroupsWithState or a session window function.
SELECT d FROM UNNEST(GENERATE_DATE_ARRAY('2026-01-01','2026-08-24')) AS d
LEFT JOIN facts f ON f.load_date = d
WHERE f.load_date IS NULL;
Generate the full range, left join, filter for nulls. The general answer to any 'what is missing' question and it should be a reflex.
How do you turn this into a freshness alert? Bound it to the last N days and fail the run if the count is non zero.
SELECT * FROM sales
QUALIFY ROW_NUMBER() OVER (PARTITION BY region ORDER BY revenue DESC) <= 3;
ROW_NUMBER, RANK or DENSE_RANK? ROW_NUMBER breaks ties arbitrarily and gives exactly N. RANK leaves gaps after ties. DENSE_RANK does not. Picking the wrong one is a silent correctness bug.
MERGE INTO dw.orders t
USING (
SELECT * EXCEPT(rn) FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY order_id ORDER BY source_lsn DESC) rn
FROM stg.orders_delta)
WHERE rn = 1
) s
ON t.order_id = s.order_id
WHEN MATCHED AND s.op = 'D' THEN DELETE
WHEN MATCHED THEN UPDATE SET status = s.status, amount = s.amount
WHEN NOT MATCHED AND s.op != 'D' THEN INSERT ROW;
Why is the inner dedupe mandatory? MERGE errors when more than one source row matches one target row, and every CDC batch has multiple changes per key.
SELECT f.*, d.segment
FROM fact_orders f
JOIN dim_customer_hist d
ON f.customer_id = d.customer_id
AND f.order_ts >= d.valid_from
AND f.order_ts < COALESCE(d.valid_to, TIMESTAMP '9999-12-31');
The half open interval and the COALESCE on the open ended row are the two things people get wrong.
What if valid_to is null for the current row and you use a closed interval? You double count on the boundary timestamp.
-- conditional aggregation works everywhere and handles a dynamic set badly
SELECT region,
SUM(IF(quarter='Q1', revenue, 0)) AS q1,
SUM(IF(quarter='Q2', revenue, 0)) AS q2
FROM sales GROUP BY region;
-- PIVOT exists on all three but needs a literal value list
How would you handle an unknown value list? Generate the SQL, with EXECUTE IMMEDIATE on BigQuery or Snowflake. Or push the pivot to the BI tool, which is usually the right answer.
SELECT o.order_id, item.sku, item.qty
FROM orders o, UNNEST(o.items) AS item WITH OFFSET AS pos;
Nesting wins when the child is always read with the parent: the join happened at write time so there is no shuffle. It loses when the child is queried independently, and updating one element rewrites the parent row.
What is the Presto to BigQuery gotcha here? Array indexing. Presto is 1 based; BigQuery is 0 based with OFFSET and 1 based with ORDINAL. It is a silent wrong answer, not an error.
SELECT COUNT(*) AS rows, COUNT(DISTINCT order_id) AS keys,
COUNT(*) - COUNT(DISTINCT order_id) AS dupes
FROM dw.fact_orders
WHERE order_date = CURRENT_DATE() - 1;
The cheapest and highest value check you can write. It catches most silent duplication and it belongs on every fact table with a consumer.
Does it block or alert? Block. A wrong number is worse than a missing one, which is the tiering rule.
SELECT COALESCE(a.k, b.k) AS k, a.amt AS src_amt, b.amt AS tgt_amt,
a.amt - b.amt AS diff
FROM (SELECT k, SUM(amount) amt FROM source GROUP BY k) a
FULL OUTER JOIN (SELECT k, SUM(amount) amt FROM target GROUP BY k) b USING (k)
WHERE a.amt IS DISTINCT FROM b.amt;
FULL OUTER plus IS DISTINCT FROM catches missing on both sides and null safe differences in one query. This is the shape of the processor to ledger to warehouse reconciliation on a resume.
Why IS DISTINCT FROM rather than !=? Because != is null unsafe, so a row present on one side only would not appear as a difference.
SELECT APPROX_COUNT_DISTINCT(user_id) FROM events; -- BigQuery, Databricks
SELECT APPROX_COUNT_DISTINCT(user_id) FROM events; -- Snowflake: HLL
HyperLogLog, roughly one percent error, dramatically less shuffle because it exchanges sketches rather than values. Fine for a trend line, wrong for a regulatory or financial count.
How would you get exact distinct cheaply across many days? Precompute HLL sketches per day and merge them, which all three support and which gives approximate answers that are cheap to roll up.
Dialect differences that cause incidents
| Area | BigQuery | Snowflake | Databricks SQL |
|---|---|---|---|
| Array index | 0 based with OFFSET, 1 based with ORDINAL | 1 based | 0 based |
| Filter on a window fn | QUALIFY |
QUALIFY |
QUALIFY |
| Semi structured | JSON type, nested STRUCT and ARRAY |
VARIANT, OBJECT,
ARRAY, colon path syntax
|
Nested types plus from_json |
| Integer division |
/ returns FLOAT64, DIV() truncates
|
/ returns a decimal |
/ returns double, div truncates
|
| Identifier quoting | backticks | double quotes, case sensitive when quoted | backticks |
| Null safe equality | IS NOT DISTINCT FROM |
IS NOT DISTINCT FROM |
<=> |
Array indexing and integer division are the two silent ones. Everything else fails loudly, which is the good kind of failure. Those two produce a query that runs and returns a subtly wrong number, which is exactly the bug that survives to production. If you are asked about migrating SQL across engines, name those two first.
QUALIFY works on all three, which makes the deduplication and top N answers portable. The two dialect differences I check first in any migration are array indexing and integer division, because they fail silently.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
System design case bank
Six cases, each worked to the cost conversation. The first one is your own architecture, which you will be asked to draw.
Fixed shape for all six. After three of them it becomes automatic under pressure:
- Restate and clarify. Two or three questions, then stop. Endless clarification reads as stalling.
- Constraints out loud, with arithmetic. Do the division in the room.
- Draw, with a reason per box and a rejected alternative for the important ones.
- Failure modes, unprompted. Naming how your own design breaks is the strongest single move available.
- End on cost. Never wait to be asked.
Turn on recall mode (r) and the architecture and cost sections blur until you click. Case 01 is the reference architecture. Some version of it appears in almost every design round, so it is the one to have automatic.
What they are really asking
- Whether you can draw a complete platform cleanly under time pressure
- Whether every box has a reason and a rejected alternative
- Whether you end on cost without being asked
Ask these before you draw anything
- What is the freshness requirement per consumer? Analytics, ML and finance almost never want the same thing.
- How much of the volume needs to be streaming versus batch?
- Who owns the metric definitions, and is there one definition or several today?
- What can the team actually operate?
The architecture, with a reason per box
producers
|
v
Pub/Sub (schema on topic, Avro, backward compatible) --> DLT + replay
|
+--> Dataflow (Beam, event time windows, exactly once)
| --> BigQuery via Storage Write API [analytics, near real time]
|
+--> GCS raw zone, Hive partitioned dt=/hour= [replay, archive, ML]
|
+--> Auto Loader --> bronze --> DLT/Structured Streaming --> silver
| --> gold Delta, MERGE upserts [Databricks: ML compute]
|
+--> BigLake / Iceberg tables [one copy, many readers]
BigQuery -> Snowpipe from GCS -> Snowflake + dbt [governed consumption:
finance, exec reporting]
Cloud Composer + Cloud Workflows orchestrate. Dataplex catalogs and lineages.
Terraform provisions. One CI/CD path: Dataflow, Dataproc, Asset Bundles, dbt.
- One copy of data on GCS and BigLake, not three. That is the sentence that makes three engines defensible instead of wasteful.
- Schema on the Pub/Sub topic because it is the only place a bad producer is caught before storage.
- Storage Write API rather than a BigQuery subscription, because the Dataflow stage is needed for windowing anyway and it is where exactly once lives.
- Snowflake fed from the lake, not from BigQuery, so the consumption warehouse is not downstream of a transformation it does not own.
- dbt owns the metric definitions, which is the mitigation for the three engine drift risk. Name the risk yourself.
The cost conversation
Do it out loud from the rates. At ~100 TB/day: about 3 PB a month of new data. BigQuery active logical at about $0.023 per GiB is roughly $70k a month for 3 PB, falling as partitions age past 90 days. Storage Write API at $0.025 per GiB on the streaming share. Pub/Sub at $40 per TiB. Then the real lever: if most of that 3 PB is not read in a quarter it does not belong in warehouse storage at all, and cold partitions go to Nearline or Coldline with an Iceberg or external table over them.
That single decision is worth more than every query optimisation in the design, and saying it is what turns a diagram into an architecture.
How it fails
- A producer ships a schema change; the topic schema rejects it and the producer team finds out at 2am, which is correct but needs an owner.
- The three engines drift on a metric definition because someone bypassed dbt.
- The streaming path and the batch reconciliation disagree and nobody labelled which is provisional.
- Snowflake ingestion falls behind because Snowpipe is loading thousands of tiny files.
Follow ups they will hit you with
- Why not just BigQuery for all of it?
- Where does exactly once live and where do duplicates get removed?
- How do you stop the same metric being defined three times?
- What breaks first at 10x?
What they are really asking
- Whether you investigate before theorising
- Whether you know the right system table on each platform
- Whether you separate a human mistake from an automated leak
Ask these before you draw anything
- Which platform, or all three? They have completely different causes.
- Compute or storage? That halves the search space immediately.
- Step change or ramp? A step points at a deploy, a ramp points at data growth.
The architecture, with a reason per box
One query per platform, and they are the thing to know
-- BigQuery
SELECT DATE(creation_time) d, user_email,
SUM(total_bytes_billed)/POW(1024,4)*6.25 usd
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 60 DAY)
GROUP BY d, user_email ORDER BY usd DESC;
-- Databricks
SELECT usage_date, custom_tags.team, SUM(usage_quantity) dbus
FROM system.billing.usage
WHERE usage_date >= current_date() - INTERVAL 60 DAYS
GROUP BY 1,2 ORDER BY dbus DESC;
-- Snowflake
SELECT DATE_TRUNC('day', start_time) d, warehouse_name,
SUM(credits_used) credits
FROM snowflake.account_usage.warehouse_metering_history
WHERE start_time > DATEADD(day,-60,CURRENT_TIMESTAMP())
GROUP BY 1,2 ORDER BY credits DESC;
The usual causes, per platform
| Platform | Most likely | Signature |
|---|---|---|
| BigQuery | A dashboard shipped and misses the result cache, or a partition filter stopped pruning | Job count up with flat bytes per job means dashboard. Flat job count with bytes per job up means pruning. |
| Databricks | An all purpose cluster left running, or auto termination removed, or Photon enabled on a job it does not help | Steady DBU consumption outside working hours is the giveaway |
| Snowflake | Auto-suspend lengthened or removed, a warehouse sized up and never sized down, or Automatic Clustering on a high churn table | Credits flat across the day rather than tracking query volume |
The cross platform cause nobody looks for: the same data materialized in all three because three teams each built their own copy. That shows up as storage growth on all three simultaneously, and it is the specific risk of a three engine estate.
The cost conversation
Say the arithmetic before you run anything. A 3x increase on a known baseline tells you the magnitude of the cause. If BigQuery went from $10k to $30k, that is 3,200 extra TiB a month at $6.25, roughly 105 TiB a day. If the table is 3 TiB, that is 35 full scans a day, which is a dashboard. If the table is 100 TiB, it is one scan a day, which is a scheduled query. The arithmetic narrows the suspect list before the first query runs.
How it fails
- You fix it and it returns in three weeks because nothing stopped it.
- There are three causes, one per platform, and you find the biggest and stop.
- The fix breaks a report the finance team depends on and nobody told you it existed.
Follow ups they will hit you with
- What control do you add so it cannot recur on each platform?
- What is the difference between what notifies and what stops, on each?
- How would you have caught this on day one?
What they are really asking
- Whether you make the write idempotent before writing anything
- Whether you isolate the backfill compute from live compute
- Whether you have verification and a rollback
Ask these before you draw anything
- Is the raw source still available for all nine months?
- Is the live pipeline still writing to the same table right now?
- Do consumers need it correct throughout, or can we cut over at the end?
- Is the fix deterministic? If reprocessing the same input twice gives different answers we have a bigger problem.
The architecture, with a reason per box
1. Shadow table, identical schema, partitioning and clustering.
2. Backfill partition by partition, OLDEST FIRST, WRITE_TRUNCATE per partition.
bq load --replace 'ds.fact_orders_v2$20260101' ...
Idempotent by construction: rerunning a partition replaces it.
3. Live pipeline dual writes to both from the moment step 2 starts.
4. Verify per partition: row counts, key cardinality, aggregate fingerprints,
with the expected delta explained rather than tolerated.
5. Swap by redefining the view consumers already read through.
6. Keep v1 for a defined window, then drop.
-
Partition decorator with WRITE_TRUNCATE is the whole
idempotency story.
On Delta the equivalent is
replaceWhere; on Snowflake it is a delete plus insert inside a transaction, or a MERGE. - Oldest first, so if you run out of time or budget the most recent data is still on the old path and consumers are least affected.
- Throttle it. Put the backfill in its own reservation, its own Databricks job cluster, or its own Snowflake warehouse, so it cannot starve live pipelines. This is the actual failure mode.
- Consumers should already read a view, which turns cutover from a rename with downtime into an atomic view redefinition.
The cost conversation
270 daily partitions. If a partition is 200 GiB and the transform reads it once, that is about 53 TiB, roughly $330 on BigQuery on demand. Say the number. Then the cost people forget: you are storing two copies of nine months for the duration of verification. At 50 TiB that is about $1,200 a month for as long as you take, which creates a real incentive to verify quickly. Naming that yourself is better than finance discovering it.
How it fails
- Someone runs all 270 partitions in parallel and starves the reservation, taking down live pipelines.
- The transform is not deterministic because it reads a dimension that has since changed, producing a third answer.
- Verification passes on counts and fails on sums, because the bug affected values not rows.
- A downstream consumer had hardcoded the physical table name.
Follow ups they will hit you with
- What if the raw source only goes back six months?
- How do you handle the partition currently being written?
- The business wants it correct throughout, not at the end. What changes?
- How do you prove it worked?
What they are really asking
- Whether you can argue against your own architecture
- Whether you reason from workload rather than from preference
- Whether you have a migration plan rather than an opinion
Ask these before you draw anything
- What is the driver: cost, operational burden, or vendor consolidation? Each points somewhere different.
- What does each platform uniquely do that the others cannot?
- Where do the ML teams actually work, and are they movable?
- What is the appetite for a migration that takes two quarters?
The architecture, with a reason per box
The elimination, done honestly
| Drop | What you lose | Feasibility |
|---|---|---|
| Snowflake | The governed consumption surface finance and executives use, plus zero copy cloning and Secure Data Sharing | Most feasible. BigQuery authorized views plus policy tags can serve the finance audience with real discipline. The loss is a clean governance boundary, not a capability. |
| Databricks | MLflow, notebooks, distributed training, Auto Loader and DLT. The ML teams move or they do not. | Feasible only if ML moves to Vertex AI and the medallion moves to Dataflow plus Dataproc. That is a people problem more than a technical one. |
| BigQuery | The analyst surface and zero cost idle. Everything else has a cluster or warehouse concept. | Least feasible. It is the one with no operational surface at all. |
The recommendation: drop Snowflake first if the driver is cost or vendor count, because the finance audience can be served by an authorized view layer in BigQuery with policy tags, and that is the smallest capability loss. Drop Databricks only if the ML organisation is willing to move, because otherwise you have not consolidated a platform, you have created a shadow one.
The migration: rank by consumer, not by table. Move the dbt project to Dataform or point dbt at BigQuery (dbt is engine agnostic, which is the argument for having used it). Rebuild the RBAC hierarchy as policy tags and authorized views. Dual run the finance reports for a full close cycle, because a finance report that changes during migration is a credibility event, not a bug.
The cost conversation
Frame it for leadership honestly: consolidating saves a licence and a cost model, not necessarily money. The same workload on one engine costs roughly the same compute; what you save is the second and third cost model, the second and third access model, and the engineering time spent keeping definitions aligned. Say that the saving is mostly in attention rather than in dollars, because a leader who expects a large cost drop and gets a small one will remember.
How it fails
- The ML teams do not move and start running Spark on Dataproc badly.
- The finance audience loses the governance boundary and starts querying engineering tables directly.
- The dbt project ports but the Snowflake specific SQL does not.
- Migration takes two quarters and the driver was a quarterly cost target.
Follow ups they will hit you with
- What if they said drop BigQuery instead?
- How long, and what would you dual run?
- What did the third platform actually buy you?
- Would you have built it with three if you started today?
What they are really asking
- Whether you know a warehouse is the wrong place for point reads
- Whether you separate the compute layer from the serving layer
- Whether you know your own resume's feature platform answer
Ask these before you draw anything
- Is the read a single key lookup or a range scan?
- How stale can the feature be? A 24 hour rolling window updated every five minutes is very different from continuously.
- Does the same feature need to be available offline for training?
- How hot is the hottest key?
The architecture, with a reason per box
events -> Pub/Sub -> Dataflow (sliding window, 24h, 5min period)
|
+--> Bigtable key = entityId#reverseTimestamp
| [serving: sub second single key read]
|
+--> BigQuery / Delta
[offline: training, backfill, audit]
- Not a warehouse. BigQuery bills a minimum per table referenced and is scan oriented; Snowflake needs a running warehouse; Databricks SQL is not a point lookup store. Millions of point reads a day belong in Bigtable or a cache.
-
Row key
entityId#reverseTimestamp, so writes distribute across entities and the recent window for one entity is a prefix scan. Never lead with a timestamp; all writes land in one tablet. - Write both: the serving copy for latency and the warehouse copy for training and audit. That dual write is where training serving skew comes from, and it is why automated validation guaranteeing consistency between the offline training set and the production inference path is the interesting part.
- Sliding windows multiply cost by size over period. A 24 hour window every 5 minutes puts each element in 288 windows. Say that number out loud and consider whether an incrementally maintained aggregate in Bigtable is cheaper than recomputing the window.
The cost conversation
The sliding window arithmetic is the cost answer: size over period is 24h / 5min = 288, so every element participates in 288 window computations. At billions of events a day that is not a rounding error. The alternative is a stateful DoFn maintaining a running aggregate with timers, which is more code and dramatically less compute. Doing that division in the room is the whole answer.
How it fails
- Row key leads with a timestamp and one Bigtable node takes every write.
- The offline and online features diverge and the model degrades silently in production.
- The sliding window multiplies compute by 288 and nobody priced it.
- A hot entity key serialises and the p99 read latency collapses.
Follow ups they will hit you with
- How do you guarantee the training and serving features match?
- What is your Bigtable row key and why?
- The window becomes 7 days. What changes?
- How would you backfill this feature for a new model?
What they are really asking
- Whether you know the retention limits of each platform
- Whether you separate data lineage from code lineage
- Whether you have actually worked under audit
Ask these before you draw anything
- Fourteen months is beyond every default time travel window. Do we have an explicit archive?
- Is the question about the data, the code, or both? Auditors usually mean both and only say one.
- What was promised in the retention policy, and does the platform match it?
The architecture, with a reason per box
The honest limits
| Platform | Time travel reach | Fourteen months? |
|---|---|---|
| BigQuery | 7 days default, 2 to 7 configurable, plus table snapshots | No, unless snapshots were taken. Snapshots are the mechanism. |
| Databricks Delta | Bounded by VACUUM retention, default 7 days | No, unless VACUUM retention was extended or you kept a versioned copy. |
| Snowflake | 0 to 90 days on Enterprise plus 7 days Fail-safe | No. 90 days is the ceiling. |
So the answer is not time travel. It is an explicit archive. The design that answers this question:
- SCD Type 2 on the dimension, so the state at any date is a query rather than a recovery operation. This is why regulated estates lean on SCD2 rather than on recovery features, and it is the correct answer to the auditor.
- Immutable raw archive on cloud storage, partitioned by date, with lifecycle to Coldline or Archive rather than deletion.
- Table snapshots or scheduled point in time extracts for the regulatory reporting layer, which is the pattern to name: scheduled queries for daily regulatory extracts, plus table snapshots for point in time evidence requests.
- Code lineage from version control, tagged per release, so the detection logic that ran on that date is identifiable. Data lineage tells you where the number came from; git tells you what computed it. Auditors need both and most teams only have one.
The cost conversation
The cost of being able to answer this is storage and it is small: an immutable archive in Coldline is a fraction of warehouse storage, and SCD2 history grows with the change rate, not the row count. The cost of not being able to answer it is a finding, which in a regulatory finding environment is a different order of magnitude. That framing is the one to use, because it converts a storage line item into risk reduction.
How it fails
- Time travel was assumed sufficient and nobody checked the window against the retention policy.
- The archive exists but nobody can map a date to the code version that ran.
- SCD2 exists but the valid_from was set from load time rather than source effective time, so the history is about when you learned rather than when it was true.
- VACUUM was run with a shortened retention and removed the files.
Follow ups they will hit you with
- What is the difference between when it was true and when you learned it?
- Which platform could actually answer this natively, and for how long?
- How do you tie the data back to the code that produced it?
- What would you have designed differently on day one?
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Behavioral and leadership rounds
Where adjectives become evidence. Prepared once, these are the easiest points in the loop to collect.
Technical candidates under-prepare this round and then lose offers in it. The content is not hard; the failure is always the same three things: answers that run long, answers with no number, and answers that describe a team rather than a person.
The question map
Almost every behavioral question maps to one of the twelve scaffolds in module 02. If you wrote those, this round is already done.
| They ask | Use scaffold | The trap |
|---|---|---|
| Tell me about yourself | The two minute open | Running long, and burying the scale number past sentence two |
| Something you built from nothing | 01 | Describing the product instead of your contribution |
| Influencing without authority | 02 | Stopping at the pitch. Delivery is the story. |
| A time you reduced cost | 03 | No control added, so it was a cleanup rather than an engineering change |
| Hardest technical problem | 05 or 08 | Choosing a problem that was hard to build rather than hard to get right |
| A production failure you owned | 06 | A vague incident. Name the actual root cause. |
| Disagreement with someone senior | 07 | Arguing twice, or never arguing at all |
| Working under constraint | 05 | Describing compliance as a blocker rather than an input |
| Data quality | 09 | Listing checks without tiering which block and which alert |
| Most interesting recent work | 10 | Leading with the demo instead of the evaluation |
| A failure or mistake | 11 | A humble brag. Interviewers hear these constantly. |
| Mentoring and leadership | See below | Describing a team instead of a person |
| Why this architecture | 12 | Answering 'what would you consolidate' with 'nothing' |
Leadership, evidenced rather than claimed
| Claim | The evidence that makes it real |
|---|---|
| I mentored engineers | One person, one thing they could not do, one thing they can do now. Not 'I mentored the team'. |
| I set the engineering standards | Name one standard and the argument you had about it. A standard with no dissent was a preference, not a standard. |
| I owned the roadmap | What you cut, and why. Roadmap ownership is visible in what you said no to, not in what shipped. |
| I partnered with senior stakeholders | One conversation that changed a decision, in either direction. Naming the level is worth less than naming the change. |
| I led the team | A decision you made that the team disagreed with, and what happened. |
The pattern across all five: a specific instance beats a general claim, and an instance where something was contested beats one where it was easy. Interviewers discount unopposed success because it does not distinguish leadership from luck.
The four failure modes, and their fixes
| Failure | Why it happens | Fix |
|---|---|---|
| Running long | You are enjoying the story and cannot feel the clock | Practise with a timer. Two minutes is much shorter than it feels. |
| No number | The number felt like bragging so you left it out | The number is the evidence. Without it the story is an assertion. |
| We instead of I | Genuine modesty, or a team culture that discouraged it | Say what the team did in one sentence, then what you did for the rest. Interviewers cannot hire a team. |
| Org chart preamble | The story does not make sense without context, so you add context | If it needs three sentences of setup, start at the problem instead and let them ask. |
Handling the questions candidates dread
"Why are you looking?" or "Why are you available?" One flat sentence, then stop. Whatever the true reason, state it without emotion and without editorialising about the former employer. Every additional sentence makes it worse, and the silence afterwards is not an invitation to keep talking.
"What is your biggest weakness?" A real one, with the compensating mechanism. "I under-communicate progress on long tasks, so I now post a short written update at a fixed cadence whether or not there is news." Real weakness, specific mechanism, no self flagellation.
"Where do you see yourself in five years?" Answer in terms of problems rather than titles. Titles vary by company and guessing wrong sounds either unambitious or entitled.
"Do you have any questions for us?" Always yes, and never about compensation in a technical round. See below.
Questions to ask, which are graded
- "What does the data platform cost, and who watches that number?" Almost nobody asks this and it lands every time.
- "Where does a metric definition live, and what happens when two teams disagree about one?"
- "What is the on-call rotation, and what was the last serious incident?"
- "What would you want the person in this role to have changed in six months?"
- "What is the split between building new pipelines and maintaining existing ones?" The honest answer to this predicts your job satisfaction better than anything else you can ask.
Do not ask about growth, culture, or work life balance in a technical round. Ask the recruiter, where the question belongs and the answer is more honest.
Say what the team did in one sentence, then what you did for the rest, and land a number. Interviewers cannot hire a team.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Traps, rapid fire and three mock rounds
Twenty four things that do not error, forty facts, and three timed rounds with the rubric visible.
Twenty four traps
An error teaches you. A trap does not: the query runs, the pipeline is green, and something is quietly wrong or quietly expensive. Interviewers love these because they are not learnable from documentation, only from having been burned.
- What actually happens
- The scan happens and the result is truncated afterwards.
- What it costs
- Full scan price for ten rows of output.
- The fix
- Partition filter, or the free table preview.
- Module
- 04
- What actually happens
-
WHERE DATE(ts)='2026-08-21'cannot be proven to select one partition, so all are scanned. - What it costs
- Up to 1000x on three years of daily partitions.
- The fix
- Half open range on the raw column.
- Module
- 04
- What actually happens
- Clustering is prefix ordered; a filter on only the second column prunes nothing.
- What it costs
- You believe you have an index and you have nothing.
- The fix
- Order by filter frequency; verify with total_bytes_billed, not the dry run.
- Module
- 04
- What actually happens
- A grant of 200 slots for a burst is billed for the whole allocation window.
- What it costs
- Many short concurrent queries, exactly a BI workload, is the worst shape.
- The fix
- Opt into fluid scaling for per second with no minimum, or move BI to a materialized view or BI Engine.
- Module
- 05
- What actually happens
- The pricing page labels insertAll as Storage Write API (REST), so teams think they are on the modern API.
- What it costs
- $0.05 per GiB with no free tier versus $0.025 with 2 TiB free.
- The fix
- Move to gRPC. Check the SKU on the bill, not the name in the code.
- Module
- 05
- What actually happens
- There is no exactly once option on export subscriptions.
- What it costs
- Duplicate rows found by finance reconciling totals.
- The fix
- Keep Dataflow in the path, or deduplicate in BigQuery on a business key.
- Module
- 07
- What actually happens
- Each pane holds the whole window result; appending sums the same data repeatedly.
- What it costs
- Metrics inflated by the number of panes fired.
- The fix
- Match mode to sink: ACCUMULATING with overwrite, DISCARDING with sum.
- Module
- 06
- What actually happens
- 48 hours of lateness holds per key state for 48 hours after every window closes.
- What it costs
- A hard 60 GB per key ceiling under Streaming Engine, not a slowdown.
- The fix
- Bound it, and reconcile very late data in a daily batch over the archive.
- Module
- 19
- What actually happens
- One slow or empty source partition holds the watermark back; throughput looks normal.
- What it costs
- Output stops advancing while every dashboard says green.
- The fix
- Alert on data freshness, now minus watermark. Not on job state.
- Module
- 06
- What actually happens
- Anything outside an operator runs on every scheduler parse, every few seconds, for every DAG.
- What it costs
- An API rate limit at 3am with no failed task to point at.
- The fix
- Everything expensive goes inside an operator or a callable.
- Module
- 08
- What actually happens
- The task appends; a retry appends again. Airflow retried it once at 4am.
- What it costs
- Silent duplication in exactly one partition, found weeks later.
- The fix
- Partition decorator with WRITE_TRUNCATE, replaceWhere on Delta, or delete plus insert in a transaction.
- Module
- 08
- What actually happens
- Removes files that concurrent long readers still reference, and destroys the time travel window.
- What it costs
- A running job fails with file not found, or a recovery is impossible.
- The fix
- Leave it at 7 days unless you understand both consequences.
- Module
- 09
- What actually happens
- A MERGE whose predicate touches every partition rewrites the table.
- What it costs
- A nightly dimension MERGE that grows until it does not finish in its window.
- The fix
- Include a partition or clustering predicate to bound the rewrite, or use append only with a current view.
- Module
- 09
- What actually happens
- Higher DBU rate than a job cluster, and it idles.
- What it costs
- Steady DBU consumption outside working hours, which is the giveaway on the bill.
- The fix
- Job clusters for anything scheduled, auto termination mandatory on all purpose, cluster policies to enforce it.
- Module
- 11
- What actually happens
- It carries a DBU multiplier and falls back on Python UDF and RDD heavy work.
- What it costs
- A job that finishes twice as fast at more than twice the rate has cost you money.
- The fix
- Evaluate per workload on DBUs times rate plus VM hours, not wall clock.
- Module
- 11
- What actually happens
- Billing has a 60 second minimum on every resume, and suspending clears the local disk cache.
- What it costs
- Paying the minimum repeatedly and serving every query cold.
- The fix
- Look at the query arrival pattern. For bursty interactive use a longer suspend is often cheaper.
- Module
- 13
- What actually happens
- Permanent carries up to 90 days Time Travel plus 7 days Fail-safe you cannot turn off.
- What it costs
- On a high churn staging table the tail can be several times the table itself.
- The fix
- Transient for anything rebuildable. One of the highest value single changes in a Snowflake estate.
- Module
- 12
- What actually happens
- Not consumed within the source table's retention period, and it cannot be read at all.
- What it costs
- A quiet CDC outage with a recreate and a reconciliation gap to explain.
- The fix
- Monitor staleness. Use SYSTEM$STREAM_HAS_DATA so a task does not burn credits on nothing.
- Module
- 14
- What actually happens
- Reclustering runs continuously as data changes, billed in credits.
- What it costs
- The reclustering cost exceeds the query saving.
- The fix
- Defining a clustering key is a cost decision. Check SYSTEM$CLUSTERING_INFORMATION first.
- Module
- 12
- What actually happens
- A per file overhead on top of compute, and tiny files load inefficiently.
- What it costs
- Expensive twice, and it gets worse as the file count grows.
- The fix
- Compact before loading toward 100 to 250 MB compressed, not after.
- Module
- 14
- What actually happens
-
WHERE updated_at > MAX(updated_at)skips rows whose updated_at is older than the current max. - What it costs
- A quiet undercount that correlates with whichever source is laggiest.
- The fix
- Lookback window plus a unique_key with merge strategy so reprocessed rows update rather than duplicate.
- Module
- 16
- What actually happens
- A message id or offset identifies a delivery, not a business event.
- What it costs
- Replay yesterday's file and every event gets a new id, so the dedup misses all of it.
- The fix
- Key on a business idempotency key. If the source has none, that is a producer conversation.
- Module
- 19
- What actually happens
- Cache requires byte identical SQL; BI tools inject timestamps, session ids and user filters.
- What it costs
- 10,000 views a day means 10,000 scans. Waiting for cache to help is waiting forever.
- The fix
- Materialized view, BI Engine, or a precomputed serving table.
- Module
- 05
- What actually happens
- Roles are additive and inherit downwards; granting at project level then restricting one dataset does not work.
- What it costs
- A sensitive dataset readable by everyone with project access.
- The fix
- Grant narrowly at dataset or table and add upward.
- Module
- 05
Rapid fire: forty facts, fifteen seconds each
Cover the right column. If you hesitate on any of these they are not ready. Ten minutes end to end.
| Prompt | Answer |
|---|---|
| BigQuery on demand rate | $6.25 per TiB, first 1 TiB free |
| BigQuery slot hour, three editions | $0.04 / $0.06 / $0.10 |
| Cheapest BigQuery slot hour | $0.036, Enterprise three year Resource CUD |
| Slot commitment minimum | 50 slots, 50 slot increments |
| Physical storage breakeven | 1.74x, higher once time travel counts |
| Long term storage trigger | 90 days unmodified, per partition |
| Max partitions per table | About 10,000 |
| Max clustering columns | Four, prefix ordered |
| Storage Write API gRPC | $0.025 per GiB, 2 TiB free |
| Legacy streaming inserts | $0.05 per GiB, no free tier |
| BigQuery result cache | 24 hours, byte identical SQL, unchanged tables |
| BigQuery time travel | 7 days default, 2 to 7 configurable |
| Dataflow max workers | 4,000 per job |
| Streaming Engine state ceiling | 60 GB per key |
| Dataflow default streaming mode | Exactly once |
| Dataflow alerting metric | Data freshness, now minus watermark |
| Pub/Sub throughput | $40 per TiB, 10 GiB free |
| Pub/Sub export subscriptions | $50 per TiB, no free tier |
| Pub/Sub max retention | 31 days |
| Pub/Sub exactly once | Pull subscriptions, single region only |
| Serverless Spark startup | 40 to 50 seconds |
| Composer 1 and 2.0.x end of life | 15 September 2026 |
| Delta OPTIMIZE target file size | Around 1 GB by default |
| Delta VACUUM default retention | 7 days |
| Liquid clustering versus Z-ORDER | Incremental, no rewrite, keys changeable |
| Change Data Feed column | _change_type |
| Databricks bills you | DBUs plus the underlying cloud VM cost |
| Job cluster versus all purpose | Lower DBU rate, and it terminates |
| Unity Catalog namespace | catalog.schema.table, metastore above |
| Databricks cost attribution | system.billing.usage joined to cluster tags |
| Auto Loader modes | Directory listing, or file notification for large directories |
| Snowflake micro-partition size | 50 to 500 MB uncompressed |
| Snowflake warehouse sizing | XS is 1 credit per hour, doubling per step |
| Snowflake billing granularity | Per second, 60 second minimum on resume |
| Snowflake Time Travel | 0 to 90 days Enterprise, 0 to 1 Standard |
| Snowflake Fail-safe | 7 days, fixed, not queryable |
| Transient table | 0 to 1 day time travel, no fail safe |
| A Snowflake Stream is | An offset, not a copy of the changes |
| Snowflake three caches | Result, local disk, metadata |
| Multi-cluster scaling policies | STANDARD favours latency, ECONOMY favours cost |
Three mock rounds
Start the session timer (space) before each. Answer out loud. Recording yourself is uncomfortable and it is the fastest way to find the rambling, because you will hear it and you cannot hear it live.
- Walk me through your background. Two minutes.
- You list several platforms. Why not one?
- Tell me about the largest system you have owned.
- How do you think about cost across three platforms?
- Why are you available?
- What are you looking for next?
| Signal | Passing looks like |
|---|---|
| Length control | The open is under two minutes and the scale number is in sentence one |
| The three platform answer | A boundary in one sentence, then the cost of the choice volunteered |
| Cost fluency | You name the unit on each platform without being prompted |
| Availability | One flat sentence, then silence |
| Minutes | What you should be doing |
|---|---|
| 0 to 5 | Restate. Two or three clarifying questions, then stop. Volume arithmetic out loud: 2 TB a day is 60 TB a month, about 730 TB a year. |
| 5 to 15 | Draw the happy path end to end, a reason per box. Do not optimise yet. |
| 15 to 30 | Take each of the three requirements and show where it is satisfied. Analytics, serving, reconciliation. |
| 30 to 40 | Failure modes, unprompted. Three ways your own design breaks and what you would do. |
| 40 to 50 | The cost conversation, in dollars, from the rate card. |
| 50 to 60 | Their follow ups. Expect 10x, a freshness change, and a compliance constraint. |
| Weak | Strong | |
|---|---|---|
| Clarifying | Names a service in the first minute, or asks eight questions | Two or three, then commits |
| Arithmetic | Talks about scale qualitatively | Does the division out loud and it shapes the design |
| Reasons | Boxes with names | Every box has a reason and a rejected alternative |
| Failure modes | Waits to be asked | Volunteers three before being asked |
| Cost | Mentions it if prompted | Ends there unprompted, in dollars |
| Correctness | Not discussed | Delivery guarantee named at every hop and where dedup lives |
- Pick one of your three platforms and go as deep as I want on it.
- The bill tripled with no traffic change. Find it.
- Tell me honestly when you would not use each of the three.
- What would you consolidate if you started again?
| Weak | Strong | |
|---|---|---|
| Depth | Even coverage across all three, all shallow | One platform at real depth with mechanism, not features |
| Method | Theorises about the cause | Names the system table on each platform and investigates in order |
| Controls | Suggests alerts | Distinguishes what notifies from what stops, per platform |
| Honesty | Defends all three | Names a genuine weakness in each, then what would change their mind |
| Consolidation | Says nothing would change | Names the trade explicitly and the mitigation they had |
Question 1 is the one that catches multi platform resumes. The interviewer is checking whether you have production depth on one or brochure depth on all three. Pick the one you can go deepest on, say you are picking it and why, and then go deep on mechanism: the Delta transaction log, or micro-partition immutability, or slot allocation. Feature lists are what shallow sounds like.
Grade yourself on these five, every time
| Habit | Test |
|---|---|
| Headline first | Was the answer in the first sentence? |
| Number early | Was the scale number in sentence one or two? |
| Reason per box | Could I defend every component against why not something else? |
| Failure modes unprompted | Did I name them before being asked? |
| Ended on cost | Did I get to dollars without prompting? |
Pick one platform and go deep on mechanism rather than features. A feature list across three platforms is what shallow sounds like, and it is exactly what a three platform resume is suspected of.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Master reference
Everything on one screen, plus your progress and the night before list.
Where you are
| Modules read | 0 |
| Checkpoints cleared | 0 |
| Practice reps logged | 0 |
| Hours banked |
0 |
Retention by module
Filled by retrieval, not by reading. A dark cell means the cards in that module are holding at a week or more.
The ledger
| # | Module | Read h | Rep h | Status |
|---|---|---|---|---|
Practice reps
These need no cloud account. They are the reps that actually move an interview outcome. Tick one when you have done it out loud, against a clock.
| Rep | Hours | |
|---|---|---|
| The two minute open, recorded and listened back | 0.5 | |
| All twelve story scaffolds, filled in and delivered against a two minute clock | 1.5 | |
| Draw the reference architecture (case 01) in five minutes, three times | 1.0 | |
| All twelve tri-lane comparators answered in recall mode | 1.0 | |
| Hand write all twelve SQL drills without autocomplete | 1.5 | |
| The resume audit: every technical noun marked own, study or cut | 1.0 | |
| Forty rapid fire facts under fifteen seconds each | 0.5 | |
| Mock round 2, cold, timed | 1.0 | |
| Mock round 2 again, after fixing what the first exposed | 1.0 | |
| Mock round 3, including the pick one platform and go deep question | 1.0 |
Every rate on one screen
| Line | Rate |
|---|---|
| BigQuery on demand | $6.25 per TiB, first 1 TiB per month free |
| BigQuery slot hour | Standard $0.04, Enterprise $0.06, Enterprise Plus $0.10 |
| Cheapest slot hour | $0.036, Enterprise three year Resource CUD |
| BigQuery active storage | about $0.023 logical, about $0.040 physical per GiB per month |
| Physical breakeven | compression above 1.74x, plus your time travel tail |
| Storage Write API gRPC | $0.025 per GiB, first 2 TiB free |
| Legacy streaming inserts | $0.05 per GiB, no free tier |
| Batch load into BigQuery | free |
| Pub/Sub throughput | $40 per TiB, first 10 GiB free |
| Pub/Sub export subscriptions | $50 per TiB, no free tier |
| Snowflake XS warehouse | 1 credit per hour, doubling per size step |
| Snowflake billing | per second, 60 second minimum per resume |
| Snowflake Time Travel | 0 to 90 days Enterprise, 0 to 1 Standard |
| Snowflake Fail-safe | 7 days, fixed, not queryable |
| Databricks | DBUs by workload and tier, plus the cloud VM cost |
| Delta VACUUM retention | 7 days default |
| Delta OPTIMIZE target | around 1 GB files |
The twelve comparators, compressed
| Axis | BigQuery | Databricks | Snowflake |
|---|---|---|---|
| Compute control | None, slots are hidden | All of it, node level | One dial, T-shirt sizes |
| Cost unit | Bytes or slot hours | DBUs plus VMs | Credits per hour |
| Pruning | Declared partition and cluster | Liquid clustering or Z-ORDER | Automatic micro-partitions |
| ACID mechanism | Native, or Iceberg on BigLake | Delta transaction log | Immutable micro-partitions |
| Row based streaming | Storage Write API | Structured Streaming | Snowpipe Streaming |
| Governance | IAM plus Dataplex policy tags | Unity Catalog | RBAC role hierarchy |
| ML | BigQuery ML plus Vertex AI | Strongest | Snowpark, improving |
| Time travel | 7 days | VACUUM bounded, 7 days | Up to 90 days |
| Sharing | Analytics Hub | Delta Sharing, open | Secure Data Sharing, tightest |
| Open format | Iceberg supported | Delta is native and open | Native is proprietary |
| Concurrency | Slot queueing | More clusters | Multi-cluster, cleanest |
| Audience | Analysts | Engineers and ML | Finance and executives |
Cost formulas
BigQuery on demand = TiB_scanned x 6.25 (first 1 TiB free)
BigQuery capacity = slot_hours x edition_rate (per second, 1 min min)
BigQuery reserve above = monthly_TiB > (slots x rate x 730) / 6.25
Snowflake = credits_per_hour x hours (XS=1, doubling per size)
Snowflake size up = cost neutral while the query still scales linearly
Databricks = DBUs x DBU_rate + VM_hours x VM_rate
Name index, current as of 2026
| Say this | Not this |
|---|---|
| Lakehouse for Apache Iceberg | BigLake |
| Lakehouse runtime catalog | BigLake metastore |
| Knowledge Catalog | Dataplex Universal Catalog, Data Catalog |
| Managed Service for Apache Spark | Dataproc, Dataproc Serverless |
| Managed Service for Apache Airflow | Cloud Composer |
| Managed Airflow Gen 3 | Composer 3 |
| Sensitive Data Protection | Cloud DLP |
| Pub/Sub or Managed Service for Apache Kafka | Pub/Sub Lite (shut down March 2026) |
| Editions and slot commitments | flat rate, Flex Slots |
Older names still appear on most resumes and job descriptions, which is fine on paper because recruiters and applicant tracking systems search for them. In the room, use the current name and add the old one in parentheses once. Do not correct an interviewer who uses the old name; mirror their vocabulary and use the current one for your own statements.
About this guide
Self contained. One HTML file, no build step, no account, no network calls except the web fonts. All progress, spaced repetition scheduling and the error log live in this browser's local storage only. Nothing is sent anywhere. Use Export before clearing site data or switching machines, and Import to restore.
Prices, limits and product names are current as of the 2026 rate cards and release notes and will drift. Verify any figure you intend to quote in an interview against the vendor pricing page on the day. Where a claim was uncertain it carries a confidence tag rather than a confident assertion.
The guide assumes you are preparing for a data engineering role that names Google Cloud, and possibly Databricks or Snowflake alongside it. It does not assume you have run any of them in production; module 01 covers the honest framing if you have not.
The night before
- The forty rapid fire facts. Ten minutes.
- The name index above. Two minutes. Cheapest credibility available.
- The two minute open, out loud, twice.
- The why three platforms answer, plus the consolidation answer.
- Your scale numbers, derived out loud.
- Case 01, drawn on paper from memory.
- Stop. Sleep is worth more than the seventh hour of revision.
Cost, correctness, and the boundary between the three platforms. If I have those and I can go deep on one engine's mechanism, I can hold any round in this loop.
Retrieval cards
Answer out loud, then open and grade honestly. Grading is what schedules the next review, so grading everything Easy just deletes the feature.
Unlocks when you mark the module read. Tick only what you can actually do out loud.
Warm up and error log
Ten minutes of interleaved retrieval across every module, and a place to record the assumptions that broke.
Interleaved warm up
Pulls up to ten due cards, round robin across modules so no two consecutive cards come from the same one. Interleaving is harder than blocking, which is why it works.
Press build to pull what is due.
Error log
Record what you got wrong, but state the broken assumption before the fix. A log of corrections teaches you nothing. A log of the beliefs that turned out false rewires how you predict.