Improve blockchain.address.get_history performance #40

Merged
jackrobison merged 6 commits from improve-history-cache into master 2022-05-22 06:00:39 +02:00
2 changed files with 22 additions and 4 deletions
Showing only changes of commit 4466bb1451 - Show all commits

View file

@ -28,6 +28,9 @@ CLAIM_HASH_LEN = 20
HISTOGRAM_BUCKETS = ( HISTOGRAM_BUCKETS = (
.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 60.0, float('inf') .005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 60.0, float('inf')
) )
SIZE_BUCKETS = (
1, 10, 100, 500, 1000, 2000, 4000, 7500, 10000, 15000, 25000, 50000, 75000, 100000, 150000, 250000, float('inf')
)
CLAIM_TYPES = { CLAIM_TYPES = {
'stream': 1, 'stream': 1,

View file

@ -21,7 +21,7 @@ from hub import __version__, PROMETHEUS_NAMESPACE
from hub.herald import PROTOCOL_MIN, PROTOCOL_MAX, HUB_PROTOCOL_VERSION from hub.herald import PROTOCOL_MIN, PROTOCOL_MAX, HUB_PROTOCOL_VERSION
from hub.build_info import BUILD, COMMIT_HASH, DOCKER_TAG from hub.build_info import BUILD, COMMIT_HASH, DOCKER_TAG
from hub.herald.search import SearchIndex from hub.herald.search import SearchIndex
from hub.common import sha256, hash_to_hex_str, hex_str_to_hash, HASHX_LEN, version_string, formatted_time from hub.common import sha256, hash_to_hex_str, hex_str_to_hash, HASHX_LEN, version_string, formatted_time, SIZE_BUCKETS
from hub.common import protocol_version, RPCError, DaemonError, TaskGroup, HISTOGRAM_BUCKETS, LRUCache, LRUCacheWithMetrics from hub.common import protocol_version, RPCError, DaemonError, TaskGroup, HISTOGRAM_BUCKETS, LRUCache, LRUCacheWithMetrics
from hub.herald.jsonrpc import JSONRPCAutoDetect, JSONRPCConnection, JSONRPCv2, JSONRPC from hub.herald.jsonrpc import JSONRPCAutoDetect, JSONRPCConnection, JSONRPCv2, JSONRPC
from hub.herald.common import BatchRequest, ProtocolError, Request, Batch, Notification from hub.herald.common import BatchRequest, ProtocolError, Request, Batch, Notification
@ -146,7 +146,6 @@ class SessionManager:
pending_query_metric = Gauge( pending_query_metric = Gauge(
"pending_queries_count", "Number of pending and running sqlite queries", namespace=NAMESPACE "pending_queries_count", "Number of pending and running sqlite queries", namespace=NAMESPACE
) )
client_version_metric = Counter( client_version_metric = Counter(
"clients", "Number of connections received per client version", "clients", "Number of connections received per client version",
namespace=NAMESPACE, labelnames=("version",) namespace=NAMESPACE, labelnames=("version",)
@ -155,6 +154,14 @@ class SessionManager:
"address_history", "Time to fetch an address history", "address_history", "Time to fetch an address history",
namespace=NAMESPACE, buckets=HISTOGRAM_BUCKETS namespace=NAMESPACE, buckets=HISTOGRAM_BUCKETS
) )
address_subscription_metric = Gauge(
"address_subscriptions", "Number of subscribed addresses",
namespace=NAMESPACE
)
address_history_size_metric = Histogram(
"history_size", "Sizes of histories for subscribed addresses",
namespace=NAMESPACE, buckets=SIZE_BUCKETS
)
notifications_in_flight_metric = Gauge( notifications_in_flight_metric = Gauge(
"notifications_in_flight", "Count of notifications in flight", "notifications_in_flight", "Count of notifications in flight",
namespace=NAMESPACE namespace=NAMESPACE
@ -596,13 +603,16 @@ class SessionManager:
return hex_hash return hex_hash
async def limited_history(self, hashX: bytes) -> typing.List[typing.Tuple[str, int]]: async def limited_history(self, hashX: bytes) -> typing.List[typing.Tuple[str, int]]:
if hashX in self.hashX_full_cache: cached_full_history = self.hashX_full_cache.get(hashX)
return self.hashX_full_cache[hashX] if cached_full_history is not None:
self.address_history_size_metric.observe(len(cached_full_history))
return cached_full_history
if hashX not in self.hashX_history_cache: if hashX not in self.hashX_history_cache:
limit = self.env.max_send // 97 limit = self.env.max_send // 97
self.hashX_history_cache[hashX] = tx_nums = await self.db.read_history(hashX, limit) self.hashX_history_cache[hashX] = tx_nums = await self.db.read_history(hashX, limit)
else: else:
tx_nums = self.hashX_history_cache[hashX] tx_nums = self.hashX_history_cache[hashX]
self.address_history_size_metric.observe(len(tx_nums))
needed_tx_infos = [] needed_tx_infos = []
append_needed_tx_info = needed_tx_infos.append append_needed_tx_info = needed_tx_infos.append
tx_infos = {} tx_infos = {}
@ -653,6 +663,7 @@ class SessionManager:
def remove_session(self, session): def remove_session(self, session):
"""Remove a session from our sessions list if there.""" """Remove a session from our sessions list if there."""
session_id = id(session) session_id = id(session)
self.address_subscription_metric.dec(len(session.hashX_subs))
for hashX in session.hashX_subs: for hashX in session.hashX_subs:
sessions = self.hashx_subscriptions_by_session[hashX] sessions = self.hashx_subscriptions_by_session[hashX]
sessions.remove(session_id) sessions.remove(session_id)
@ -1378,6 +1389,8 @@ class LBRYElectrumX(asyncio.Protocol):
sessions.remove(id(self)) sessions.remove(id(self))
except KeyError: except KeyError:
pass pass
else:
self.session_manager.address_subscription_metric.dec()
if not sessions: if not sessions:
self.hashX_subs.pop(hashX, None) self.hashX_subs.pop(hashX, None)
@ -1415,6 +1428,7 @@ class LBRYElectrumX(asyncio.Protocol):
if len(addresses) > 1000: if len(addresses) > 1000:
raise RPCError(BAD_REQUEST, f'too many addresses in subscription request: {len(addresses)}') raise RPCError(BAD_REQUEST, f'too many addresses in subscription request: {len(addresses)}')
results = [] results = []
self.session_manager.address_subscription_metric.inc(len(addresses))
for address in addresses: for address in addresses:
results.append(await self.hashX_subscribe(self.address_to_hashX(address), address)) results.append(await self.hashX_subscribe(self.address_to_hashX(address), address))
await asyncio.sleep(0) await asyncio.sleep(0)
@ -1473,6 +1487,7 @@ class LBRYElectrumX(asyncio.Protocol):
scripthash: the SHA256 hash of the script to subscribe to""" scripthash: the SHA256 hash of the script to subscribe to"""
hashX = scripthash_to_hashX(scripthash) hashX = scripthash_to_hashX(scripthash)
self.session_manager.address_subscription_metric.inc()
return await self.hashX_subscribe(hashX, scripthash) return await self.hashX_subscribe(hashX, scripthash)
async def scripthash_unsubscribe(self, scripthash: str): async def scripthash_unsubscribe(self, scripthash: str):