HrishiKabra commited on
Commit
cdce81b
·
verified ·
1 Parent(s): f8dd35f

resilient supabase writes (lock+retry) + persist result_json

Browse files
Files changed (2) hide show
  1. backend/jobs.py +7 -1
  2. backend/persistence.py +31 -10
backend/jobs.py CHANGED
@@ -33,9 +33,15 @@ class JobStore:
33
  def update(self, job_id: str, **kw: Any) -> None:
34
  if job_id in self._jobs:
35
  self._jobs[job_id].update(kw)
36
- row = {"job_id": job_id, "status": self._jobs[job_id]["status"]}
 
37
  if kw.get("error"):
38
  row["error_message"] = kw["error"]
 
 
 
 
 
39
  supabase.upsert_job(row)
40
 
41
  def response(self, job_id: str) -> Optional[InferenceResponse]:
 
33
  def update(self, job_id: str, **kw: Any) -> None:
34
  if job_id in self._jobs:
35
  self._jobs[job_id].update(kw)
36
+ status = self._jobs[job_id]["status"]
37
+ row: dict[str, Any] = {"job_id": job_id, "status": status}
38
  if kw.get("error"):
39
  row["error_message"] = kw["error"]
40
+ if kw.get("result"):
41
+ row["result_json"] = kw["result"]
42
+ if status in ("complete", "failed"):
43
+ from datetime import datetime, timezone
44
+ row["completed_at"] = datetime.now(timezone.utc).isoformat()
45
  supabase.upsert_job(row)
46
 
47
  def response(self, job_id: str) -> Optional[InferenceResponse]:
backend/persistence.py CHANGED
@@ -6,13 +6,34 @@ end-to-end locally. Tables match backend/db/schema.sql.
6
  from __future__ import annotations
7
 
8
  import logging
 
 
9
  import uuid
10
- from typing import Any, Optional
11
 
12
  from .config import settings
13
 
14
  logger = logging.getLogger(__name__)
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  # ---------------------------------------------------------------------------
18
  # Object storage — Supabase Storage by default (no extra account), R2 if configured.
@@ -48,8 +69,8 @@ class ObjectStore:
48
  # 2) Supabase Storage (via the service_role client; bucket must be public)
49
  if self._sb is not None:
50
  try:
51
- self._sb.storage.from_(self._bucket).upload(
52
- key, data, {"content-type": content_type, "upsert": "true"})
53
  url = self._sb.storage.from_(self._bucket).get_public_url(key)
54
  return url if isinstance(url, str) else key
55
  except Exception as e: # noqa: BLE001
@@ -80,7 +101,7 @@ class SupabaseLogger:
80
  if self._client is None or not rows:
81
  return
82
  try:
83
- self._client.table(table).insert(rows).execute()
84
  except Exception as e: # noqa: BLE001
85
  logger.warning("supabase insert into %s failed: %s", table, e)
86
 
@@ -89,7 +110,7 @@ class SupabaseLogger:
89
  if self._client is None:
90
  return
91
  try:
92
- self._client.table("jobs").upsert(row).execute()
93
  except Exception as e: # noqa: BLE001
94
  logger.warning("supabase upsert job failed: %s", e)
95
 
@@ -110,7 +131,7 @@ class SupabaseLogger:
110
  if self._client is None:
111
  return []
112
  try:
113
- return build(self._client.table(table).select("*")).execute().data or []
114
  except Exception as e: # noqa: BLE001
115
  logger.warning("supabase select from %s failed: %s", table, e)
116
  return []
@@ -133,16 +154,16 @@ class SupabaseLogger:
133
  if self._client is None:
134
  return False
135
  try:
136
- row = self._client.table("review_queue").select("*").eq("id", review_id).execute().data
137
  if not row:
138
  return False
139
  r = row[0]
140
- self._client.table("human_labels").insert({
141
  "review_queue_id": review_id, "image_id": r.get("image_id"),
142
  "segment_id": r.get("segment_id"), "confirmed_label": label,
143
  "labeled_by": labeled_by,
144
- }).execute()
145
- self._client.table("review_queue").update({"status": "confirmed"}).eq("id", review_id).execute()
146
  return True
147
  except Exception as e: # noqa: BLE001
148
  logger.warning("confirm_label failed: %s", e)
 
6
  from __future__ import annotations
7
 
8
  import logging
9
+ import threading
10
+ import time
11
  import uuid
12
+ from typing import Any, Callable, Optional
13
 
14
  from .config import settings
15
 
16
  logger = logging.getLogger(__name__)
17
 
18
+ # The supabase client is httpx-based and shared across the event-loop thread (job status
19
+ # updates) and the run_in_executor worker thread (pipeline logging). Serialize all access
20
+ # and retry transient transport errors so best-effort logging is actually reliable on the
21
+ # (slower) deployed box, where unsynchronized/stale connections intermittently dropped writes.
22
+ _SB_LOCK = threading.Lock()
23
+ _SB_RETRIES = 3
24
+
25
+
26
+ def _sb_call(fn: Callable[[], Any]) -> Any:
27
+ last: Exception | None = None
28
+ with _SB_LOCK:
29
+ for i in range(_SB_RETRIES):
30
+ try:
31
+ return fn()
32
+ except Exception as e: # noqa: BLE001 (transient transport/connection errors)
33
+ last = e
34
+ time.sleep(0.3 * (i + 1))
35
+ raise last # type: ignore[misc]
36
+
37
 
38
  # ---------------------------------------------------------------------------
39
  # Object storage — Supabase Storage by default (no extra account), R2 if configured.
 
69
  # 2) Supabase Storage (via the service_role client; bucket must be public)
70
  if self._sb is not None:
71
  try:
72
+ _sb_call(lambda: self._sb.storage.from_(self._bucket).upload(
73
+ key, data, {"content-type": content_type, "upsert": "true"}))
74
  url = self._sb.storage.from_(self._bucket).get_public_url(key)
75
  return url if isinstance(url, str) else key
76
  except Exception as e: # noqa: BLE001
 
101
  if self._client is None or not rows:
102
  return
103
  try:
104
+ _sb_call(lambda: self._client.table(table).insert(rows).execute())
105
  except Exception as e: # noqa: BLE001
106
  logger.warning("supabase insert into %s failed: %s", table, e)
107
 
 
110
  if self._client is None:
111
  return
112
  try:
113
+ _sb_call(lambda: self._client.table("jobs").upsert(row).execute())
114
  except Exception as e: # noqa: BLE001
115
  logger.warning("supabase upsert job failed: %s", e)
116
 
 
131
  if self._client is None:
132
  return []
133
  try:
134
+ return _sb_call(lambda: build(self._client.table(table).select("*")).execute().data) or []
135
  except Exception as e: # noqa: BLE001
136
  logger.warning("supabase select from %s failed: %s", table, e)
137
  return []
 
154
  if self._client is None:
155
  return False
156
  try:
157
+ row = _sb_call(lambda: self._client.table("review_queue").select("*").eq("id", review_id).execute().data)
158
  if not row:
159
  return False
160
  r = row[0]
161
+ _sb_call(lambda: self._client.table("human_labels").insert({
162
  "review_queue_id": review_id, "image_id": r.get("image_id"),
163
  "segment_id": r.get("segment_id"), "confirmed_label": label,
164
  "labeled_by": labeled_by,
165
+ }).execute())
166
+ _sb_call(lambda: self._client.table("review_queue").update({"status": "confirmed"}).eq("id", review_id).execute())
167
  return True
168
  except Exception as e: # noqa: BLE001
169
  logger.warning("confirm_label failed: %s", e)