Extremely long delay grabbing type info for string array (and likely other types) on CockroachDB
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
調査の方向性
この issue では asyncpg.Connection._introspect_types をエントリーポイントとして特定し、CockroachDB の型イントロスペクションで深刻な遅延が発生すると報告しています。まずそこから始め、CockroachDB に対してその挙動を再現してください。提案されているキャッシュによる workaround は一つの方向性を示していますが、意図する修正と完了基準については maintainer の合意が必要です。
索引モデルが issue の本文から書いたものです。
説明
I was having atrocious and unacceptable delays in my production environment that I wasn't seeing locally, using CockroachDB cloud. Found the cause was the introspection of types. I'm using the latest version of the CockroachDB drivers.
I wrote this hack to work around the issue. It caches the result in local memory and also caches it to redis so that new instances don't see it. You can change the key for the redis cache using an environment variable so that new versions aren't locked to old values.
If someone wants to turn it into part of the product, please be my guest. I won't have time for it. In the mean time. here's the hack that does monkey patching:
introspection_result_cache: dict[tuple[str, int, str], Any] = {}
orig_introspection_types = asyncpg.Connection._introspect_types
INTROSPECTION_KEY = os.environ.get(
"ASYNCPG_INTROSPECTION_CACHE_KEY", "ASYNCPG_INTROSPECTION_CACHE_KEY"
)
introspection_lock = asyncio.Lock()
class FauxResult:
_binary_fields = ("kind", "elemdelim")
column_order = [
"oid",
"ns",
"name",
"kind",
"basetype",
"elemtype",
"elemdelim",
"range_subtype",
"attrtypoids",
"attrnames",
"depth",
"basetype_name",
"elemtype_name",
"range_subtype_name",
]
def __init__(self, row=None, data: dict | None = None) -> None:
if row:
self.data = dict(row)
else:
assert data
self.data = data
def __getattr__(self, name: str) -> Any:
return self.data[name]
def __getitem__(self, idx_or_column_name: int | str) -> Any:
if isinstance(idx_or_column_name, int):
return self.data[self.column_order[idx_or_column_name]]
return self.data[idx_or_column_name]
def for_serialization(self) -> dict:
result = copy.copy(self.data)
for field in self._binary_fields:
if (value := self.data.get(field)) is not None:
result[field] = value.decode()
return result
@classmethod
def from_serialization(cls, data: dict) -> Self:
for field in cls._binary_fields:
if (value := data.get(field)) is not None:
data[field] = value.encode()
return cls(data=data)
class FauxPreparedStatementState:
def __init__(self, name) -> None:
self.name = name
async def to_redis_cache(
host: str, port: int, database: str, inspection_types: tuple[list, Any]
) -> None:
pss = FauxPreparedStatementState(inspection_types[1].name)
results = [FauxResult(row) for row in inspection_types[0]]
await redis_client().set(
INTROSPECTION_KEY + f"-{host}-{port}-{database}",
orjson.dumps([[result.for_serialization() for result in results], pss.name]),
)
async def from_redis_cache(host: str, port: int, database: str) -> tuple[list, Any] | None:
data = await redis_client().get(INTROSPECTION_KEY + f"-{host}-{port}-{database}")
if data is None:
return None
results, pss_name = orjson.loads(data)
pss = FauxPreparedStatementState(pss_name)
return [FauxResult.from_serialization(row) for row in results], pss
def apply_introspection_caching():
async def new_introspect_types(self, *args, **kwargs) -> Any:
host: str
port: int
database: str
host, port = self._addr
database = self._params.database
if (cached_val := introspection_result_cache.get((host, port, database))) is not None:
return cached_val
async with introspection_lock:
redis_cached_value = await from_redis_cache(host, port, database)
if redis_cached_value is not None:
introspection_result_cache[host, port, database] = redis_cached_value
return redis_cached_value
result = await orig_introspection_types(self, *args, **kwargs)
await to_redis_cache(host, port, database, result)
return result
asyncpg.Connection._introspect_types = new_introspect_types
- 主要言語
- Python
- スター
- 8.1k
- フォーク
- 469
- 平均マージ
- 2日 20時間
- マージ済み PR(30日)
- 9
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
MagicStack/asyncpg のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
MagicStack/asyncpg#1357 · コメント 1 件 · リアクション 1 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 72/100
MagicStack/asyncpg#1342 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 56/100
MagicStack/asyncpg#1340 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 28/100
MagicStack/asyncpg#1337 ·
-
難易度 4/5 3〜5日 初心者へのやさしさ 42/100
MagicStack/asyncpg#1330 · コメント 1 件 ·
MagicStack/asyncpg の issue をすべて見る
似ている issue
-
bug confirmed issue
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
open-webui/open-webui#30750 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
OpenwaterHealth/openmotion-bloodflow-app#604 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
good first issue
難易度 1/5 1時間未満 初心者へのやさしさ 90/100