fix(likes): dedupe latest state with DISTINCT ON

max(created_at)+equality-join returned both rows when two like events shared
an identical created_at (realistic: likes carry a client-supplied timestamp
from offline sync), double-counting the track. Replace with DISTINCT ON
(track_id) + deterministic tiebreaker (created_at desc, id desc) across
get_latest_state / liked-tracks / count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Цвылев Александр Вадимович
2026-07-29 10:53:34 +03:00
parent 591a938e71
commit ed77acf0fe
@@ -6,7 +6,7 @@ Likes are an append-only event log. Current state = latest event per (user, trac
import datetime as dt
import uuid
from sqlalchemy import func, select
from sqlalchemy import Subquery, func, select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.ext.asyncio import AsyncSession
@@ -105,31 +105,52 @@ class SqlAlchemyLikeRepository:
rows = (await self._session.execute(stmt)).scalars().all()
return [_to_entity(r) for r in rows]
def _latest_events_sq(
self, user_id: uuid.UUID, track_ids: list[uuid.UUID] | None
) -> Subquery:
"""The latest like event per ``track_id`` for a user, as a subquery.
``DISTINCT ON (track_id)`` with a deterministic tiebreaker (``created_at``
then ``id``) picks exactly one row per track even when two events share an
identical ``created_at`` — likes carry a client-supplied timestamp from
offline sync, so ties are realistic and a plain ``max()``+equality-join
would return both rows (double-counting the track)."""
stmt = select(
LikeModel.track_id,
LikeModel.value.label("value"),
LikeModel.created_at.label("created_at"),
).where(LikeModel.user_id == user_id)
if track_ids is not None:
stmt = stmt.where(LikeModel.track_id.in_(track_ids))
return (
stmt.distinct(LikeModel.track_id)
.order_by(
LikeModel.track_id,
LikeModel.created_at.desc(),
LikeModel.id.desc(),
)
.subquery()
)
async def get_latest_state(
self, *, user_id: uuid.UUID, track_ids: list[uuid.UUID]
) -> list[Like]:
if not track_ids:
return []
# Subquery: max(created_at) per track for this user
max_sq = (
select(
LikeModel.track_id,
func.max(LikeModel.created_at).label("latest"),
)
.where(LikeModel.user_id == user_id, LikeModel.track_id.in_(track_ids))
.group_by(LikeModel.track_id)
.subquery()
)
rows = (
(
await self._session.execute(
select(LikeModel)
.join(
max_sq,
(LikeModel.track_id == max_sq.c.track_id)
& (LikeModel.created_at == max_sq.c.latest),
.where(
LikeModel.user_id == user_id,
LikeModel.track_id.in_(track_ids),
)
.distinct(LikeModel.track_id)
.order_by(
LikeModel.track_id,
LikeModel.created_at.desc(),
LikeModel.id.desc(),
)
.where(LikeModel.user_id == user_id)
)
)
.scalars()
@@ -141,31 +162,14 @@ class SqlAlchemyLikeRepository:
self, *, user_id: uuid.UUID, limit: int, offset: int
) -> list[Track]:
# Tracks where the latest like event has value='like', ordered by like time desc
max_sq = (
select(
LikeModel.track_id,
func.max(LikeModel.created_at).label("latest"),
)
.where(LikeModel.user_id == user_id)
.group_by(LikeModel.track_id)
.subquery()
)
liked_sq = (
select(LikeModel.track_id, LikeModel.created_at)
.join(
max_sq,
(LikeModel.track_id == max_sq.c.track_id)
& (LikeModel.created_at == max_sq.c.latest),
)
.where(LikeModel.user_id == user_id, LikeModel.value == "like")
.subquery()
)
latest_sq = self._latest_events_sq(user_id, None)
rows = (
(
await self._session.execute(
select(TrackModel)
.join(liked_sq, TrackModel.id == liked_sq.c.track_id)
.order_by(liked_sq.c.created_at.desc())
.join(latest_sq, TrackModel.id == latest_sq.c.track_id)
.where(latest_sq.c.value == "like")
.order_by(latest_sq.c.created_at.desc())
.limit(limit)
.offset(offset)
)
@@ -176,25 +180,11 @@ class SqlAlchemyLikeRepository:
return [_track_to_entity(r) for r in rows]
async def count_liked_tracks(self, *, user_id: uuid.UUID) -> int:
max_sq = (
select(
LikeModel.track_id,
func.max(LikeModel.created_at).label("latest"),
)
.where(LikeModel.user_id == user_id)
.group_by(LikeModel.track_id)
.subquery()
)
liked_sq = (
select(LikeModel.track_id)
.join(
max_sq,
(LikeModel.track_id == max_sq.c.track_id)
& (LikeModel.created_at == max_sq.c.latest),
)
.where(LikeModel.user_id == user_id, LikeModel.value == "like")
.subquery()
)
latest_sq = self._latest_events_sq(user_id, None)
return (
await self._session.execute(select(func.count()).select_from(liked_sq))
await self._session.execute(
select(func.count())
.select_from(latest_sq)
.where(latest_sq.c.value == "like")
)
).scalar_one()