-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathredis_client.py
More file actions
120 lines (93 loc) · 4.07 KB
/
redis_client.py
File metadata and controls
120 lines (93 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import os
from redis import StrictRedis
from nypl_py_utils.functions.log_helper import create_log
class RedisClient:
KEY_PREFIX = "m2-barcode-store-by-barcode-"
def __init__(self, endpoint):
self.host = endpoint
self.logger = create_log("redis_client", json=True)
self.client = self._connect()
self._last_n_lookups = []
def _connect(self):
try:
client = StrictRedis(host=self.host, decode_responses=True)
client.ping()
except ConnectionError as e:
self.logger.error("Error connecting to redis at endpoint: " + self.host)
raise RedisClientError(f"Connected to redis at: {self.host}".format(e))
self.logger.info("Connected to redis at:" + self.host)
return client
def pipeline(self):
return self.client.pipeline()
def get_size(self):
return self.client.dbsize()
def _remove_prefix(self, barcode):
return barcode.replace("m2-barcode-store-by-barcode-", "")
def barcodes_with_customer_codes(self, barcodes):
barcodes_length = len(barcodes) if barcodes is not None else 0
barcodes_with_prefix = [self.KEY_PREFIX + barcode for barcode in barcodes]
customer_codes = self.client.mget(barcodes_with_prefix)
return [
{"barcode": barcodes[i], "m2CustomerCode": customer_codes[i]}
for i in range(barcodes_length)
]
def get_customer_codes(self, barcodes):
if barcodes is None or len(barcodes) == 0:
raise RedisClientError("No barcode supplied")
pairs = self.barcodes_with_customer_codes(barcodes)
self.monitor_failed_lookups(pairs)
found = [pair for pair in pairs if pair["m2CustomerCode"] is not None]
if len(found) == 0:
raise RedisClientError(
f'Customer codes not found for barcodes: {", ".join(barcodes)}'
)
failed = [pair["barcode"] for pair in pairs if pair["m2CustomerCode"] is None]
if len(failed) > 0:
self.logger.debug(
"Barcodes {} returned no customer codes".format(", ".join(failed))
)
return pairs
def monitor_failed_lookups(self, pairs):
"""
Given a recently fetched set of cc/barcode pairs,
updates a running queue of the most recently executed lookups
to analyze the lookup success/failure rate as an indicator of
a deeper issue with missing data.
"""
max_lookups = 100
try:
max_lookups = int(os.environ.get("MONITOR_LOOKUPS_COUNT", "100"))
except:
self.logger.error(
f"Failure to parse MONITOR_FAILURE_RATE: {os.environ['MONITOR_FAILURE_RATE']}"
)
self._last_n_lookups = pairs + self._last_n_lookups
if len(self._last_n_lookups) > max_lookups:
self._last_n_lookups = self._last_n_lookups[0:max_lookups]
monitor_failure_rate = 0.1
try:
monitor_failure_rate = float(os.environ.get("MONITOR_FAILURE_RATE", "0.10"))
except:
self.logger.error(
f"Failure to parse MONITOR_FAILURE_RATE: {os.environ['MONITOR_FAILURE_RATE']}"
)
failed = len(
[pair for pair in self._last_n_lookups if pair["m2CustomerCode"] is None]
)
requested = len(self._last_n_lookups)
failure_rate = failed / requested
# If we've collected enough samples and failure rate exceeds threshold, log error:
sufficient_data = requested >= min(max_lookups, 10)
if sufficient_data and failure_rate > monitor_failure_rate:
self.logger.error(
f'Lookup failure rate is {"{:2.1f}".format(failure_rate * 100)}%'
)
def _danger_delete(self, *, pattern="", key=""):
if len(pattern) > 0:
for key in self.client.scan_iter(pattern):
self.client.delete(key)
if len(key) > 0:
self.client.delete("key")
class RedisClientError(Exception):
def __init__(self, message=None):
self.message = message