feast-dev / feast-dev/feast

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

Open
#6,819 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
7.3k
Forks
1.4k
Avg merge
1d 21h
Merged PRs (30d)
15

Description

## 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.

Contributor guide

Open the contributing guide

Research direction

Start with the empty-input reproductions, then read sdk/python/feast/infra/online_stores/online_store.py at lines 169 and 453 and sdk/python/feast/utils.py around _validate_entity_values and _get_unique_entities. Done means empty supplied join-key values return an empty result with feature metadata, while no supplied keys produces a typed HTTP 400 naming the missing key.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design, data-engineering
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.