The bidirectional lookup requirement is what tripped me up first.
Start by clarifying the core entities and relationships (users, doors, permissions), then design a normalized data model that supports efficient bidirectional lookups. Layer on API design, indexing strategy, and authorization controls before addressing cross-cutting concerns like audit logging and future operations such as revocation and time-based access.
Pro tip: Since Verkada is a physical security company, demonstrate domain awareness by discussing real-world constraints like time-based access windows (e.g., an employee can only enter between 9am–6pm), door groups or zones, and the difference between online and offline door controllers that may need to cache permissions locally.
Identify the primary entities — Users, Doors, Roles, and AccessGrants — and model their relationships. Use a junction table (e.g., access_grants) with foreign keys to user and door, plus metadata fields like granted_by, granted_at, expires_at, and is_active to support future operations.
Define RESTful endpoints covering the three core queries: GET /users/{userId}/doors (doors a person can open), GET /doors/{doorId}/users (people who can open a door), and POST /doors/{doorId}/grants (admin grants access). Include endpoints for revoking access (DELETE or PATCH) and listing audit logs.
Add a composite index on (user_id, is_active) for user-to-door lookups and a composite index on (door_id, is_active) for door-to-user lookups. Consider a covering index that includes expires_at to efficiently filter time-based access without additional table scans.
Implement RBAC where only users with an Admin or DoorManager role can call grant/revoke endpoints, enforced via middleware. Consider scoped permissions so a building manager can only manage doors within their assigned facility, preventing privilege escalation.
Append every grant, revoke, and access-check event to an immutable audit_log table with actor, action, target, timestamp, and IP address. Design the schema to support future features like time-windowed access, group-based permissions, and bulk revocation (e.g., when an employee is offboarded) by keeping grants soft-deletable and queryable.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
10 million devices pinging once per minute is roughly 167k writes per second.
Start by quantifying the scale (10M devices × 1 signal/min = ~167K writes/sec) to anchor all architectural decisions in concrete numbers. Then walk through the system layer by layer — ingestion, storage tiering, online/offline detection logic, and dashboard APIs — explicitly calling out trade-offs at each stage. Ground your design in Verkada's domain: reliability and real-time visibility are critical for physical security infrastructure.
Pro tip: Proactively address the 'thundering herd' problem — if all 10M devices reconnect simultaneously after a network partition, your ingestion layer must handle a massive spike without cascading failures. Mentioning this edge case signals you think about failure modes at scale, not just the happy path.
Calculate write throughput (~167K writes/sec sustained, with burst headroom of 2-3x), define SLOs for staleness tolerance (e.g., a device is 'offline' if no heartbeat in 2-3 minutes), and clarify dashboard latency requirements (near real-time vs. eventual consistency).
Use a horizontally scalable message queue (e.g., Kafka with partitioning by device_id) to decouple producers from consumers, absorb traffic spikes, and provide replay capability. Lightweight edge agents or load balancers handle initial device connections before publishing to the queue.
Store recent heartbeat state (last seen timestamp per device) in a low-latency key-value store like Redis or DynamoDB for O(1) online/offline lookups. Persist historical heartbeat data to a time-series database (e.g., InfluxDB, TimescaleDB) or columnar store (e.g., Cassandra, S3+Parquet) for trend analysis and auditing.
Use a stream processing layer (e.g., Kafka Streams, Flink) to consume heartbeats and update a 'last_seen' timestamp per device; a separate sweeper service or TTL-based expiry flags devices as offline after a configurable threshold (e.g., 2 missed heartbeats). Avoid polling the full device table — instead, use event-driven state transitions to minimize unnecessary work.
Expose paginated REST or GraphQL APIs backed by the hot store for current device status, with WebSocket or SSE push for real-time updates on status changes. For failures, discuss consumer group rebalancing in Kafka, Redis replication/sentinel for HA, circuit breakers on downstream services, and graceful degradation (e.g., serving stale status with a staleness indicator rather than returning errors).
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.