forked from apache/cassandra-python-driver
-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathtablets.py
More file actions
156 lines (126 loc) · 5.13 KB
/
tablets.py
File metadata and controls
156 lines (126 loc) · 5.13 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import sys
from operator import attrgetter
from threading import Lock
from typing import Optional
from uuid import UUID
# C-accelerated attrgetter avoids per-call lambda allocation overhead
_get_first_token = attrgetter("first_token")
_get_last_token = attrgetter("last_token")
# On Python >= 3.10, bisect.bisect_left supports the key= parameter and is
# implemented in C -- roughly 2.5-3.5x faster than the pure-Python fallback.
# Keep the fallback for Python < 3.10 (which lacks key= support).
if sys.version_info >= (3, 10):
from bisect import bisect_left
else:
def bisect_left(a, x, lo=0, hi=None, *, key=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e < x, and all e in
a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will
insert just before the leftmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
# Note, the comparison uses "<" to match the
# __lt__() logic in list.sort() and in heapq.
if key is None:
while lo < hi:
mid = (lo + hi) // 2
if a[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
while lo < hi:
mid = (lo + hi) // 2
if key(a[mid]) < x:
lo = mid + 1
else:
hi = mid
return lo
class Tablet(object):
"""
Represents a single ScyllaDB tablet.
It stores information about each replica, its host and shard,
and the token interval in the format (first_token, last_token].
"""
first_token = 0
last_token = 0
replicas = None
def __init__(self, first_token=0, last_token=0, replicas=None):
self.first_token = first_token
self.last_token = last_token
self.replicas = replicas
def __str__(self):
return "<Tablet: first_token=%s last_token=%s replicas=%s>" \
% (self.first_token, self.last_token, self.replicas)
__repr__ = __str__
@staticmethod
def _is_valid_tablet(replicas):
return replicas is not None and len(replicas) != 0
@staticmethod
def from_row(first_token, last_token, replicas):
if Tablet._is_valid_tablet(replicas):
tablet = Tablet(first_token, last_token, replicas)
return tablet
return None
def replica_contains_host_id(self, uuid: UUID) -> bool:
for replica in self.replicas:
if replica[0] == uuid:
return True
return False
class Tablets(object):
_lock = None
_tablets = {}
def __init__(self, tablets):
self._tablets = tablets
self._lock = Lock()
def table_has_tablets(self, keyspace, table) -> bool:
return bool(self._tablets.get((keyspace, table), []))
def get_tablet_for_key(self, keyspace, table, t):
tablet = self._tablets.get((keyspace, table), [])
if not tablet:
return None
id = bisect_left(tablet, t.value, key=_get_last_token)
if id < len(tablet) and t.value > tablet[id].first_token:
return tablet[id]
return None
def drop_tablets(self, keyspace: str, table: Optional[str] = None):
with self._lock:
if table is not None:
self._tablets.pop((keyspace, table), None)
return
to_be_deleted = []
for key in self._tablets.keys():
if key[0] == keyspace:
to_be_deleted.append(key)
for key in to_be_deleted:
del self._tablets[key]
def drop_tablets_by_host_id(self, host_id: Optional[UUID]):
if host_id is None:
return
with self._lock:
for key, tablets in self._tablets.items():
to_be_deleted = []
for tablet_id, tablet in enumerate(tablets):
if tablet.replica_contains_host_id(host_id):
to_be_deleted.append(tablet_id)
for tablet_id in reversed(to_be_deleted):
tablets.pop(tablet_id)
def add_tablet(self, keyspace, table, tablet):
with self._lock:
tablets_for_table = self._tablets.setdefault((keyspace, table), [])
# find first overlapping range
start = bisect_left(tablets_for_table, tablet.first_token, key=_get_first_token)
if start > 0 and tablets_for_table[start - 1].last_token > tablet.first_token:
start = start - 1
# find last overlapping range
end = bisect_left(tablets_for_table, tablet.last_token, key=_get_last_token)
if end < len(tablets_for_table) and tablets_for_table[end].first_token >= tablet.last_token:
end = end - 1
if start <= end:
del tablets_for_table[start:end + 1]
tablets_for_table.insert(start, tablet)