perf: batch notification snapshot maintenance (#439)
This commit is contained in:
1 parent
74d1435ca7
commit
0a3fb180ed
5 files changed
+124
-27
No files matched your search
@@ -217,13 +217,16 @@ class LiveSnapshotStore:
|
||||
connection.commit()
|
||||
return self.load()
|
||||
|
||||
def remove_notification(self, notification_id: int) -> LiveSnapshotState:
|
||||
"""Filter a confirmed read from every worker and subsequent stale refresh."""
|
||||
def remove_notifications(self, notification_ids: Iterable[int]) -> LiveSnapshotState:
|
||||
"""Filter confirmed reads from every worker and subsequent stale refreshes."""
|
||||
read_ids = set(notification_ids)
|
||||
if not read_ids:
|
||||
return self.load()
|
||||
with self._connect() as connection:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
connection.execute(
|
||||
connection.executemany(
|
||||
"INSERT OR IGNORE INTO live_read_notification VALUES (?)",
|
||||
(notification_id,),
|
||||
((notification_id,) for notification_id in read_ids),
|
||||
)
|
||||
row = connection.execute(
|
||||
"SELECT value_json, revisions_json FROM live_snapshot WHERE singleton = 1"
|
||||
@@ -233,8 +236,9 @@ class LiveSnapshotStore:
|
||||
if value is not None and isinstance(value.get("notifications"), list):
|
||||
previous = value["notifications"]
|
||||
retained = [
|
||||
item for item in previous
|
||||
if not isinstance(item, dict) or item.get("id") != notification_id
|
||||
item
|
||||
for item in previous
|
||||
if not isinstance(item, dict) or item.get("id") not in read_ids
|
||||
]
|
||||
if retained != previous:
|
||||
value["notifications"] = retained
|
||||
@@ -249,6 +253,10 @@ class LiveSnapshotStore:
|
||||
connection.commit()
|
||||
return self.load()
|
||||
|
||||
def remove_notification(self, notification_id: int) -> LiveSnapshotState:
|
||||
"""Filter one confirmed read from every worker and subsequent stale refresh."""
|
||||
return self.remove_notifications([notification_id])
|
||||
|
||||
def release_refresh(self, owner: str) -> None:
|
||||
"""Relinquish a lease that became unnecessary after a coherent recheck."""
|
||||
with self._connect() as connection:
|
||||
|
||||
+23
-21
@@ -1735,30 +1735,30 @@ def _live_snapshot_payload(
|
||||
return payload
|
||||
|
||||
|
||||
def _remove_notification_from_live_snapshot(thread_id: int) -> None:
|
||||
async def _remove_notifications_from_live_snapshot(thread_ids: list[int]) -> None:
|
||||
global _live_snapshot_value, _read_notification_ids
|
||||
_read_notification_ids = _read_notification_ids | {thread_id}
|
||||
read_ids = set(thread_ids)
|
||||
if not read_ids:
|
||||
return
|
||||
_read_notification_ids = _read_notification_ids | read_ids
|
||||
if _live_snapshot_value is not None:
|
||||
retained_notifications = _live_snapshot_value.get("notifications")
|
||||
if isinstance(retained_notifications, list):
|
||||
updated = dict(_live_snapshot_value)
|
||||
updated["notifications"] = [
|
||||
notification
|
||||
for notification in retained_notifications
|
||||
if not isinstance(notification, dict)
|
||||
or notification.get("id") not in read_ids
|
||||
]
|
||||
if updated["notifications"] != retained_notifications:
|
||||
_live_section_revisions["notifications"] += 1
|
||||
_live_snapshot_value = updated
|
||||
try:
|
||||
shared = _live_snapshot_store.remove_notification(thread_id)
|
||||
if shared.value is not None or _live_snapshot_value is None:
|
||||
_apply_shared_live_state(shared)
|
||||
await asyncio.to_thread(_live_snapshot_store.remove_notifications, read_ids)
|
||||
except (OSError, sqlite3.Error):
|
||||
# The upstream mutation already succeeded; keep process-local filtering.
|
||||
pass
|
||||
if _live_snapshot_value is None:
|
||||
return
|
||||
retained_notifications = _live_snapshot_value.get("notifications")
|
||||
if not isinstance(retained_notifications, list):
|
||||
return
|
||||
updated = dict(_live_snapshot_value)
|
||||
updated["notifications"] = [
|
||||
notification
|
||||
for notification in retained_notifications
|
||||
if not isinstance(notification, dict) or notification.get("id") != thread_id
|
||||
]
|
||||
if updated["notifications"] != retained_notifications:
|
||||
_live_section_revisions["notifications"] += 1
|
||||
_live_snapshot_value = updated
|
||||
|
||||
|
||||
def _without_read_notifications(snapshot: dict) -> dict:
|
||||
@@ -1986,7 +1986,6 @@ async def _mark_notification_read_result(thread_id: int) -> tuple[int, bool]:
|
||||
)
|
||||
except Exception:
|
||||
return thread_id, False
|
||||
_remove_notification_from_live_snapshot(thread_id)
|
||||
return thread_id, True
|
||||
|
||||
|
||||
@@ -2016,6 +2015,9 @@ async def read_notifications(batch: NotificationReadBatch) -> JSONResponse:
|
||||
for thread_id, succeeded in [task.result()]
|
||||
if succeeded
|
||||
}
|
||||
await _remove_notifications_from_live_snapshot(
|
||||
[thread_id for thread_id in thread_ids if thread_id in succeeded_ids]
|
||||
)
|
||||
failed = [thread_id for thread_id in thread_ids if thread_id not in succeeded_ids]
|
||||
return JSONResponse(
|
||||
{
|
||||
@@ -2116,7 +2118,7 @@ async def read_notification(thread_id: int = PathParam(gt=0)) -> JSONResponse:
|
||||
{"error": "The update could not be marked read. Please retry."},
|
||||
status_code=503,
|
||||
)
|
||||
_remove_notification_from_live_snapshot(thread_id)
|
||||
await _remove_notifications_from_live_snapshot([thread_id])
|
||||
return JSONResponse({"id": thread_id, "status": "read"})
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user