Skip to content

Commit dc58c25

Browse files
author
panglei
committed
feat: linux允许并行读取缓存;放宽duckdb版本
1 parent 3ecc5ba commit dc58c25

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

data_watchtower/core/data_loaders.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33
import os
4+
import sys
45
import datetime
6+
import logging
57
from functools import lru_cache
8+
from contextlib import contextmanager
69

710
import duckdb
811
import polars as pl
912
from attrs import define, field
10-
from filelock import FileLock
1113

1214
from .base import DataLoader
1315
from ..utils import get_subclasses, load_subclasses
1416

17+
logger = logging.getLogger(__name__)
1518
CUSTOM_DATA_LOADER_PATH = os.getenv(
1619
"DW_CUSTOM_DATA_LOADER_PATH", "dw_custom.data_loaders"
1720
)
@@ -20,6 +23,46 @@
2023
_CACHE_TABLE = "cache_data"
2124
_META_TABLE = "cache_meta"
2225

26+
_IS_WINDOWS = sys.platform == "win32"
27+
28+
if _IS_WINDOWS:
29+
from filelock import FileLock
30+
31+
@contextmanager
32+
def _read_lock(lock_path):
33+
"""Windows: 独占锁(不支持共享锁)"""
34+
with FileLock(lock_path):
35+
yield
36+
37+
@contextmanager
38+
def _write_lock(lock_path):
39+
"""Windows: 独占锁"""
40+
with FileLock(lock_path):
41+
yield
42+
43+
else:
44+
import fcntl
45+
46+
@contextmanager
47+
def _read_lock(lock_path):
48+
"""Linux/macOS: 共享锁,允许多进程并发读"""
49+
with open(lock_path, "a+") as f:
50+
fcntl.flock(f, fcntl.LOCK_SH)
51+
try:
52+
yield
53+
finally:
54+
fcntl.flock(f, fcntl.LOCK_UN)
55+
56+
@contextmanager
57+
def _write_lock(lock_path):
58+
"""Linux/macOS: 独占锁,阻塞直到所有读锁释放"""
59+
with open(lock_path, "a+") as f:
60+
fcntl.flock(f, fcntl.LOCK_EX)
61+
try:
62+
yield
63+
finally:
64+
fcntl.flock(f, fcntl.LOCK_UN)
65+
2366

2467
@define()
2568
class DatabaseLoader(DataLoader):
@@ -52,7 +95,7 @@ def _cache_paths(self):
5295
def _is_cache_valid(self, db_path, lock_path) -> bool:
5396
if not os.path.exists(db_path):
5497
return False
55-
with FileLock(lock_path):
98+
with _read_lock(lock_path):
5699
with duckdb.connect(db_path, read_only=True) as conn:
57100
row = conn.execute(f"SELECT cached_at FROM {_META_TABLE}").fetchone()
58101
if row is None:
@@ -61,12 +104,13 @@ def _is_cache_valid(self, db_path, lock_path) -> bool:
61104
return (datetime.datetime.now() - cached_at).total_seconds() < self.ttl_cache
62105

63106
def _read_cache(self, db_path, lock_path) -> pl.DataFrame:
64-
with FileLock(lock_path):
107+
with _read_lock(lock_path):
65108
with duckdb.connect(db_path, read_only=True) as conn:
66109
return conn.execute(f"SELECT * FROM {_CACHE_TABLE}").pl()
67110

68111
def _write_cache(self, db_path, lock_path, df: pl.DataFrame):
69-
with FileLock(lock_path):
112+
logger.info("About to update data, starting to acquire lock")
113+
with _write_lock(lock_path):
70114
with duckdb.connect(db_path) as conn:
71115
conn.execute(
72116
f"CREATE OR REPLACE TABLE {_CACHE_TABLE} AS SELECT * FROM df"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies = [
1818
"connectorx>=0.3.0",
1919
"apischema>=0.15.0",
2020
"arrow>=0.7.0",
21-
"duckdb>=1.5.2",
21+
"duckdb>=1.1.3",
2222
"filelock>=3.13.0",
2323
]
2424

0 commit comments

Comments
 (0)