</> MAANG.io

Expert

Design landmark systems in depth, from Dynamo, GFS, Bigtable and Kafka to payments and ads pipelines.

0 of 232 solved 0%

Section 5: What Must Survive, What Can Be Forgotten - Data Modeling

Executive Summary: Section 4's calls create about a dozen kinds of state, and the modeling move that matters is sorting them into two bins. Durable truth (accounts, functions, immutable versions, aliases, code chunks, accepted async events, billing) lives in replicated stores and is never lost. Soft state (which sandboxes exist, which are idle, who holds a lease) lives in memory and is rebuilt from the workers in seconds. We partition metadata by account, the async queue by function, and placement by function version, and we make versions and code content-addressed and immutable so that no cache on the hot path ever needs invalidating.

The interviewer taps the PublishVersion and Invoke calls still on the board from Section 4: "Every one of these writes something somewhere. What's in the tables, and where do the tables live?"

"Before the schema, one sorting question: if this disappeared right now, would a customer lose something they can't get back? That splits the platform in two and decides nearly every storage choice."

The Entities

accounts            (account_id PK, cell_id, concurrency_limit = 1000, unreserved_floor)
functions           (account_id, function_name, latest_config {runtime, handler, memory,
                     timeout, env_ciphertext, role}, latest_code_digest)     -- "$LATEST", mutable
versions            (account_id, function_name, version_no,                  -- immutable once published
                     code_digest, runtime, handler, memory_mb, timeout_s,
                     env_ciphertext, role, config_hash)
aliases             (account_id, function_name, alias_name,
                     routing [{version_no, weight}])                         -- a pointer, 1 or 2 targets
concurrency_config  (account_id, function_name, reserved,
                     provisioned [{qualifier, count}])
event_invoke_config (account_id, function_name, qualifier, max_retries = 2,
                     max_event_age_s <= 21600, on_failure_destination)
event_source_maps   (mapping_id, account_id, function_qualifier, source_arn,
                     batch_size, checkpoint {shard -> sequence},
                     state: enabled|disabled|failing)
code_manifests      (code_digest PK, [chunk_hash...], size)                  -- content-addressed
code_chunks         (chunk_hash PK, encrypted_blob, key_ref)                 -- content-addressed
async_events        (event_id, function_qualifier, payload <= 256 KB,
                     attempt, next_attempt_at, first_accepted_at)
idempotency_keys    (account_id, function_name, idempotency_key PK, event_id,
                     expires_at = accepted + 6 h)
billing_rollups     (account_id, function_version, memory_mb, minute,
                     invocations, billed_ms)                                 -- 1 ms increments
sandboxes (SOFT)    (sandbox_id, host_id, az, function_version, state,
                     lease_holder, lease_expires_at, last_used_at)

Data model: an account owns functions; a function publishes immutable versions, is configured by concurrency and event invoke config, and is invoked by event source mappings and targeted by async events; aliases route weighted traffic to versions, which point to a code manifest of code chunks and are run by sandboxes and metered by billing rollups

Here is Tidewater in those rows. checkout-api has a functions row holding whatever was last uploaded, and a versions row for version 42: Node.js 22, 1,024 MB, the digest of a 25 MB zip, and encrypted env vars. Alias live holds [{42, 1.0}]. During Section 4's canary it held [{42, 0.90}, {43, 0.10}]. concurrency_config carries the provisioned 3,000 that gets warmed at 08:55. thumbnail-maker's event_invoke_config says 2 retries, 6 h max age, and a dead-letter queue as the on-failure destination.

Three details carry more weight than they look:

  • A version is a frozen tuple. Code digest plus every setting that changes behaviour, hashed into config_hash. Nothing about version 42 can change after publish, which is why rollback is just repointing live, and why provisioned concurrency targets a version or alias, never $LATEST.
  • An alias holds no data of its own. It is a pointer with at most two weighted targets. Alias moves are the main thing the config change stream carries.
  • An async event carries first_accepted_at, not just attempt. The 6 h max age is measured from acceptance. A throttled event can be retried many times, but it can never outlive its age limit.

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.