exonware-xwnode 0.0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exonware/__init__.py +14 -0
- exonware/xwnode/__init__.py +127 -0
- exonware/xwnode/base.py +676 -0
- exonware/xwnode/config.py +178 -0
- exonware/xwnode/contracts.py +730 -0
- exonware/xwnode/errors.py +503 -0
- exonware/xwnode/facade.py +460 -0
- exonware/xwnode/strategies/__init__.py +158 -0
- exonware/xwnode/strategies/advisor.py +463 -0
- exonware/xwnode/strategies/edges/__init__.py +32 -0
- exonware/xwnode/strategies/edges/adj_list.py +227 -0
- exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
- exonware/xwnode/strategies/edges/base.py +169 -0
- exonware/xwnode/strategies/flyweight.py +328 -0
- exonware/xwnode/strategies/impls/__init__.py +13 -0
- exonware/xwnode/strategies/impls/_base_edge.py +403 -0
- exonware/xwnode/strategies/impls/_base_node.py +307 -0
- exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
- exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
- exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
- exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
- exonware/xwnode/strategies/impls/edge_coo.py +533 -0
- exonware/xwnode/strategies/impls/edge_csc.py +447 -0
- exonware/xwnode/strategies/impls/edge_csr.py +492 -0
- exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
- exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
- exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
- exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
- exonware/xwnode/strategies/impls/edge_octree.py +574 -0
- exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
- exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
- exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
- exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
- exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
- exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
- exonware/xwnode/strategies/manager.py +775 -0
- exonware/xwnode/strategies/metrics.py +538 -0
- exonware/xwnode/strategies/migration.py +432 -0
- exonware/xwnode/strategies/nodes/__init__.py +50 -0
- exonware/xwnode/strategies/nodes/_base_node.py +307 -0
- exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
- exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
- exonware/xwnode/strategies/nodes/array_list.py +209 -0
- exonware/xwnode/strategies/nodes/base.py +247 -0
- exonware/xwnode/strategies/nodes/deque.py +200 -0
- exonware/xwnode/strategies/nodes/hash_map.py +135 -0
- exonware/xwnode/strategies/nodes/heap.py +307 -0
- exonware/xwnode/strategies/nodes/linked_list.py +232 -0
- exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
- exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
- exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
- exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
- exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
- exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
- exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
- exonware/xwnode/strategies/nodes/node_btree.py +357 -0
- exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
- exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
- exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
- exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
- exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
- exonware/xwnode/strategies/nodes/node_heap.py +191 -0
- exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
- exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
- exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
- exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
- exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
- exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
- exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
- exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
- exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
- exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
- exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
- exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
- exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
- exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
- exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
- exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
- exonware/xwnode/strategies/nodes/node_treap.py +387 -0
- exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
- exonware/xwnode/strategies/nodes/node_trie.py +252 -0
- exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
- exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
- exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
- exonware/xwnode/strategies/nodes/queue.py +161 -0
- exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
- exonware/xwnode/strategies/nodes/stack.py +152 -0
- exonware/xwnode/strategies/nodes/trie.py +274 -0
- exonware/xwnode/strategies/nodes/union_find.py +283 -0
- exonware/xwnode/strategies/pattern_detector.py +603 -0
- exonware/xwnode/strategies/performance_monitor.py +487 -0
- exonware/xwnode/strategies/queries/__init__.py +24 -0
- exonware/xwnode/strategies/queries/base.py +236 -0
- exonware/xwnode/strategies/queries/cql.py +201 -0
- exonware/xwnode/strategies/queries/cypher.py +181 -0
- exonware/xwnode/strategies/queries/datalog.py +70 -0
- exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
- exonware/xwnode/strategies/queries/eql.py +70 -0
- exonware/xwnode/strategies/queries/flux.py +70 -0
- exonware/xwnode/strategies/queries/gql.py +70 -0
- exonware/xwnode/strategies/queries/graphql.py +240 -0
- exonware/xwnode/strategies/queries/gremlin.py +181 -0
- exonware/xwnode/strategies/queries/hiveql.py +214 -0
- exonware/xwnode/strategies/queries/hql.py +70 -0
- exonware/xwnode/strategies/queries/jmespath.py +219 -0
- exonware/xwnode/strategies/queries/jq.py +66 -0
- exonware/xwnode/strategies/queries/json_query.py +66 -0
- exonware/xwnode/strategies/queries/jsoniq.py +248 -0
- exonware/xwnode/strategies/queries/kql.py +70 -0
- exonware/xwnode/strategies/queries/linq.py +238 -0
- exonware/xwnode/strategies/queries/logql.py +70 -0
- exonware/xwnode/strategies/queries/mql.py +68 -0
- exonware/xwnode/strategies/queries/n1ql.py +210 -0
- exonware/xwnode/strategies/queries/partiql.py +70 -0
- exonware/xwnode/strategies/queries/pig.py +215 -0
- exonware/xwnode/strategies/queries/promql.py +70 -0
- exonware/xwnode/strategies/queries/sparql.py +220 -0
- exonware/xwnode/strategies/queries/sql.py +275 -0
- exonware/xwnode/strategies/queries/xml_query.py +66 -0
- exonware/xwnode/strategies/queries/xpath.py +223 -0
- exonware/xwnode/strategies/queries/xquery.py +258 -0
- exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
- exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
- exonware/xwnode/strategies/registry.py +604 -0
- exonware/xwnode/strategies/simple.py +273 -0
- exonware/xwnode/strategies/utils.py +532 -0
- exonware/xwnode/types.py +912 -0
- exonware/xwnode/version.py +78 -0
- exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
- exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
- exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
- exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,400 @@
|
|
1
|
+
"""
|
2
|
+
LSM Tree Node Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the LSM_TREE strategy for write-heavy workloads
|
5
|
+
with eventual consistency and compaction.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, Dict, List, Optional, Tuple
|
9
|
+
import time
|
10
|
+
import threading
|
11
|
+
from collections import defaultdict
|
12
|
+
from .base import ANodeTreeStrategy
|
13
|
+
from ...types import NodeMode, NodeTrait
|
14
|
+
|
15
|
+
|
16
|
+
class MemTable:
|
17
|
+
"""In-memory table for LSM tree."""
|
18
|
+
|
19
|
+
def __init__(self, max_size: int = 1000):
|
20
|
+
self.data: Dict[str, Tuple[Any, float]] = {} # key -> (value, timestamp)
|
21
|
+
self.max_size = max_size
|
22
|
+
self.size = 0
|
23
|
+
|
24
|
+
def put(self, key: str, value: Any) -> bool:
|
25
|
+
"""Put value, returns True if table is now full."""
|
26
|
+
self.data[key] = (value, time.time())
|
27
|
+
if key not in self.data:
|
28
|
+
self.size += 1
|
29
|
+
return self.size >= self.max_size
|
30
|
+
|
31
|
+
def get(self, key: str) -> Optional[Tuple[Any, float]]:
|
32
|
+
"""Get value and timestamp."""
|
33
|
+
return self.data.get(key)
|
34
|
+
|
35
|
+
def has(self, key: str) -> bool:
|
36
|
+
"""Check if key exists."""
|
37
|
+
return key in self.data
|
38
|
+
|
39
|
+
def remove(self, key: str) -> bool:
|
40
|
+
"""Remove key (tombstone)."""
|
41
|
+
if key in self.data:
|
42
|
+
self.data[key] = (None, time.time()) # Tombstone
|
43
|
+
return True
|
44
|
+
return False
|
45
|
+
|
46
|
+
def items(self) -> Iterator[Tuple[str, Tuple[Any, float]]]:
|
47
|
+
"""Get all items."""
|
48
|
+
return iter(self.data.items())
|
49
|
+
|
50
|
+
def clear(self) -> None:
|
51
|
+
"""Clear all data."""
|
52
|
+
self.data.clear()
|
53
|
+
self.size = 0
|
54
|
+
|
55
|
+
|
56
|
+
class SSTable:
|
57
|
+
"""Sorted String Table for LSM tree."""
|
58
|
+
|
59
|
+
def __init__(self, level: int, data: Dict[str, Tuple[Any, float]]):
|
60
|
+
self.level = level
|
61
|
+
self.data = dict(sorted(data.items())) # Keep sorted
|
62
|
+
self.creation_time = time.time()
|
63
|
+
self.size = len(data)
|
64
|
+
|
65
|
+
def get(self, key: str) -> Optional[Tuple[Any, float]]:
|
66
|
+
"""Get value and timestamp."""
|
67
|
+
return self.data.get(key)
|
68
|
+
|
69
|
+
def has(self, key: str) -> bool:
|
70
|
+
"""Check if key exists."""
|
71
|
+
return key in self.data
|
72
|
+
|
73
|
+
def items(self) -> Iterator[Tuple[str, Tuple[Any, float]]]:
|
74
|
+
"""Get all items in sorted order."""
|
75
|
+
return iter(self.data.items())
|
76
|
+
|
77
|
+
def keys(self) -> Iterator[str]:
|
78
|
+
"""Get all keys in sorted order."""
|
79
|
+
return iter(self.data.keys())
|
80
|
+
|
81
|
+
def range_query(self, start_key: str, end_key: str) -> List[Tuple[str, Any, float]]:
|
82
|
+
"""Query range [start_key, end_key]."""
|
83
|
+
result = []
|
84
|
+
for key, (value, timestamp) in self.data.items():
|
85
|
+
if start_key <= key <= end_key and value is not None: # Skip tombstones
|
86
|
+
result.append((key, value, timestamp))
|
87
|
+
return result
|
88
|
+
|
89
|
+
|
90
|
+
class LSMTreeStrategy(ANodeTreeStrategy):
|
91
|
+
"""
|
92
|
+
LSM Tree node strategy for write-heavy workloads.
|
93
|
+
|
94
|
+
Provides excellent write performance with eventual read consistency
|
95
|
+
through in-memory memtables and sorted disk-based SSTables.
|
96
|
+
"""
|
97
|
+
|
98
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
99
|
+
"""Initialize the LSM Tree strategy."""
|
100
|
+
super().__init__(NodeMode.LSM_TREE, traits, **options)
|
101
|
+
|
102
|
+
self.memtable_size = options.get('memtable_size', 1000)
|
103
|
+
self.max_levels = options.get('max_levels', 7)
|
104
|
+
self.level_multiplier = options.get('level_multiplier', 10)
|
105
|
+
|
106
|
+
# Storage components
|
107
|
+
self.memtable = MemTable(self.memtable_size)
|
108
|
+
self.immutable_memtables: List[MemTable] = []
|
109
|
+
self.sstables: Dict[int, List[SSTable]] = defaultdict(list)
|
110
|
+
self._values: Dict[str, Any] = {} # Direct key-value cache for fast access
|
111
|
+
|
112
|
+
# Compaction control
|
113
|
+
self._compaction_lock = threading.RLock()
|
114
|
+
self._background_compaction = options.get('background_compaction', False)
|
115
|
+
self._last_compaction = time.time()
|
116
|
+
|
117
|
+
self._size = 0
|
118
|
+
|
119
|
+
def get_supported_traits(self) -> NodeTrait:
|
120
|
+
"""Get the traits supported by the LSM tree strategy."""
|
121
|
+
return (NodeTrait.ORDERED | NodeTrait.STREAMING | NodeTrait.PERSISTENT)
|
122
|
+
|
123
|
+
# ============================================================================
|
124
|
+
# CORE OPERATIONS (Key-based interface for compatibility)
|
125
|
+
# ============================================================================
|
126
|
+
|
127
|
+
def put(self, key: Any, value: Any = None) -> None:
|
128
|
+
"""Store a value (optimized for writes)."""
|
129
|
+
key_str = str(key)
|
130
|
+
|
131
|
+
# Always write to active memtable first
|
132
|
+
was_new_key = key_str not in self._values
|
133
|
+
|
134
|
+
if self.memtable.put(key_str, value):
|
135
|
+
# Memtable is full, flush to L0
|
136
|
+
self._flush_memtable()
|
137
|
+
|
138
|
+
# Update our direct storage too for consistency
|
139
|
+
self._values[key_str] = value
|
140
|
+
|
141
|
+
if was_new_key:
|
142
|
+
self._size += 1
|
143
|
+
|
144
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
145
|
+
"""Retrieve a value (may involve multiple lookups)."""
|
146
|
+
key_str = str(key)
|
147
|
+
|
148
|
+
# 1. Check active memtable first
|
149
|
+
result = self.memtable.get(key_str)
|
150
|
+
if result is not None:
|
151
|
+
value, timestamp = result
|
152
|
+
return value if value is not None else default
|
153
|
+
|
154
|
+
# 2. Check immutable memtables (newest first)
|
155
|
+
for memtable in reversed(self.immutable_memtables):
|
156
|
+
result = memtable.get(key_str)
|
157
|
+
if result is not None:
|
158
|
+
value, timestamp = result
|
159
|
+
return value if value is not None else default
|
160
|
+
|
161
|
+
# 3. Check SSTables from L0 down (newest first within each level)
|
162
|
+
for level in range(self.max_levels):
|
163
|
+
for sstable in reversed(self.sstables[level]):
|
164
|
+
result = sstable.get(key_str)
|
165
|
+
if result is not None:
|
166
|
+
value, timestamp = result
|
167
|
+
return value if value is not None else default
|
168
|
+
|
169
|
+
return default
|
170
|
+
|
171
|
+
def has(self, key: Any) -> bool:
|
172
|
+
"""Check if key exists (may involve multiple lookups)."""
|
173
|
+
return str(key) in self._values
|
174
|
+
|
175
|
+
def remove(self, key: Any) -> bool:
|
176
|
+
"""Remove value by key (writes tombstone)."""
|
177
|
+
key_str = str(key)
|
178
|
+
|
179
|
+
if not self.has(key_str):
|
180
|
+
return False
|
181
|
+
|
182
|
+
# Write tombstone to memtable
|
183
|
+
if self.memtable.put(key_str, None): # None = tombstone
|
184
|
+
self._flush_memtable()
|
185
|
+
|
186
|
+
# Remove from direct cache
|
187
|
+
del self._values[key_str]
|
188
|
+
self._size -= 1
|
189
|
+
return True
|
190
|
+
|
191
|
+
def delete(self, key: Any) -> bool:
|
192
|
+
"""Remove value by key (alias for remove)."""
|
193
|
+
return self.remove(key)
|
194
|
+
|
195
|
+
def clear(self) -> None:
|
196
|
+
"""Clear all data."""
|
197
|
+
with self._compaction_lock:
|
198
|
+
self.memtable.clear()
|
199
|
+
self.immutable_memtables.clear()
|
200
|
+
self.sstables.clear()
|
201
|
+
self._values.clear()
|
202
|
+
self._size = 0
|
203
|
+
|
204
|
+
def keys(self) -> Iterator[str]:
|
205
|
+
"""Get all keys (merged from all levels)."""
|
206
|
+
seen_keys = set()
|
207
|
+
|
208
|
+
# Active memtable
|
209
|
+
for key, (value, _) in self.memtable.items():
|
210
|
+
if value is not None and key not in seen_keys:
|
211
|
+
seen_keys.add(key)
|
212
|
+
yield key
|
213
|
+
|
214
|
+
# Immutable memtables
|
215
|
+
for memtable in reversed(self.immutable_memtables):
|
216
|
+
for key, (value, _) in memtable.items():
|
217
|
+
if value is not None and key not in seen_keys:
|
218
|
+
seen_keys.add(key)
|
219
|
+
yield key
|
220
|
+
|
221
|
+
# SSTables
|
222
|
+
for level in range(self.max_levels):
|
223
|
+
for sstable in reversed(self.sstables[level]):
|
224
|
+
for key in sstable.keys():
|
225
|
+
if key not in seen_keys:
|
226
|
+
value, _ = sstable.get(key)
|
227
|
+
if value is not None:
|
228
|
+
seen_keys.add(key)
|
229
|
+
yield key
|
230
|
+
|
231
|
+
def values(self) -> Iterator[Any]:
|
232
|
+
"""Get all values."""
|
233
|
+
for key in self.keys():
|
234
|
+
yield self.get(key)
|
235
|
+
|
236
|
+
def items(self) -> Iterator[tuple[str, Any]]:
|
237
|
+
"""Get all key-value pairs."""
|
238
|
+
for key in self.keys():
|
239
|
+
yield (key, self.get(key))
|
240
|
+
|
241
|
+
def __len__(self) -> int:
|
242
|
+
"""Get the number of items."""
|
243
|
+
return self._size
|
244
|
+
|
245
|
+
def to_native(self) -> Dict[str, Any]:
|
246
|
+
"""Convert to native Python dict."""
|
247
|
+
return dict(self.items())
|
248
|
+
|
249
|
+
@property
|
250
|
+
def is_list(self) -> bool:
|
251
|
+
"""This is not primarily a list strategy."""
|
252
|
+
return False
|
253
|
+
|
254
|
+
@property
|
255
|
+
def is_dict(self) -> bool:
|
256
|
+
"""This is a dict-like strategy."""
|
257
|
+
return True
|
258
|
+
|
259
|
+
# ============================================================================
|
260
|
+
# LSM TREE SPECIFIC OPERATIONS
|
261
|
+
# ============================================================================
|
262
|
+
|
263
|
+
def _flush_memtable(self) -> None:
|
264
|
+
"""Flush active memtable to L0."""
|
265
|
+
if self.memtable.size == 0:
|
266
|
+
return
|
267
|
+
|
268
|
+
with self._compaction_lock:
|
269
|
+
# Move active memtable to immutable
|
270
|
+
self.immutable_memtables.append(self.memtable)
|
271
|
+
self.memtable = MemTable(self.memtable_size)
|
272
|
+
|
273
|
+
# Create L0 SSTable from oldest immutable memtable
|
274
|
+
if self.immutable_memtables:
|
275
|
+
old_memtable = self.immutable_memtables.pop(0)
|
276
|
+
sstable = SSTable(0, old_memtable.data)
|
277
|
+
self.sstables[0].append(sstable)
|
278
|
+
|
279
|
+
# Trigger compaction if needed
|
280
|
+
self._maybe_compact()
|
281
|
+
|
282
|
+
def _maybe_compact(self) -> None:
|
283
|
+
"""Check if compaction is needed and trigger it."""
|
284
|
+
# Simple compaction strategy: compact when level has too many SSTables
|
285
|
+
for level in range(self.max_levels - 1):
|
286
|
+
max_sstables = self.level_multiplier ** level
|
287
|
+
if len(self.sstables[level]) > max_sstables:
|
288
|
+
self._compact_level(level)
|
289
|
+
break
|
290
|
+
|
291
|
+
def _compact_level(self, level: int) -> None:
|
292
|
+
"""Compact SSTables from level to level+1."""
|
293
|
+
if level >= self.max_levels - 1:
|
294
|
+
return
|
295
|
+
|
296
|
+
# Simple compaction: merge all SSTables in level
|
297
|
+
merged_data = {}
|
298
|
+
|
299
|
+
for sstable in self.sstables[level]:
|
300
|
+
for key, (value, timestamp) in sstable.items():
|
301
|
+
if key not in merged_data or timestamp > merged_data[key][1]:
|
302
|
+
merged_data[key] = (value, timestamp)
|
303
|
+
|
304
|
+
# Remove tombstones and create new SSTable
|
305
|
+
clean_data = {k: v for k, v in merged_data.items() if v[0] is not None}
|
306
|
+
|
307
|
+
if clean_data:
|
308
|
+
new_sstable = SSTable(level + 1, clean_data)
|
309
|
+
self.sstables[level + 1].append(new_sstable)
|
310
|
+
|
311
|
+
# Clear the compacted level
|
312
|
+
self.sstables[level].clear()
|
313
|
+
|
314
|
+
self._last_compaction = time.time()
|
315
|
+
|
316
|
+
def force_compaction(self) -> None:
|
317
|
+
"""Force full compaction of all levels."""
|
318
|
+
with self._compaction_lock:
|
319
|
+
# Flush any pending memtables first
|
320
|
+
if self.memtable.size > 0:
|
321
|
+
self._flush_memtable()
|
322
|
+
|
323
|
+
# Compact each level
|
324
|
+
for level in range(self.max_levels - 1):
|
325
|
+
if self.sstables[level]:
|
326
|
+
self._compact_level(level)
|
327
|
+
|
328
|
+
def range_query(self, start_key: str, end_key: str) -> List[Tuple[str, Any]]:
|
329
|
+
"""Efficient range query across all levels."""
|
330
|
+
result_map = {}
|
331
|
+
|
332
|
+
# Query all levels and merge results (newest wins)
|
333
|
+
for level in range(self.max_levels):
|
334
|
+
for sstable in self.sstables[level]:
|
335
|
+
for key, value, timestamp in sstable.range_query(start_key, end_key):
|
336
|
+
if key not in result_map or timestamp > result_map[key][1]:
|
337
|
+
result_map[key] = (value, timestamp)
|
338
|
+
|
339
|
+
# Return sorted results (excluding tombstones)
|
340
|
+
return [(k, v) for k, (v, _) in sorted(result_map.items()) if v is not None]
|
341
|
+
|
342
|
+
def get_level_stats(self) -> Dict[int, Dict[str, Any]]:
|
343
|
+
"""Get statistics for each level."""
|
344
|
+
stats = {}
|
345
|
+
for level in range(self.max_levels):
|
346
|
+
sstables = self.sstables[level]
|
347
|
+
stats[level] = {
|
348
|
+
'sstable_count': len(sstables),
|
349
|
+
'total_keys': sum(sstable.size for sstable in sstables),
|
350
|
+
'oldest_sstable': min((ss.creation_time for ss in sstables), default=0),
|
351
|
+
'newest_sstable': max((ss.creation_time for ss in sstables), default=0)
|
352
|
+
}
|
353
|
+
return stats
|
354
|
+
|
355
|
+
def compact_if_needed(self) -> bool:
|
356
|
+
"""Check and perform compaction if needed."""
|
357
|
+
# Compaction heuristics
|
358
|
+
total_sstables = sum(len(tables) for tables in self.sstables.values())
|
359
|
+
time_since_last = time.time() - self._last_compaction
|
360
|
+
|
361
|
+
if total_sstables > 50 or time_since_last > 300: # 5 minutes
|
362
|
+
self.force_compaction()
|
363
|
+
return True
|
364
|
+
return False
|
365
|
+
|
366
|
+
# ============================================================================
|
367
|
+
# PERFORMANCE CHARACTERISTICS
|
368
|
+
# ============================================================================
|
369
|
+
|
370
|
+
@property
|
371
|
+
def backend_info(self) -> Dict[str, Any]:
|
372
|
+
"""Get backend implementation info."""
|
373
|
+
return {
|
374
|
+
'strategy': 'LSM_TREE',
|
375
|
+
'backend': 'Memtables + SSTables',
|
376
|
+
'memtable_size': self.memtable_size,
|
377
|
+
'max_levels': self.max_levels,
|
378
|
+
'complexity': {
|
379
|
+
'write': 'O(1) amortized',
|
380
|
+
'read': 'O(log n) worst case',
|
381
|
+
'range_query': 'O(log n + k)',
|
382
|
+
'compaction': 'O(n)'
|
383
|
+
}
|
384
|
+
}
|
385
|
+
|
386
|
+
@property
|
387
|
+
def metrics(self) -> Dict[str, Any]:
|
388
|
+
"""Get performance metrics."""
|
389
|
+
total_sstables = sum(len(tables) for tables in self.sstables.values())
|
390
|
+
memtable_utilization = self.memtable.size / self.memtable_size * 100
|
391
|
+
|
392
|
+
return {
|
393
|
+
'size': self._size,
|
394
|
+
'active_memtable_size': self.memtable.size,
|
395
|
+
'immutable_memtables': len(self.immutable_memtables),
|
396
|
+
'total_sstables': total_sstables,
|
397
|
+
'memtable_utilization': f"{memtable_utilization:.1f}%",
|
398
|
+
'last_compaction': self._last_compaction,
|
399
|
+
'memory_usage': f"{(self.memtable.size + total_sstables * 500) * 24} bytes (estimated)"
|
400
|
+
}
|