Katkat

Search Recorded Metrics by Name, Tags, and Recency

VercelSoftware Engineer · Onsite

Last updated

Design an in-memory metric storage and querying system that allows recording time-series metrics with key-value tags and retrieving the most recent matches filtered by name and tags.

Problem Statement

You are tasked with building an in-memory metric storage engine, MetricStore, to collect runtime statistics from serverless deployments and query them efficiently.

Each recorded metric consists of:

  • name: A string identifier for the metric (e.g., "http_requests").
  • value: A floating-point measurement.
  • tags: A dictionary of key-value string pairs representing metadata (e.g., {"region": "iad1", "status": "200"}).
  • timestamp: An integer representing the epoch timestamp in milliseconds.

Implement the MetricStore class with two methods:

  1. record(name: str, value: float, tags: dict[str, str], timestamp: int) -> None

    • Stores the metric event. Multiple events can share the same name, timestamp, and tags.
  2. query(name: str, tags: dict[str, str], limit: int) -> list[dict]

    • Returns up to limit recorded metrics that match the given name and contain all key-value pairs specified in tags (i.e., query tags must be a subset of the metric's tags; an empty query tags dictionary matches all tag sets).
    • Results must be sorted primarily by timestamp in descending order (most recent first).
    • In case of timestamp ties, break ties by insertion order descending (the metric recorded later appears first).
    • Each returned metric should be represented as a dictionary: {"name": str, "value": float, "tags": dict[str, str], "timestamp": int}.

Example

store = MetricStore()

store.record("cpu_usage", 75.5, {"env": "prod", "host": "a"}, 1000)
store.record("cpu_usage", 82.0, {"env": "prod", "host": "b"}, 1020)
store.record("cpu_usage", 40.0, {"env": "dev", "host": "c"}, 1050)
store.record("cpu_usage", 90.0, {"env": "prod", "host": "a"}, 1020)

# Query cpu_usage for prod environment, limit 2
results = store.query("cpu_usage", {"env": "prod"}, 2)
# Explanation:
# Matches: host 'b' at 1020, host 'a' at 1020, host 'a' at 1000.
# Both host 'b' and host 'a' have timestamp 1020; host 'a' was recorded after host 'b',
# so host 'a' comes first in the tiebreak.
# Output:
# [
#   {"name": "cpu_usage", "value": 90.0, "tags": {"env": "prod", "host": "a"}, "timestamp": 1020},
#   {"name": "cpu_usage", "value": 82.0, "tags": {"env": "prod", "host": "b"}, "timestamp": 1020}
# ]

Constraints

  • name is a non-empty string consisting of alphanumeric characters and underscores (1 <= len(name) <= 100).
  • tags contains between 0 and 20 key-value string pairs.
  • 1 <= timestamp <= 2^63 - 1. Timestamps are not guaranteed to arrive in strictly chronological order.
  • 1 <= limit <= 1000.
  • Total calls to record and query: up to 50,000.

Edge Cases

  • Empty query tags: should match any metric with the specified name.
  • limit greater than the number of matching metrics: return all matches.
  • Metrics recorded with identical name, tags, timestamp, and value should still be treated as distinct events.

Hints

Hint 1

Grouping metrics by `name` is a natural first step to avoid scanning unrelated metrics during a query.

Hint 2

For tag matching, check whether every `(key, value)` pair in the query dictionary exists and matches in the metric's tags dictionary.

Hint 3

Track an auto-incrementing insertion ID for each recorded metric so tiebreaks on identical timestamps are easily resolved.

Follow-up questions

  • How would you optimize queries if the number of metrics per name is large (e.g., millions) and querying with specific tags is frequent?
  • If timestamps arrive roughly in chronological order, how could you optimize the query to avoid sorting all historical metrics matching a name?
  • How would you implement a retention policy that purges metrics older than a fixed time-to-live (TTL) without degrading query latency?