mirror of
https://github.com/lancedb/lancedb.git
synced 2026-09-12 16:22:24 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bfd475702 | ||
|
|
670bda8725 | ||
|
|
10151b2dc8 | ||
|
|
53b4c3b715 | ||
|
|
638430fdb4 | ||
|
|
2221b8df6a | ||
|
|
14eeae4bd4 | ||
|
|
320755ed55 | ||
|
|
e55c2da7b1 | ||
|
|
d33b05328c | ||
|
|
82f5355b71 | ||
|
|
40cff9b644 | ||
|
|
edf95e53fc | ||
|
|
0b5eba085d | ||
|
|
21bf859c0b | ||
|
|
e0499de959 |
@@ -42,6 +42,8 @@ listing a storage directory.
|
|||||||
|
|
||||||
::: lancedb.table.Table
|
::: lancedb.table.Table
|
||||||
|
|
||||||
|
::: lancedb.table.CompactionOptions
|
||||||
|
|
||||||
::: lancedb.table.FragmentStatistics
|
::: lancedb.table.FragmentStatistics
|
||||||
|
|
||||||
::: lancedb.table.FragmentSummaryStats
|
::: lancedb.table.FragmentSummaryStats
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from .materialized_view import (
|
|||||||
MaterializedView,
|
MaterializedView,
|
||||||
MaterializedViewDefinition,
|
MaterializedViewDefinition,
|
||||||
)
|
)
|
||||||
from .table import AsyncTable, Table
|
from .table import AsyncTable, CompactionOptions, Table
|
||||||
from .types import BaseTokenizerType
|
from .types import BaseTokenizerType
|
||||||
from ._lancedb import Session
|
from ._lancedb import Session
|
||||||
from .namespace import (
|
from .namespace import (
|
||||||
@@ -558,6 +558,7 @@ __all__ = [
|
|||||||
"AsyncJob",
|
"AsyncJob",
|
||||||
"AsyncLanceNamespaceDBConnection",
|
"AsyncLanceNamespaceDBConnection",
|
||||||
"AsyncTable",
|
"AsyncTable",
|
||||||
|
"CompactionOptions",
|
||||||
"FtsToken",
|
"FtsToken",
|
||||||
"col",
|
"col",
|
||||||
"Expr",
|
"Expr",
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ class Table:
|
|||||||
*,
|
*,
|
||||||
cleanup_since_ms: Optional[int] = None,
|
cleanup_since_ms: Optional[int] = None,
|
||||||
delete_unverified: Optional[bool] = None,
|
delete_unverified: Optional[bool] = None,
|
||||||
|
compaction_options: Optional[Dict[str, Any]] = None,
|
||||||
) -> OptimizeStats: ...
|
) -> OptimizeStats: ...
|
||||||
async def uri(self) -> str: ...
|
async def uri(self) -> str: ...
|
||||||
async def initial_storage_options(self) -> Optional[Dict[str, str]]: ...
|
async def initial_storage_options(self) -> Optional[Dict[str, str]]: ...
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ from ..table import (
|
|||||||
AsyncTable,
|
AsyncTable,
|
||||||
BlobMode,
|
BlobMode,
|
||||||
Branches,
|
Branches,
|
||||||
|
CompactionOptions,
|
||||||
IndexStatistics,
|
IndexStatistics,
|
||||||
Query,
|
Query,
|
||||||
Table,
|
Table,
|
||||||
@@ -961,6 +962,7 @@ class RemoteTable(Table):
|
|||||||
*,
|
*,
|
||||||
cleanup_older_than: Optional[timedelta] = None,
|
cleanup_older_than: Optional[timedelta] = None,
|
||||||
delete_unverified: bool = False,
|
delete_unverified: bool = False,
|
||||||
|
compaction_options: Optional[CompactionOptions] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
optimize() is a no-op on LanceDB Cloud.
|
optimize() is a no-op on LanceDB Cloud.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
TypedDict,
|
||||||
Union,
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
@@ -227,6 +228,88 @@ IndexConfigType = Union[
|
|||||||
FTS,
|
FTS,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompactionOptions(TypedDict, total=False):
|
||||||
|
"""Options that control file compaction during table optimization.
|
||||||
|
|
||||||
|
Unspecified options use Lance's defaults.
|
||||||
|
|
||||||
|
Compaction planning is row based. Lowering ``target_rows_per_fragment``
|
||||||
|
based on the expected row size can bound later compaction passes once
|
||||||
|
oversized fragments have been rewritten. It does not split an existing
|
||||||
|
fragment, so the first pass over an oversized fragment is not subject to
|
||||||
|
that bound. ``max_bytes_per_file`` limits output file size, not compaction
|
||||||
|
memory. Source budgets keep whole planned tasks; if the first task exceeds
|
||||||
|
a budget, that run performs no compaction work.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
Derive a steady-state row target from the expected row size:
|
||||||
|
|
||||||
|
>>> desired_fragment_bytes = 750 * 1024 * 1024
|
||||||
|
>>> average_row_bytes = 1_500_000
|
||||||
|
>>> options: CompactionOptions = {
|
||||||
|
... "target_rows_per_fragment": max(
|
||||||
|
... 1, desired_fragment_bytes // average_row_bytes
|
||||||
|
... ),
|
||||||
|
... }
|
||||||
|
>>> await table.optimize(compaction_options=options) # doctest: +SKIP
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_rows_per_fragment: int
|
||||||
|
"""Target rows per fragment; existing oversized fragments are not split."""
|
||||||
|
|
||||||
|
max_rows_per_group: int
|
||||||
|
"""Maximum number of rows per row group (default: 1,024)."""
|
||||||
|
|
||||||
|
max_bytes_per_file: Optional[int]
|
||||||
|
"""Maximum output data-file size; this does not bound compaction memory."""
|
||||||
|
|
||||||
|
materialize_deletions: bool
|
||||||
|
"""Whether to rewrite fragments containing deleted rows (default: True)."""
|
||||||
|
|
||||||
|
materialize_deletions_threshold: float
|
||||||
|
"""Minimum deleted-row fraction that makes a fragment eligible (default: 0.1)."""
|
||||||
|
|
||||||
|
num_threads: Optional[int]
|
||||||
|
"""Number of compaction tasks to run in parallel."""
|
||||||
|
|
||||||
|
batch_size: Optional[int]
|
||||||
|
"""Number of rows per input scan batch."""
|
||||||
|
|
||||||
|
io_buffer_size: Optional[int]
|
||||||
|
"""Maximum number of bytes queued in the input scan I/O buffer."""
|
||||||
|
|
||||||
|
defer_index_remap: bool
|
||||||
|
"""Whether to defer index remapping during compaction (default: False)."""
|
||||||
|
|
||||||
|
index_remap_mode: Literal["direct", "compact"]
|
||||||
|
"""How to construct the old-to-new row-address mapping."""
|
||||||
|
|
||||||
|
compaction_mode: Optional[
|
||||||
|
Literal["reencode", "try_binary_copy", "force_binary_copy"]
|
||||||
|
]
|
||||||
|
"""Whether compaction re-encodes data or uses binary copying."""
|
||||||
|
|
||||||
|
binary_copy_read_batch_bytes: Optional[int]
|
||||||
|
"""Number of bytes read per batch during binary-copy compaction."""
|
||||||
|
|
||||||
|
max_source_fragments: Optional[int]
|
||||||
|
"""Maximum number of source fragments compacted in one run."""
|
||||||
|
|
||||||
|
max_source_rows: Optional[int]
|
||||||
|
"""Maximum live source rows per run, applied to whole planned tasks."""
|
||||||
|
|
||||||
|
max_source_bytes: Optional[int]
|
||||||
|
"""Maximum source bytes per run, applied to whole planned tasks."""
|
||||||
|
|
||||||
|
excluded_fragment_ids: List[int]
|
||||||
|
"""Fragment IDs to leave unchanged and use as planning boundaries."""
|
||||||
|
|
||||||
|
max_overlays_per_fragment: Optional[int]
|
||||||
|
"""Maximum overlays before a fragment is fully compacted."""
|
||||||
|
|
||||||
|
|
||||||
# Known distance metrics for legacy API detection
|
# Known distance metrics for legacy API detection
|
||||||
KNOWN_METRICS = {"l2", "cosine", "dot", "hamming"}
|
KNOWN_METRICS = {"l2", "cosine", "dot", "hamming"}
|
||||||
|
|
||||||
@@ -2049,6 +2132,7 @@ class Table(ABC):
|
|||||||
cleanup_older_than: Optional[timedelta] = None,
|
cleanup_older_than: Optional[timedelta] = None,
|
||||||
delete_unverified: bool = False,
|
delete_unverified: bool = False,
|
||||||
retrain: bool = False,
|
retrain: bool = False,
|
||||||
|
compaction_options: Optional[CompactionOptions] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Optimize the on-disk data and indices for better performance.
|
Optimize the on-disk data and indices for better performance.
|
||||||
@@ -2081,6 +2165,11 @@ class Table(ABC):
|
|||||||
|
|
||||||
retrain: bool, default False
|
retrain: bool, default False
|
||||||
This parameter is no longer used and is deprecated.
|
This parameter is no longer used and is deprecated.
|
||||||
|
compaction_options: CompactionOptions, optional
|
||||||
|
Options that control file compaction. For large rows, derive a lower
|
||||||
|
``target_rows_per_fragment`` from the expected row size to bound later
|
||||||
|
passes. This does not cap the first pass over an existing oversized
|
||||||
|
fragment; see [CompactionOptions][lancedb.table.CompactionOptions].
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
@@ -4201,6 +4290,7 @@ class LanceTable(Table):
|
|||||||
cleanup_older_than: Optional[timedelta] = None,
|
cleanup_older_than: Optional[timedelta] = None,
|
||||||
delete_unverified: bool = False,
|
delete_unverified: bool = False,
|
||||||
retrain: bool = False,
|
retrain: bool = False,
|
||||||
|
compaction_options: Optional[CompactionOptions] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Optimize the on-disk data and indices for better performance.
|
Optimize the on-disk data and indices for better performance.
|
||||||
@@ -4233,6 +4323,11 @@ class LanceTable(Table):
|
|||||||
|
|
||||||
retrain: bool, default False
|
retrain: bool, default False
|
||||||
This parameter is no longer used and is deprecated.
|
This parameter is no longer used and is deprecated.
|
||||||
|
compaction_options: CompactionOptions, optional
|
||||||
|
Options that control file compaction. For large rows, derive a lower
|
||||||
|
``target_rows_per_fragment`` from the expected row size to bound later
|
||||||
|
passes. This does not cap the first pass over an existing oversized
|
||||||
|
fragment; see [CompactionOptions][lancedb.table.CompactionOptions].
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
@@ -4247,6 +4342,7 @@ class LanceTable(Table):
|
|||||||
cleanup_older_than=cleanup_older_than,
|
cleanup_older_than=cleanup_older_than,
|
||||||
delete_unverified=delete_unverified,
|
delete_unverified=delete_unverified,
|
||||||
retrain=retrain,
|
retrain=retrain,
|
||||||
|
compaction_options=compaction_options,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -6648,6 +6744,7 @@ class AsyncTable:
|
|||||||
cleanup_older_than: Optional[timedelta] = None,
|
cleanup_older_than: Optional[timedelta] = None,
|
||||||
delete_unverified: bool = False,
|
delete_unverified: bool = False,
|
||||||
retrain=False,
|
retrain=False,
|
||||||
|
compaction_options: Optional[CompactionOptions] = None,
|
||||||
) -> OptimizeStats:
|
) -> OptimizeStats:
|
||||||
"""
|
"""
|
||||||
Optimize the on-disk data and indices for better performance.
|
Optimize the on-disk data and indices for better performance.
|
||||||
@@ -6680,6 +6777,11 @@ class AsyncTable:
|
|||||||
|
|
||||||
retrain: bool, default False
|
retrain: bool, default False
|
||||||
This parameter is no longer used and is deprecated.
|
This parameter is no longer used and is deprecated.
|
||||||
|
compaction_options: CompactionOptions, optional
|
||||||
|
Options that control file compaction. For large rows, derive a lower
|
||||||
|
``target_rows_per_fragment`` from the expected row size to bound later
|
||||||
|
passes. This does not cap the first pass over an existing oversized
|
||||||
|
fragment; see [CompactionOptions][lancedb.table.CompactionOptions].
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
@@ -6706,6 +6808,7 @@ class AsyncTable:
|
|||||||
return await self._inner.optimize(
|
return await self._inner.optimize(
|
||||||
cleanup_since_ms=cleanup_since_ms,
|
cleanup_since_ms=cleanup_since_ms,
|
||||||
delete_unverified=delete_unverified,
|
delete_unverified=delete_unverified,
|
||||||
|
compaction_options=compaction_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def list_indices(self) -> Iterable[IndexConfig]:
|
async def list_indices(self) -> Iterable[IndexConfig]:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import List
|
from typing import Any, List
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import lancedb
|
import lancedb
|
||||||
@@ -3876,6 +3876,100 @@ async def test_optimize(mem_db_async: AsyncConnection):
|
|||||||
assert await table.query().to_arrow() == pa.table({"x": [[1], [2]]})
|
assert await table.query().to_arrow() == pa.table({"x": [[1], [2]]})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_optimize_compaction_options(mem_db_async: AsyncConnection):
|
||||||
|
table = await mem_db_async.create_table("test", data=[{"x": 1}])
|
||||||
|
await table.add([{"x": 2}])
|
||||||
|
|
||||||
|
stats = await table.optimize(
|
||||||
|
compaction_options={
|
||||||
|
"target_rows_per_fragment": 1,
|
||||||
|
"batch_size": 1,
|
||||||
|
"num_threads": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert stats.compaction.fragments_removed == 0
|
||||||
|
assert stats.compaction.fragments_added == 0
|
||||||
|
|
||||||
|
stats = await table.optimize(compaction_options={"target_rows_per_fragment": 3})
|
||||||
|
assert stats.compaction.fragments_removed == 2
|
||||||
|
assert stats.compaction.fragments_added == 1
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid compaction option: unknown"):
|
||||||
|
await table.optimize(compaction_options={"unknown": 1})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("option", ["max_source_rows", "max_source_bytes"])
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_optimize_compaction_source_limits(
|
||||||
|
mem_db_async: AsyncConnection, option: str
|
||||||
|
):
|
||||||
|
table = await mem_db_async.create_table("test", data=[{"x": 1}])
|
||||||
|
await table.add([{"x": 2}])
|
||||||
|
|
||||||
|
stats = await table.optimize(
|
||||||
|
compaction_options={
|
||||||
|
"target_rows_per_fragment": 3,
|
||||||
|
option: 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert stats.compaction.fragments_removed == 0
|
||||||
|
assert stats.compaction.fragments_added == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_optimize_compaction_excluded_fragments(mem_db_async: AsyncConnection):
|
||||||
|
table = await mem_db_async.create_table("test", data=[{"x": 1}])
|
||||||
|
await table.add([{"x": 2}])
|
||||||
|
|
||||||
|
stats = await table.optimize(
|
||||||
|
compaction_options={
|
||||||
|
"target_rows_per_fragment": 3,
|
||||||
|
"excluded_fragment_ids": [0],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert stats.compaction.fragments_removed == 0
|
||||||
|
assert stats.compaction.fragments_added == 0
|
||||||
|
|
||||||
|
stats = await table.optimize(compaction_options={"target_rows_per_fragment": 3})
|
||||||
|
assert stats.compaction.fragments_removed == 2
|
||||||
|
assert stats.compaction.fragments_added == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("option", "value", "message"),
|
||||||
|
[
|
||||||
|
("target_rows_per_fragment", 0, "must be between 1 and 4294967295"),
|
||||||
|
("max_rows_per_group", 0, "must be between 1 and 4294967295"),
|
||||||
|
("batch_size", 0, "must be between 1 and 4294967295"),
|
||||||
|
("num_threads", 0, "must be greater than 0"),
|
||||||
|
("target_rows_per_fragment", 2**32, "must be between 1 and 4294967295"),
|
||||||
|
("max_rows_per_group", 2**32, "must be between 1 and 4294967295"),
|
||||||
|
("batch_size", 2**32, "must be between 1 and 4294967295"),
|
||||||
|
("io_buffer_size", 2**63, "must be at most 9223372036854775807"),
|
||||||
|
("max_source_rows", 0, "must be greater than 0"),
|
||||||
|
("max_source_bytes", 0, "must be greater than 0"),
|
||||||
|
(
|
||||||
|
"excluded_fragment_ids",
|
||||||
|
[-1],
|
||||||
|
"must contain values between 0 and 4294967295",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"excluded_fragment_ids",
|
||||||
|
[2**32],
|
||||||
|
"must contain values between 0 and 4294967295",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_optimize_compaction_options_validation(
|
||||||
|
mem_db_async: AsyncConnection, option: str, value: Any, message: str
|
||||||
|
):
|
||||||
|
table = await mem_db_async.create_table("test", data=[{"x": 1}])
|
||||||
|
with pytest.raises(ValueError, match=message):
|
||||||
|
await table.optimize(compaction_options={option: value})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_optimize_delete_unverified(tmp_db_async: AsyncConnection, tmp_path):
|
async def test_optimize_delete_unverified(tmp_db_async: AsyncConnection, tmp_path):
|
||||||
table = await tmp_db_async.create_table(
|
table = await tmp_db_async.create_table(
|
||||||
|
|||||||
+132
-7
@@ -20,8 +20,9 @@ use arrow::{
|
|||||||
use lancedb::blob::{BlobFile, BlobRangeRequest};
|
use lancedb::blob::{BlobFile, BlobRangeRequest};
|
||||||
use lancedb::index::scalar::FtsIndexBuilder;
|
use lancedb::index::scalar::FtsIndexBuilder;
|
||||||
use lancedb::table::{
|
use lancedb::table::{
|
||||||
AddDataMode, ColumnAlteration, Duration, FieldMetadataUpdate, FtsToken as LanceDbFtsToken,
|
AddDataMode, ColumnAlteration, CompactionMode, CompactionOptions, Duration,
|
||||||
NewColumnTransform, OptimizeAction, OptimizeOptions, Ref, Table as LanceDbTable,
|
FieldMetadataUpdate, FtsToken as LanceDbFtsToken, IndexRemapMode, NewColumnTransform,
|
||||||
|
OptimizeAction, OptimizeOptions, Ref, Table as LanceDbTable,
|
||||||
};
|
};
|
||||||
use lancedb::tokenize as lancedb_tokenize;
|
use lancedb::tokenize as lancedb_tokenize;
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
@@ -100,6 +101,128 @@ enum PredicateArg {
|
|||||||
Sql(String),
|
Sql(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_positive_u32(value: u64, name: &str) -> PyResult<usize> {
|
||||||
|
if !(1..=u32::MAX as u64).contains(&value) {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"{name} must be between 1 and {}",
|
||||||
|
u32::MAX
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(value as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn positive_u32(value: &Bound<'_, PyAny>, name: &str) -> PyResult<usize> {
|
||||||
|
validate_positive_u32(value.extract()?, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_positive_u32(value: &Bound<'_, PyAny>, name: &str) -> PyResult<Option<usize>> {
|
||||||
|
value
|
||||||
|
.extract::<Option<u64>>()?
|
||||||
|
.map(|value| validate_positive_u32(value, name))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_positive_usize(value: &Bound<'_, PyAny>, name: &str) -> PyResult<Option<usize>> {
|
||||||
|
let value: Option<usize> = value.extract()?;
|
||||||
|
if value == Some(0) {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"{name} must be greater than 0"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_positive_u64(value: &Bound<'_, PyAny>, name: &str) -> PyResult<Option<u64>> {
|
||||||
|
let value: Option<u64> = value.extract()?;
|
||||||
|
if value == Some(0) {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"{name} must be greater than 0"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u32_list(value: &Bound<'_, PyAny>, name: &str) -> PyResult<Vec<u32>> {
|
||||||
|
value
|
||||||
|
.extract::<Vec<i64>>()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|value| {
|
||||||
|
u32::try_from(value).map_err(|_| {
|
||||||
|
PyValueError::new_err(format!(
|
||||||
|
"{name} must contain values between 0 and {}",
|
||||||
|
u32::MAX
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_i64_bounded_u64(value: &Bound<'_, PyAny>, name: &str) -> PyResult<Option<u64>> {
|
||||||
|
let value: Option<u64> = value.extract()?;
|
||||||
|
if value.is_some_and(|value| value > i64::MAX as u64) {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"{name} must be at most {}",
|
||||||
|
i64::MAX
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_compaction_options(options: Option<&Bound<'_, PyDict>>) -> PyResult<CompactionOptions> {
|
||||||
|
let mut parsed = CompactionOptions::default();
|
||||||
|
let Some(options) = options else {
|
||||||
|
return Ok(parsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in options.iter() {
|
||||||
|
let key: String = key.extract()?;
|
||||||
|
match key.as_str() {
|
||||||
|
"target_rows_per_fragment" => {
|
||||||
|
parsed.target_rows_per_fragment = positive_u32(&value, &key)?
|
||||||
|
}
|
||||||
|
"max_rows_per_group" => parsed.max_rows_per_group = positive_u32(&value, &key)?,
|
||||||
|
"max_bytes_per_file" => parsed.max_bytes_per_file = value.extract()?,
|
||||||
|
"materialize_deletions" => parsed.materialize_deletions = value.extract()?,
|
||||||
|
"materialize_deletions_threshold" => {
|
||||||
|
parsed.materialize_deletions_threshold = value.extract()?
|
||||||
|
}
|
||||||
|
"num_threads" => parsed.num_threads = optional_positive_usize(&value, &key)?,
|
||||||
|
"batch_size" => parsed.batch_size = optional_positive_u32(&value, &key)?,
|
||||||
|
"io_buffer_size" => parsed.io_buffer_size = optional_i64_bounded_u64(&value, &key)?,
|
||||||
|
"defer_index_remap" => parsed.defer_index_remap = value.extract()?,
|
||||||
|
"index_remap_mode" => {
|
||||||
|
let mode: String = value.extract()?;
|
||||||
|
parsed.index_remap_mode = IndexRemapMode::try_from(mode.as_str())
|
||||||
|
.map_err(|err| PyValueError::new_err(err.to_string()))?;
|
||||||
|
}
|
||||||
|
"compaction_mode" => {
|
||||||
|
let mode: Option<String> = value.extract()?;
|
||||||
|
parsed.compaction_mode = mode
|
||||||
|
.map(|mode| {
|
||||||
|
CompactionMode::try_from(mode.as_str())
|
||||||
|
.map_err(|err| PyValueError::new_err(err.to_string()))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
}
|
||||||
|
"binary_copy_read_batch_bytes" => {
|
||||||
|
parsed.binary_copy_read_batch_bytes = value.extract()?
|
||||||
|
}
|
||||||
|
"max_source_fragments" => parsed.max_source_fragments = value.extract()?,
|
||||||
|
"max_source_rows" => parsed.max_source_rows = optional_positive_usize(&value, &key)?,
|
||||||
|
"max_source_bytes" => parsed.max_source_bytes = optional_positive_u64(&value, &key)?,
|
||||||
|
"excluded_fragment_ids" => parsed.excluded_fragment_ids = u32_list(&value, &key)?,
|
||||||
|
"max_overlays_per_fragment" => parsed.max_overlays_per_fragment = value.extract()?,
|
||||||
|
_ => {
|
||||||
|
return Err(PyValueError::new_err(format!(
|
||||||
|
"Invalid compaction option: {key}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Statistics about a compaction operation.
|
/// Statistics about a compaction operation.
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all, from_py_object)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -1340,13 +1463,15 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize the on-disk data by compacting and pruning old data, for better performance.
|
/// Optimize the on-disk data by compacting and pruning old data, for better performance.
|
||||||
#[pyo3(signature = (cleanup_since_ms=None, delete_unverified=None))]
|
#[pyo3(signature = (cleanup_since_ms=None, delete_unverified=None, compaction_options=None))]
|
||||||
pub fn optimize(
|
pub fn optimize<'py>(
|
||||||
self_: PyRef<'_, Self>,
|
self_: PyRef<'py, Self>,
|
||||||
cleanup_since_ms: Option<u64>,
|
cleanup_since_ms: Option<u64>,
|
||||||
delete_unverified: Option<bool>,
|
delete_unverified: Option<bool>,
|
||||||
) -> PyResult<Bound<'_, PyAny>> {
|
compaction_options: Option<&Bound<'py, PyDict>>,
|
||||||
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
|
let compaction_options = parse_compaction_options(compaction_options)?;
|
||||||
let older_than = if let Some(ms) = cleanup_since_ms {
|
let older_than = if let Some(ms) = cleanup_since_ms {
|
||||||
if ms > i64::MAX as u64 {
|
if ms > i64::MAX as u64 {
|
||||||
return Err(PyValueError::new_err(format!(
|
return Err(PyValueError::new_err(format!(
|
||||||
@@ -1362,7 +1487,7 @@ impl Table {
|
|||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
let compaction_stats = inner
|
let compaction_stats = inner
|
||||||
.optimize(OptimizeAction::Compact {
|
.optimize(OptimizeAction::Compact {
|
||||||
options: lancedb::table::CompactionOptions::default(),
|
options: compaction_options,
|
||||||
remap_options: None,
|
remap_options: None,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ pub use lance::dataset::refs::{BranchContents, Ref, TagContents, Tags as LanceTa
|
|||||||
pub use lance::dataset::scanner::DatasetRecordBatchStream;
|
pub use lance::dataset::scanner::DatasetRecordBatchStream;
|
||||||
pub use lance_index::optimize::OptimizeOptions;
|
pub use lance_index::optimize::OptimizeOptions;
|
||||||
pub use lsm_stats::{BucketStats, GenerationStats, LsmStats, MemtableStats};
|
pub use lsm_stats::{BucketStats, GenerationStats, LsmStats, MemtableStats};
|
||||||
pub use optimize::{CompactionOptions, OptimizeAction, OptimizeStats};
|
pub use optimize::{
|
||||||
|
CompactionMode, CompactionOptions, IndexRemapMode, OptimizeAction, OptimizeStats,
|
||||||
|
};
|
||||||
pub use refresh::RefreshColumnResult;
|
pub use refresh::RefreshColumnResult;
|
||||||
pub use schema_evolution::{
|
pub use schema_evolution::{
|
||||||
AddColumnsResult, AlterColumnsResult, DropColumnsResult, FieldMetadataUpdate,
|
AddColumnsResult, AlterColumnsResult, DropColumnsResult, FieldMetadataUpdate,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use lance_index::optimize::OptimizeOptions;
|
|||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
pub use chrono::Duration;
|
pub use chrono::Duration;
|
||||||
pub use lance::dataset::optimize::CompactionOptions;
|
pub use lance::dataset::optimize::{CompactionMode, CompactionOptions, IndexRemapMode};
|
||||||
|
|
||||||
use super::NativeTable;
|
use super::NativeTable;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|||||||
Reference in New Issue
Block a user