#6819·feast

Online requests with zero entity rows return HTTP 500 instead of an empty result or a 400

Author: Daksha1611Created Sep 7, 2026Updated Sep 7, 2026

Expected Behavior

An online request carrying zero entity rows should be answered, not crash. A caller whose upstream query matched nothing this run sends an empty batch, and a request that genuinely omits a required join key should come back as a client error (4xx) naming the key.

Current Behavior

Three related empty-input shapes surface internal exceptions, and the feature server turns each into an HTTP 500 with an opaque message:

Request Result
store.get_online_features(features=[...], entity_rows=[]) IndexError: list index out of range
POST /get-online-features with {"entities": {"driver_id": []}} HTTP 500 — "Missing join key values for keys: []. No values provided for keys: ['driver_id']. Provided join_key_values: ['driver_id']"
POST /get-online-features with {"entities": {}} HTTP 500 — "'pop from an empty set'"

The middle one is the worst of the three: the join key was supplied, it just has no values, so this is a well-formed request that should return an empty result.

The same request shape with a single entity row returns 200.

Steps to reproduce

Against a local file + sqlite repo:

python
fs.get_online_features(features=["driver_stats:conv_rate"], entity_rows=[]).to_dict()
# IndexError: list index out of range

And through the server:

python
client = TestClient(get_app(fs), raise_server_exceptions=False)

client.post("/get-online-features", json={
    "features": ["driver_stats:conv_rate"],
    "entities": {"driver_id": []},
})
# 500 "Missing join key values for keys: []. No values provided for keys: ['driver_id']..."

client.post("/get-online-features", json={
    "features": ["driver_stats:conv_rate"],
    "entities": {},
})
# 500 "'pop from an empty set'"

Specifications

  • Version: master @ 5ad5592390febfca60c9d88edf7daccbdd156fd6
  • Platform: Linux x86_64, Python 3.11.15
  • Subsystem: online serving / feature server

Possible Solution

Three distinct causes:

  • sdk/python/feast/infra/online_stores/online_store.py:169 and :453 (the sync and async paths) both build the columnar dict with {k: [] for k in entity_rows[0].keys()}, with no guard for an empty list.
  • sdk/python/feast/utils.py:554 _validate_entity_values ends with set_of_row_lengths.pop(). For an empty mapping the set is empty and .pop() raises KeyError: 'pop from an empty set'.
  • sdk/python/feast/utils.py _get_unique_entities treats "join key present but holding zero values" the same as "join key never supplied". The row-wise conversion immediately below it already handles zero rows (if not rowise: return (), (), 0), so the guard is rejecting a case the code beneath it supports.

A reasonable contract, and the one I've implemented in the linked PR:

  • join keys supplied but empty → empty response with the correct feature-name metadata, consistent with get_historical_features returning an empty frame for a zero-row entity_df;
  • nothing supplied at all → a typed client error carrying HTTP 400 rather than a 500.

I went with an empty response rather than a 400 for the empty-but-present case because it keeps batch-scoring callers from having to special-case "my filter matched nothing". Happy to switch it to a 400 if maintainers prefer the stricter reading — it is a one-line change either way.