|
26 | 26 | import shutil |
27 | 27 | import tempfile |
28 | 28 | import threading |
| 29 | +import tracemalloc |
29 | 30 | import unittest |
30 | 31 | from concurrent.futures import ThreadPoolExecutor |
31 | 32 | from unittest.mock import MagicMock, patch |
@@ -242,6 +243,25 @@ def test_read_spanning_multiple_blocks(self): |
242 | 243 | result = stream.read(30) # 90..120, spans blocks 0 and 1 |
243 | 244 | self.assertEqual(data[90:120], result) |
244 | 245 |
|
| 246 | + def test_large_cached_read_uses_one_result_buffer(self): |
| 247 | + block_size = 256 * 1024 |
| 248 | + data = b'x' * (32 * block_size) |
| 249 | + cache = LocalMemoryCacheManager(len(data), block_size=block_size) |
| 250 | + for i in range(32): |
| 251 | + cache.put_block('test', i, data[i * block_size:(i + 1) * block_size]) |
| 252 | + for positioned in (False, True): |
| 253 | + with self.subTest(positioned=positioned): |
| 254 | + stream = CachingInputStream(self._make_file_io(data), 'test', cache) |
| 255 | + tracemalloc.start() |
| 256 | + try: |
| 257 | + result = (stream.read_at(len(data), 0) if positioned else stream.read()) |
| 258 | + _, peak = tracemalloc.get_traced_memory() |
| 259 | + finally: |
| 260 | + tracemalloc.stop() |
| 261 | + self.assertEqual(data, result) |
| 262 | + self.assertEqual(0 if positioned else len(data), stream.tell()) |
| 263 | + self.assertLess(peak, len(data) * 3 // 2) |
| 264 | + |
245 | 265 | def test_cache_hit_avoids_remote_read(self): |
246 | 266 | data = b"0123456789abcdef" |
247 | 267 | cache = LocalDiskCacheManager(self.cache_dir, 2 ** 63 - 1, block_size=8) |
@@ -472,6 +492,57 @@ def test_custom_whitelist_meta_only(self): |
472 | 492 | result = caching_io.new_input_stream("global-index-uuid.index") |
473 | 493 | self.assertNotIsInstance(result, CachingInputStream) |
474 | 494 |
|
| 495 | + def test_structured_data_cache_preserves_existing_selectors(self): |
| 496 | + from pypaimon.filesystem.caching_file_io import LocalMemoryCacheManager |
| 497 | + from pypaimon.utils.file_type import FileType |
| 498 | + |
| 499 | + for disk in (False, True): |
| 500 | + for whitelist in ('meta,global-index', 'data', 'structured-data', |
| 501 | + 'meta,global-index,structured-data', '*'): |
| 502 | + for name in ('data.parquet', 'data.blob', 'data.orc', 'data.avro', |
| 503 | + 'data.row', 'data.mosaic', 'data.vortex', 'data.lance', |
| 504 | + 'data.video', 'data.unknown', 'data.parquet.index', |
| 505 | + 'snapshot-1', 'snapshot-1.blob'): |
| 506 | + with self.subTest(disk=disk, whitelist=whitelist, name=name): |
| 507 | + with tempfile.TemporaryDirectory() as directory: |
| 508 | + cache = (LocalDiskCacheManager(directory, 1024, block_size=4) if disk |
| 509 | + else LocalMemoryCacheManager(1024, block_size=4)) |
| 510 | + delegate = self._make_delegate({name: b'abcdefgh'}) |
| 511 | + caching_io = CachingFileIO( |
| 512 | + delegate, cache, FileType.parse_whitelist(whitelist)) |
| 513 | + if whitelist == '*': |
| 514 | + cached = True |
| 515 | + elif whitelist == 'data': |
| 516 | + cached = name.startswith('data.') and not name.endswith('.index') |
| 517 | + elif whitelist == 'structured-data': |
| 518 | + cached = name in ( |
| 519 | + 'data.parquet', 'data.orc', 'data.avro', |
| 520 | + 'data.row', 'data.mosaic') |
| 521 | + elif whitelist == 'meta,global-index,structured-data': |
| 522 | + cached = name in ( |
| 523 | + 'data.parquet', 'data.orc', 'data.avro', 'data.row', |
| 524 | + 'data.mosaic', 'snapshot-1', 'snapshot-1.blob') |
| 525 | + else: |
| 526 | + cached = name.startswith('snapshot-') |
| 527 | + for _ in range(2): |
| 528 | + with caching_io.new_input_stream(name) as stream: |
| 529 | + stream.seek(1) |
| 530 | + self.assertEqual(b'bcdefg', stream.read(6)) |
| 531 | + self.assertEqual(1 if cached else 2, |
| 532 | + delegate.new_input_stream.call_count) |
| 533 | + if not cached: |
| 534 | + delegate.get_file_size.assert_not_called() |
| 535 | + |
| 536 | + def test_data_whitelist_still_caches_blob(self): |
| 537 | + from pypaimon.utils.file_type import FileType |
| 538 | + delegate = self._make_delegate({"data.blob": b"abcdefgh"}) |
| 539 | + cache = LocalDiskCacheManager(self.cache_dir, 1024, block_size=4) |
| 540 | + caching_io = CachingFileIO(delegate, cache, {FileType.DATA}) |
| 541 | + for _ in range(2): |
| 542 | + with caching_io.new_input_stream("data.blob") as stream: |
| 543 | + self.assertEqual(b"abcdefgh", stream.read()) |
| 544 | + delegate.new_input_stream.assert_called_once() |
| 545 | + |
475 | 546 | def test_data_file_not_cached(self): |
476 | 547 | data = b"data content" |
477 | 548 | delegate = self._make_delegate({"data-abc.orc": data}) |
|
0 commit comments