refactor(connections): 实现数据库连接池和连接管理机制
- 新增 MySQL 和 PostgreSQL 连接池类,支持按需创建和自动回收连接 - 增加连接池全局实例及其获取函数,实现连接复用 - 修改原有连接获取函数,改用连接池管理连接 - 添加 refresh_conn 函数用于检测和刷新断开的连接 - 在 runner 模块中批处理循环前调用 refresh_conn 保证长时间任务连接可用 - 用 close_all_pools 统一关闭所有连接池连接,替代原单独关闭调用 - 提升长期运行任务的数据库连接稳定性与效率
Showing
2 changed files
with
303 additions
and
27 deletions
| 1 | import time | ||
| 1 | import pymysql | 2 | import pymysql |
| 2 | import pymysql.cursors | 3 | import pymysql.cursors |
| 3 | import pg8000 | 4 | import pg8000 |
| ... | @@ -5,6 +6,10 @@ import oss2 | ... | @@ -5,6 +6,10 @@ import oss2 |
| 5 | from .config import SOURCE_DB, HK_SONGS_DB, CRAWLER_DB, OSS_CONFIG, OSS_CONNECTION_POOL_SIZE | 6 | from .config import SOURCE_DB, HK_SONGS_DB, CRAWLER_DB, OSS_CONFIG, OSS_CONNECTION_POOL_SIZE |
| 6 | 7 | ||
| 7 | 8 | ||
| 9 | CONN_POOL_SIZE = 4 | ||
| 10 | CONN_RECYCLE_SECONDS = 600 # 连接最长存活 10 分钟,超时自动换新 | ||
| 11 | |||
| 12 | |||
| 8 | def _strip_nul(value): | 13 | def _strip_nul(value): |
| 9 | """PostgreSQL text/json 参数不接受 NUL 字符,递归清理全部绑定参数。""" | 14 | """PostgreSQL text/json 参数不接受 NUL 字符,递归清理全部绑定参数。""" |
| 10 | if isinstance(value, str): | 15 | if isinstance(value, str): |
| ... | @@ -57,27 +62,272 @@ class _NulSafePgConnection: | ... | @@ -57,27 +62,272 @@ class _NulSafePgConnection: |
| 57 | return getattr(self._connection, name) | 62 | return getattr(self._connection, name) |
| 58 | 63 | ||
| 59 | 64 | ||
| 65 | class _MySqlConnPool: | ||
| 66 | """MySQL 连接池:按需创建连接,定期回收,获取时自动重连。""" | ||
| 67 | |||
| 68 | def __init__(self, connect_kwargs, size=CONN_POOL_SIZE, recycle=CONN_RECYCLE_SECONDS): | ||
| 69 | self._connect_kwargs = connect_kwargs | ||
| 70 | self._size = size | ||
| 71 | self._recycle = recycle | ||
| 72 | self._pool = [] | ||
| 73 | self._created_at = [] | ||
| 74 | |||
| 75 | def _new_conn(self): | ||
| 76 | conn = pymysql.connect(**self._connect_kwargs, cursorclass=pymysql.cursors.DictCursor) | ||
| 77 | self._pool.append(conn) | ||
| 78 | self._created_at.append(time.monotonic()) | ||
| 79 | return conn | ||
| 80 | |||
| 81 | def get(self) -> pymysql.Connection: | ||
| 82 | for i in range(len(self._pool)): | ||
| 83 | conn = self._pool[i] | ||
| 84 | try: | ||
| 85 | conn.ping(reconnect=False) | ||
| 86 | except Exception: | ||
| 87 | pass | ||
| 88 | else: | ||
| 89 | if time.monotonic() - self._created_at[i] < self._recycle: | ||
| 90 | return conn | ||
| 91 | # 连接超时回收 | ||
| 92 | try: | ||
| 93 | conn.close() | ||
| 94 | except Exception: | ||
| 95 | pass | ||
| 96 | self._pool.pop(i) | ||
| 97 | self._created_at.pop(i) | ||
| 98 | return self._new_conn() | ||
| 99 | # ping 失败,尝试重连 | ||
| 100 | try: | ||
| 101 | conn.ping(reconnect=True) | ||
| 102 | self._created_at[i] = time.monotonic() | ||
| 103 | return conn | ||
| 104 | except Exception: | ||
| 105 | try: | ||
| 106 | conn.close() | ||
| 107 | except Exception: | ||
| 108 | pass | ||
| 109 | self._pool.pop(i) | ||
| 110 | self._created_at.pop(i) | ||
| 111 | return self._new_conn() | ||
| 112 | return self._new_conn() | ||
| 113 | |||
| 114 | def check(self, conn) -> pymysql.Connection: | ||
| 115 | """确保连接可用;如断开则自动替换为新连接。""" | ||
| 116 | try: | ||
| 117 | conn.ping(reconnect=False) | ||
| 118 | except Exception: | ||
| 119 | pass | ||
| 120 | else: | ||
| 121 | if id(conn) in {id(c) for c in self._pool}: | ||
| 122 | return conn | ||
| 123 | try: | ||
| 124 | conn.ping(reconnect=True) | ||
| 125 | return conn | ||
| 126 | except Exception: | ||
| 127 | pass | ||
| 128 | try: | ||
| 129 | idx = None | ||
| 130 | for i, c in enumerate(self._pool): | ||
| 131 | if id(c) == id(conn): | ||
| 132 | idx = i | ||
| 133 | break | ||
| 134 | if idx is not None: | ||
| 135 | try: | ||
| 136 | conn.close() | ||
| 137 | except Exception: | ||
| 138 | pass | ||
| 139 | self._pool.pop(idx) | ||
| 140 | self._created_at.pop(idx) | ||
| 141 | except Exception: | ||
| 142 | pass | ||
| 143 | return self._new_conn() | ||
| 144 | |||
| 145 | def close_all(self): | ||
| 146 | for conn in self._pool: | ||
| 147 | try: | ||
| 148 | conn.close() | ||
| 149 | except Exception: | ||
| 150 | pass | ||
| 151 | self._pool.clear() | ||
| 152 | self._created_at.clear() | ||
| 153 | |||
| 154 | |||
| 155 | class _PgConnPool: | ||
| 156 | """PostgreSQL 连接池:按需创建连接,定期回收,获取时自动重连。""" | ||
| 157 | |||
| 158 | def __init__(self, connect_kwargs, size=CONN_POOL_SIZE, recycle=CONN_RECYCLE_SECONDS): | ||
| 159 | self._connect_kwargs = connect_kwargs | ||
| 160 | self._size = size | ||
| 161 | self._recycle = recycle | ||
| 162 | self._pool = [] | ||
| 163 | self._created_at = [] | ||
| 164 | |||
| 165 | def _new_conn(self): | ||
| 166 | conn = pg8000.connect(**self._connect_kwargs) | ||
| 167 | conn.run("SET TimeZone = 'Asia/Shanghai'") | ||
| 168 | wrapped = _NulSafePgConnection(conn) | ||
| 169 | self._pool.append(conn) | ||
| 170 | self._created_at.append(time.monotonic()) | ||
| 171 | return wrapped | ||
| 172 | |||
| 173 | def get(self) -> _NulSafePgConnection: | ||
| 174 | for i in range(len(self._pool)): | ||
| 175 | conn = self._pool[i] | ||
| 176 | try: | ||
| 177 | conn.run("SELECT 1") | ||
| 178 | except Exception: | ||
| 179 | try: | ||
| 180 | conn.close() | ||
| 181 | except Exception: | ||
| 182 | pass | ||
| 183 | self._pool.pop(i) | ||
| 184 | self._created_at.pop(i) | ||
| 185 | return self._new_conn() | ||
| 186 | if time.monotonic() - self._created_at[i] < self._recycle: | ||
| 187 | return _NulSafePgConnection(conn) | ||
| 188 | # 回收 | ||
| 189 | try: | ||
| 190 | conn.close() | ||
| 191 | except Exception: | ||
| 192 | pass | ||
| 193 | self._pool.pop(i) | ||
| 194 | self._created_at.pop(i) | ||
| 195 | return self._new_conn() | ||
| 196 | return self._new_conn() | ||
| 197 | |||
| 198 | def check(self, conn) -> _NulSafePgConnection: | ||
| 199 | """确保连接可用;如断开则自动替换为新连接。""" | ||
| 200 | try: | ||
| 201 | conn.run("SELECT 1") | ||
| 202 | except Exception: | ||
| 203 | pass | ||
| 204 | else: | ||
| 205 | raw = conn._connection if hasattr(conn, '_connection') else conn | ||
| 206 | if id(raw) in {id(c) for c in self._pool}: | ||
| 207 | return conn | ||
| 208 | # 创建新连接替换当前连接 | ||
| 209 | try: | ||
| 210 | idx = None | ||
| 211 | for i, c in enumerate(self._pool): | ||
| 212 | if id(c) == id(raw): | ||
| 213 | idx = i | ||
| 214 | break | ||
| 215 | if idx is not None: | ||
| 216 | try: | ||
| 217 | raw.close() | ||
| 218 | except Exception: | ||
| 219 | pass | ||
| 220 | self._pool.pop(idx) | ||
| 221 | self._created_at.pop(idx) | ||
| 222 | except Exception: | ||
| 223 | pass | ||
| 224 | return self._new_conn() | ||
| 225 | |||
| 226 | def close_all(self): | ||
| 227 | for conn in self._pool: | ||
| 228 | try: | ||
| 229 | conn.close() | ||
| 230 | except Exception: | ||
| 231 | pass | ||
| 232 | self._pool.clear() | ||
| 233 | self._created_at.clear() | ||
| 234 | |||
| 235 | |||
| 236 | # ── 全局连接池实例 ── | ||
| 237 | |||
| 238 | _source_pool = None | ||
| 239 | _hk_songs_pool = None | ||
| 240 | _spider_pool = None | ||
| 241 | _pg_pool = None | ||
| 242 | |||
| 243 | |||
| 244 | def _get_source_pool() -> _MySqlConnPool: | ||
| 245 | global _source_pool | ||
| 246 | if _source_pool is None: | ||
| 247 | _source_pool = _MySqlConnPool(SOURCE_DB) | ||
| 248 | return _source_pool | ||
| 249 | |||
| 250 | |||
| 251 | def _get_hk_songs_pool() -> _MySqlConnPool: | ||
| 252 | global _hk_songs_pool | ||
| 253 | if _hk_songs_pool is None: | ||
| 254 | _hk_songs_pool = _MySqlConnPool(HK_SONGS_DB) | ||
| 255 | return _hk_songs_pool | ||
| 256 | |||
| 257 | |||
| 258 | def _get_spider_pool() -> _MySqlConnPool: | ||
| 259 | global _spider_pool | ||
| 260 | if _spider_pool is None: | ||
| 261 | cfg = SOURCE_DB.copy() | ||
| 262 | cfg['database'] = 'hikoon-data-spider' | ||
| 263 | _spider_pool = _MySqlConnPool(cfg) | ||
| 264 | return _spider_pool | ||
| 265 | |||
| 266 | |||
| 267 | def _get_pg_pool() -> _PgConnPool: | ||
| 268 | global _pg_pool | ||
| 269 | if _pg_pool is None: | ||
| 270 | _pg_pool = _PgConnPool(CRAWLER_DB) | ||
| 271 | return _pg_pool | ||
| 272 | |||
| 273 | |||
| 60 | def get_source_conn() -> pymysql.Connection: | 274 | def get_source_conn() -> pymysql.Connection: |
| 61 | return pymysql.connect(**SOURCE_DB, cursorclass=pymysql.cursors.DictCursor) | 275 | return _get_source_pool().get() |
| 62 | 276 | ||
| 63 | 277 | ||
| 64 | def get_spider_conn() -> pymysql.Connection: | 278 | def get_spider_conn() -> pymysql.Connection: |
| 65 | cfg = SOURCE_DB.copy() | 279 | return _get_spider_pool().get() |
| 66 | cfg['database'] = 'hikoon-data-spider' | ||
| 67 | return pymysql.connect(**cfg, cursorclass=pymysql.cursors.DictCursor) | ||
| 68 | 280 | ||
| 69 | 281 | ||
| 70 | def get_hk_songs_conn() -> pymysql.Connection: | 282 | def get_hk_songs_conn() -> pymysql.Connection: |
| 71 | return pymysql.connect(**HK_SONGS_DB, cursorclass=pymysql.cursors.DictCursor) | 283 | return _get_hk_songs_pool().get() |
| 72 | 284 | ||
| 73 | 285 | ||
| 74 | def get_pg_conn() -> _NulSafePgConnection: | 286 | def get_pg_conn() -> _NulSafePgConnection: |
| 75 | conn = pg8000.connect(**CRAWLER_DB) | 287 | return _get_pg_pool().get() |
| 76 | conn.run("SET TimeZone = 'Asia/Shanghai'") | ||
| 77 | return _NulSafePgConnection(conn) | ||
| 78 | 288 | ||
| 79 | 289 | ||
| 80 | def get_oss_bucket() -> oss2.Bucket: | 290 | def get_oss_bucket() -> oss2.Bucket: |
| 81 | oss2.defaults.connection_pool_size = OSS_CONNECTION_POOL_SIZE | 291 | oss2.defaults.connection_pool_size = OSS_CONNECTION_POOL_SIZE |
| 82 | auth = oss2.Auth(OSS_CONFIG['access_key_id'], OSS_CONFIG['access_key_secret']) | 292 | auth = oss2.Auth(OSS_CONFIG['access_key_id'], OSS_CONFIG['access_key_secret']) |
| 83 | return oss2.Bucket(auth, OSS_CONFIG['endpoint'], OSS_CONFIG['bucket_name']) | 293 | return oss2.Bucket(auth, OSS_CONFIG['endpoint'], OSS_CONFIG['bucket_name']) |
| 294 | |||
| 295 | |||
| 296 | def close_all_pools(): | ||
| 297 | """关闭所有连接池中的连接。应在 ETL 退出时调用。""" | ||
| 298 | for pool in (_source_pool, _hk_songs_pool, _spider_pool): | ||
| 299 | if pool is not None: | ||
| 300 | pool.close_all() | ||
| 301 | if _pg_pool is not None: | ||
| 302 | _pg_pool.close_all() | ||
| 303 | |||
| 304 | |||
| 305 | def refresh_conn(conn, pool_type: str): | ||
| 306 | """验证连接健康度,断开时返回新连接,正常时原样返回。 | ||
| 307 | |||
| 308 | 用法:在批次循环开头调用,如 ``conn = refresh_conn(conn, 'pg')``。 | ||
| 309 | pool_type 取 'source' | 'hk_songs' | 'spider' | 'pg'。 | ||
| 310 | """ | ||
| 311 | try: | ||
| 312 | if pool_type == 'pg': | ||
| 313 | conn.run("SELECT 1") | ||
| 314 | return conn | ||
| 315 | # MySQL | ||
| 316 | conn.ping(reconnect=False) | ||
| 317 | return conn | ||
| 318 | except Exception: | ||
| 319 | pass | ||
| 320 | |||
| 321 | pools = { | ||
| 322 | 'source': _get_source_pool, | ||
| 323 | 'hk_songs': _get_hk_songs_pool, | ||
| 324 | 'spider': _get_spider_pool, | ||
| 325 | 'pg': _get_pg_pool, | ||
| 326 | } | ||
| 327 | pool = pools.get(pool_type) | ||
| 328 | if pool is None: | ||
| 329 | raise ValueError(f"unknown pool_type: {pool_type}") | ||
| 330 | |||
| 331 | if pool_type == 'pg': | ||
| 332 | return pool().check(conn) | ||
| 333 | return pool().check(conn) | ... | ... |
| ... | @@ -5,7 +5,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed | ... | @@ -5,7 +5,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed |
| 5 | from tqdm import tqdm | 5 | from tqdm import tqdm |
| 6 | 6 | ||
| 7 | from .config import PLATFORM_QQ, PLATFORM_KUGOU, PLATFORM_NETEASE, BATCH_SIZE, BACKFILL_BATCH_SIZE, OSS_CONFIG | 7 | from .config import PLATFORM_QQ, PLATFORM_KUGOU, PLATFORM_NETEASE, BATCH_SIZE, BACKFILL_BATCH_SIZE, OSS_CONFIG |
| 8 | from .connections import get_hk_songs_conn, get_source_conn, get_spider_conn, get_pg_conn, get_oss_bucket | 8 | from .connections import get_hk_songs_conn, get_source_conn, get_spider_conn, get_pg_conn, get_oss_bucket, refresh_conn, close_all_pools |
| 9 | from .reader import ( | 9 | from .reader import ( |
| 10 | iter_hk_songs_batches, | 10 | iter_hk_songs_batches, |
| 11 | iter_hk_songs_records2_batches, | 11 | iter_hk_songs_records2_batches, |
| ... | @@ -1185,6 +1185,11 @@ def initialize_yinyan_song_records(platforms: list[str], max_batches: int | None | ... | @@ -1185,6 +1185,11 @@ def initialize_yinyan_song_records(platforms: list[str], max_batches: int | None |
| 1185 | skipped_batches = 0 | 1185 | skipped_batches = 0 |
| 1186 | try: | 1186 | try: |
| 1187 | for i, batch in enumerate(tqdm(iter_hk_songs_batches(hk_conn, BATCH_SIZE), desc='init-yinyan')): | 1187 | for i, batch in enumerate(tqdm(iter_hk_songs_batches(hk_conn, BATCH_SIZE), desc='init-yinyan')): |
| 1188 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1189 | hk_conn = refresh_conn(hk_conn, 'hk_songs') | ||
| 1190 | src_conn = refresh_conn(src_conn, 'source') | ||
| 1191 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1192 | |||
| 1188 | if max_batches is not None and i >= max_batches: | 1193 | if max_batches is not None and i >= max_batches: |
| 1189 | break | 1194 | break |
| 1190 | # 过滤掉已存在的 song_id | 1195 | # 过滤掉已存在的 song_id |
| ... | @@ -1236,9 +1241,7 @@ def initialize_yinyan_song_records(platforms: list[str], max_batches: int | None | ... | @@ -1236,9 +1241,7 @@ def initialize_yinyan_song_records(platforms: list[str], max_batches: int | None |
| 1236 | hk_conn.commit() | 1241 | hk_conn.commit() |
| 1237 | log.info("Init: marked %d unmatched songs as deleted", len(unmatched_ids)) | 1242 | log.info("Init: marked %d unmatched songs as deleted", len(unmatched_ids)) |
| 1238 | finally: | 1243 | finally: |
| 1239 | hk_conn.close() | 1244 | close_all_pools() |
| 1240 | src_conn.close() | ||
| 1241 | pg_conn.close() | ||
| 1242 | 1245 | ||
| 1243 | log.info("Initialized yinyan_song_records candidates=%d, skipped_batches=%d", total, skipped_batches) | 1246 | log.info("Initialized yinyan_song_records candidates=%d, skipped_batches=%d", total, skipped_batches) |
| 1244 | 1247 | ||
| ... | @@ -1254,6 +1257,11 @@ def initialize_yinyan_song_records2(platforms: list[str], max_batches: int | Non | ... | @@ -1254,6 +1257,11 @@ def initialize_yinyan_song_records2(platforms: list[str], max_batches: int | Non |
| 1254 | for index, batch in enumerate( | 1257 | for index, batch in enumerate( |
| 1255 | tqdm(iter_hk_songs_records2_batches(hk_conn, BATCH_SIZE), desc='init-yinyan-records2') | 1258 | tqdm(iter_hk_songs_records2_batches(hk_conn, BATCH_SIZE), desc='init-yinyan-records2') |
| 1256 | ): | 1259 | ): |
| 1260 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1261 | hk_conn = refresh_conn(hk_conn, 'hk_songs') | ||
| 1262 | src_conn = refresh_conn(src_conn, 'source') | ||
| 1263 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1264 | |||
| 1257 | if max_batches is not None and index >= max_batches: | 1265 | if max_batches is not None and index >= max_batches: |
| 1258 | break | 1266 | break |
| 1259 | 1267 | ||
| ... | @@ -1281,9 +1289,7 @@ def initialize_yinyan_song_records2(platforms: list[str], max_batches: int | Non | ... | @@ -1281,9 +1289,7 @@ def initialize_yinyan_song_records2(platforms: list[str], max_batches: int | Non |
| 1281 | deleted = delete_yinyan_song_records2_existing_relations(pg_cur) | 1289 | deleted = delete_yinyan_song_records2_existing_relations(pg_cur) |
| 1282 | pg_conn.commit() | 1290 | pg_conn.commit() |
| 1283 | finally: | 1291 | finally: |
| 1284 | hk_conn.close() | 1292 | close_all_pools() |
| 1285 | src_conn.close() | ||
| 1286 | pg_conn.close() | ||
| 1287 | 1293 | ||
| 1288 | log.info( | 1294 | log.info( |
| 1289 | "Initialized yinyan_song_records2 candidates=%d, removed_existing_records1_relations=%d", | 1295 | "Initialized yinyan_song_records2 candidates=%d, removed_existing_records1_relations=%d", |
| ... | @@ -1302,6 +1308,10 @@ def backfill_yinyan_record_platforms(max_batches: int | None = None) -> None: | ... | @@ -1302,6 +1308,10 @@ def backfill_yinyan_record_platforms(max_batches: int | None = None) -> None: |
| 1302 | batch_index = 0 | 1308 | batch_index = 0 |
| 1303 | pbar = tqdm(desc='backfill-yinyan-platforms') | 1309 | pbar = tqdm(desc='backfill-yinyan-platforms') |
| 1304 | while max_batches is None or batch_index < max_batches: | 1310 | while max_batches is None or batch_index < max_batches: |
| 1311 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1312 | src_conn = refresh_conn(src_conn, 'source') | ||
| 1313 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1314 | |||
| 1305 | with pg_conn.cursor() as pg_cur: | 1315 | with pg_conn.cursor() as pg_cur: |
| 1306 | rows = fetch_yinyan_records_missing_platform(pg_cur, BACKFILL_BATCH_SIZE) | 1316 | rows = fetch_yinyan_records_missing_platform(pg_cur, BACKFILL_BATCH_SIZE) |
| 1307 | if not rows: | 1317 | if not rows: |
| ... | @@ -1334,8 +1344,7 @@ def backfill_yinyan_record_platforms(max_batches: int | None = None) -> None: | ... | @@ -1334,8 +1344,7 @@ def backfill_yinyan_record_platforms(max_batches: int | None = None) -> None: |
| 1334 | pbar.update(1) | 1344 | pbar.update(1) |
| 1335 | pbar.close() | 1345 | pbar.close() |
| 1336 | finally: | 1346 | finally: |
| 1337 | src_conn.close() | 1347 | close_all_pools() |
| 1338 | pg_conn.close() | ||
| 1339 | 1348 | ||
| 1340 | log.info("Backfilled yinyan_song_records platform rows=%d, skipped=%d", total, skipped) | 1349 | log.info("Backfilled yinyan_song_records platform rows=%d, skipped=%d", total, skipped) |
| 1341 | 1350 | ||
| ... | @@ -1345,6 +1354,10 @@ def _backfill_qq_singers(spider_conn, pg_conn, bucket, base_url, max_batches): | ... | @@ -1345,6 +1354,10 @@ def _backfill_qq_singers(spider_conn, pg_conn, bucket, base_url, max_batches): |
| 1345 | total_ok = total_skip = 0 | 1354 | total_ok = total_skip = 0 |
| 1346 | pbar = tqdm(desc='backfill-qq-singers') | 1355 | pbar = tqdm(desc='backfill-qq-singers') |
| 1347 | while max_batches is None or batch_index < max_batches: | 1356 | while max_batches is None or batch_index < max_batches: |
| 1357 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1358 | spider_conn = refresh_conn(spider_conn, 'spider') | ||
| 1359 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1360 | |||
| 1348 | with pg_conn.cursor() as pg_cur: | 1361 | with pg_conn.cursor() as pg_cur: |
| 1349 | rows = fetch_qq_songs_missing_singers(pg_cur, BATCH_SIZE) | 1362 | rows = fetch_qq_songs_missing_singers(pg_cur, BATCH_SIZE) |
| 1350 | if not rows: | 1363 | if not rows: |
| ... | @@ -1421,6 +1434,10 @@ def _backfill_kugou_singers(spider_conn, pg_conn, bucket, base_url, max_batches) | ... | @@ -1421,6 +1434,10 @@ def _backfill_kugou_singers(spider_conn, pg_conn, bucket, base_url, max_batches) |
| 1421 | total_ok = total_skip = 0 | 1434 | total_ok = total_skip = 0 |
| 1422 | pbar = tqdm(desc='backfill-kugou-singers') | 1435 | pbar = tqdm(desc='backfill-kugou-singers') |
| 1423 | while max_batches is None or batch_index < max_batches: | 1436 | while max_batches is None or batch_index < max_batches: |
| 1437 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1438 | spider_conn = refresh_conn(spider_conn, 'spider') | ||
| 1439 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1440 | |||
| 1424 | with pg_conn.cursor() as pg_cur: | 1441 | with pg_conn.cursor() as pg_cur: |
| 1425 | rows = fetch_kugou_songs_missing_singers(pg_cur, BATCH_SIZE) | 1442 | rows = fetch_kugou_songs_missing_singers(pg_cur, BATCH_SIZE) |
| 1426 | if not rows: | 1443 | if not rows: |
| ... | @@ -1491,6 +1508,10 @@ def _backfill_netease_singers(spider_conn, pg_conn, bucket, base_url, max_batche | ... | @@ -1491,6 +1508,10 @@ def _backfill_netease_singers(spider_conn, pg_conn, bucket, base_url, max_batche |
| 1491 | total_ok = total_skip = 0 | 1508 | total_ok = total_skip = 0 |
| 1492 | pbar = tqdm(desc='backfill-netease-singers') | 1509 | pbar = tqdm(desc='backfill-netease-singers') |
| 1493 | while max_batches is None or batch_index < max_batches: | 1510 | while max_batches is None or batch_index < max_batches: |
| 1511 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1512 | spider_conn = refresh_conn(spider_conn, 'spider') | ||
| 1513 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1514 | |||
| 1494 | with pg_conn.cursor() as pg_cur: | 1515 | with pg_conn.cursor() as pg_cur: |
| 1495 | rows = fetch_netease_songs_missing_singers(pg_cur, BATCH_SIZE) | 1516 | rows = fetch_netease_songs_missing_singers(pg_cur, BATCH_SIZE) |
| 1496 | if not rows: | 1517 | if not rows: |
| ... | @@ -1569,8 +1590,7 @@ def backfill_empty_singers(platforms: list[str], max_batches: int | None = None) | ... | @@ -1569,8 +1590,7 @@ def backfill_empty_singers(platforms: list[str], max_batches: int | None = None) |
| 1569 | if PLATFORM_NETEASE in platforms: | 1590 | if PLATFORM_NETEASE in platforms: |
| 1570 | _backfill_netease_singers(spider_conn, pg_conn, bucket, base_url, max_batches) | 1591 | _backfill_netease_singers(spider_conn, pg_conn, bucket, base_url, max_batches) |
| 1571 | finally: | 1592 | finally: |
| 1572 | spider_conn.close() | 1593 | close_all_pools() |
| 1573 | pg_conn.close() | ||
| 1574 | 1594 | ||
| 1575 | 1595 | ||
| 1576 | def _is_target_oss_url(url: str | None, base_url: str) -> bool: | 1596 | def _is_target_oss_url(url: str | None, base_url: str) -> bool: |
| ... | @@ -1589,6 +1609,12 @@ def backfill_qq_invalid_covers(max_batches: int | None = None) -> None: | ... | @@ -1589,6 +1609,12 @@ def backfill_qq_invalid_covers(max_batches: int | None = None) -> None: |
| 1589 | pbar = tqdm(desc='backfill-qq-invalid-covers') | 1609 | pbar = tqdm(desc='backfill-qq-invalid-covers') |
| 1590 | try: | 1610 | try: |
| 1591 | while max_batches is None or batch_index < max_batches: | 1611 | while max_batches is None or batch_index < max_batches: |
| 1612 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1613 | hk_conn = refresh_conn(hk_conn, 'hk_songs') | ||
| 1614 | src_conn = refresh_conn(src_conn, 'source') | ||
| 1615 | spider_conn = refresh_conn(spider_conn, 'spider') | ||
| 1616 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1617 | |||
| 1592 | with pg_conn.cursor() as pg_cur: | 1618 | with pg_conn.cursor() as pg_cur: |
| 1593 | invalid_rows = fetch_qq_songs_with_invalid_covers( | 1619 | invalid_rows = fetch_qq_songs_with_invalid_covers( |
| 1594 | pg_cur, QQ_MISSING_ALBUM_COVER, BATCH_SIZE, | 1620 | pg_cur, QQ_MISSING_ALBUM_COVER, BATCH_SIZE, |
| ... | @@ -1674,10 +1700,7 @@ def backfill_qq_invalid_covers(max_batches: int | None = None) -> None: | ... | @@ -1674,10 +1700,7 @@ def backfill_qq_invalid_covers(max_batches: int | None = None) -> None: |
| 1674 | pbar.update(1) | 1700 | pbar.update(1) |
| 1675 | finally: | 1701 | finally: |
| 1676 | pbar.close() | 1702 | pbar.close() |
| 1677 | hk_conn.close() | 1703 | close_all_pools() |
| 1678 | src_conn.close() | ||
| 1679 | spider_conn.close() | ||
| 1680 | pg_conn.close() | ||
| 1681 | log.info('QQ invalid cover backfill: replaced=%d filtered=%d retry_later=%d', replaced, filtered, retry_later) | 1704 | log.info('QQ invalid cover backfill: replaced=%d filtered=%d retry_later=%d', replaced, filtered, retry_later) |
| 1682 | 1705 | ||
| 1683 | 1706 | ||
| ... | @@ -1698,6 +1721,12 @@ def run( | ... | @@ -1698,6 +1721,12 @@ def run( |
| 1698 | batch_index = 0 | 1721 | batch_index = 0 |
| 1699 | pbar = tqdm(desc='batches') | 1722 | pbar = tqdm(desc='batches') |
| 1700 | while max_batches is None or batch_index < max_batches: | 1723 | while max_batches is None or batch_index < max_batches: |
| 1724 | # 长任务连接可能断开,每批次开始前刷新 | ||
| 1725 | hk_conn = refresh_conn(hk_conn, 'hk_songs') | ||
| 1726 | src_conn = refresh_conn(src_conn, 'source') | ||
| 1727 | spider_conn = refresh_conn(spider_conn, 'spider') | ||
| 1728 | pg_conn = refresh_conn(pg_conn, 'pg') | ||
| 1729 | |||
| 1701 | with pg_conn.cursor() as pg_cur: | 1730 | with pg_conn.cursor() as pg_cur: |
| 1702 | pending_records = fetch_pending_yinyan_song_records(pg_cur, BATCH_SIZE) | 1731 | pending_records = fetch_pending_yinyan_song_records(pg_cur, BATCH_SIZE) |
| 1703 | if not pending_records: | 1732 | if not pending_records: |
| ... | @@ -1880,10 +1909,7 @@ def run( | ... | @@ -1880,10 +1909,7 @@ def run( |
| 1880 | pbar.close() | 1909 | pbar.close() |
| 1881 | 1910 | ||
| 1882 | finally: | 1911 | finally: |
| 1883 | hk_conn.close() | 1912 | close_all_pools() |
| 1884 | src_conn.close() | ||
| 1885 | spider_conn.close() | ||
| 1886 | pg_conn.close() | ||
| 1887 | 1913 | ||
| 1888 | log.info("Done. OK=%d ERR=%d", total_ok, total_err) | 1914 | log.info("Done. OK=%d ERR=%d", total_ok, total_err) |
| 1889 | if imported: | 1915 | if imported: | ... | ... |
-
Please register or sign in to post a comment