Skip to content

Commit 4610d55

Browse files
pmcfadinclaude
andcommitted
fix: bound per-day fetch limit to MAX_ACTIVITY_ROWS // 30 (closes #13)
Previously per_day_limit was set to MAX_ACTIVITY_ROWS (1000), allowing up to 30 x 1000 = 30,000 rows to be fetched across all 30 partitions before the post-gather trim. Changed to max(1, MAX_ACTIVITY_ROWS // 30) (~33) so the total rows fetched stays bounded at ~MAX_ACTIVITY_ROWS. Added test_list_user_activity_per_day_limit_is_bounded to assert that find() is called with the smaller per-partition limit for all 30 partitions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0a2e6e5 commit 4610d55

2 files changed

Lines changed: 37 additions & 3 deletions

File tree

app/services/user_activity_service.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ async def list_user_activity(
122122
for delta in range(30)
123123
]
124124

125-
# Run all 30 partition queries concurrently; pass per-day limit to avoid
126-
# pulling more rows than we could ever display.
127-
per_day_limit = MAX_ACTIVITY_ROWS
125+
# Run all 30 partition queries concurrently; divide the total cap evenly
126+
# across all partitions so that 30 x per_day_limit stays bounded at
127+
# ~MAX_ACTIVITY_ROWS rather than 30 x MAX_ACTIVITY_ROWS.
128+
per_day_limit = max(1, MAX_ACTIVITY_ROWS // 30)
128129

129130
results: List[List[dict]] = await asyncio.gather(
130131
*[

tests/services/test_user_activity_service.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
list_user_activity,
1111
ANONYMOUS_USER_ID,
1212
USER_ACTIVITY_TABLE_NAME,
13+
MAX_ACTIVITY_ROWS,
1314
)
1415
from app.models.user_activity import UserActivity
1516

@@ -362,6 +363,38 @@ async def test_record_user_activity_each_activity_type():
362363
assert doc["activity_type"] == activity_type
363364

364365

366+
@pytest.mark.asyncio
367+
async def test_list_user_activity_per_day_limit_is_bounded():
368+
"""list_user_activity passes a per-partition limit of MAX_ACTIVITY_ROWS//30, not MAX_ACTIVITY_ROWS.
369+
370+
With 30 partitions the naive limit would allow up to 30 x MAX_ACTIVITY_ROWS rows
371+
to be fetched before the post-gather trim. The bounded limit keeps the total
372+
near MAX_ACTIVITY_ROWS.
373+
"""
374+
captured_limits: list[int] = []
375+
376+
def mock_find(filter=None, limit=None, **kwargs):
377+
if limit is not None:
378+
captured_limits.append(limit)
379+
cursor = AsyncMock()
380+
cursor.to_list = AsyncMock(return_value=[])
381+
return cursor
382+
383+
mock_table = AsyncMock()
384+
mock_table.find = mock_find
385+
386+
await list_user_activity(userid=uuid4(), page=1, page_size=10, db_table=mock_table)
387+
388+
expected_limit = max(1, MAX_ACTIVITY_ROWS // 30)
389+
assert len(captured_limits) == 30, "Expected find() to be called for all 30 partitions"
390+
assert all(
391+
lim == expected_limit for lim in captured_limits
392+
), f"All per-day limits should be {expected_limit}, got: {set(captured_limits)}"
393+
# Confirm the per-day limit is strictly less than MAX_ACTIVITY_ROWS so that
394+
# 30 partitions cannot return more than ~MAX_ACTIVITY_ROWS rows total.
395+
assert expected_limit < MAX_ACTIVITY_ROWS
396+
397+
365398
@pytest.mark.asyncio
366399
async def test_list_user_activity_page_beyond_data():
367400
"""Requesting a page beyond available data returns empty list with correct total."""

0 commit comments

Comments
 (0)