1 """Sharded MySQL repository: route by user_id, scatter-gather counts."""
2
3 import asyncio
4 import zlib
5
6 import aiomysql
7
8 from app.config import settings
9 from app.domain import Action
10 from app.mysql_repo import LikeRepository
11
12
13 def shard_of(user_id: int, n: int) -> int:
14 """Stable, cross-process-consistent shard index. NOT Python hash()."""
15 return zlib.crc32(str(user_id).encode()) % n
16
17
18 class ShardedLikeRepository:
19 def __init__(self, shards: list[LikeRepository]):
20 self._shards = shards
21 self._n = len(shards)
22
23 def _for(self, user_id: int) -> LikeRepository:
24 return self._shards[shard_of(user_id, self._n)]
25
26 async def upsert(self, user_id: int, content_id: int, action: Action) -> None:
27 await self._for(user_id).upsert(user_id, content_id, action)
28
29 async def get_status(self, user_id: int, content_id: int) -> int | None:
30 return await self._for(user_id).get_status(user_id, content_id)
31
32 async def get_count(self, content_id: int) -> int:
33 """Scatter-gather: query all shards in parallel, sum."""
34 results = await asyncio.gather(
35 *[s.get_count(content_id) for s in self._shards]
36 )
37 return sum(results)
38
39
40 async def build_sharded_repo() -> ShardedLikeRepository:
41 """Build N pools from settings and wrap as a sharded repo."""
42 shards = []
43 for i in range(settings.mysql_shards):
44 pool = await aiomysql.create_pool(
45 host=settings.mysql_host,
46 port=settings.mysql_port + i,
47 user=settings.mysql_user,
48 password=settings.mysql_password,
49 db=settings.mysql_db,
50 autocommit=False,
51 )
52 shards.append(LikeRepository(pool))
53 return ShardedLikeRepository(shards)