This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 18a19e1c05 [python] Avoid full-file blob rewrite on partial row 
updates (#8234)
18a19e1c05 is described below

commit 18a19e1c0574f078ac83ad581f433606972f4cc6
Author: XiaoHongbo <[email protected]>
AuthorDate: Sun Jun 14 19:05:46 2026 +0800

    [python] Avoid full-file blob rewrite on partial row updates (#8234)
    
    Updating one blob row via `_ROW_ID` produces a new blob delta file whose
    `row_count` and `row_id_range` cover the **entire** original file's row
    range, fully overlapping it. For a 1-of-100 update, the new file shows
    `row_count=100`, `row_range=[0,99]` — identical to the original at the
    metadata level.
    
    After repeated partial updates, manifest entries grow with the table
    size (not the update size), and every read has to walk a chain of
    overlapping delta files.
---
 paimon-python/pypaimon/tests/blob_table_test.py    | 390 ++++++++++++++++++++-
 .../pypaimon/write/table_update_by_row_id.py       |  77 ++--
 2 files changed, 427 insertions(+), 40 deletions(-)

diff --git a/paimon-python/pypaimon/tests/blob_table_test.py 
b/paimon-python/pypaimon/tests/blob_table_test.py
index 9d87716a08..24b527c49b 100755
--- a/paimon-python/pypaimon/tests/blob_table_test.py
+++ b/paimon-python/pypaimon/tests/blob_table_test.py
@@ -1103,24 +1103,24 @@ class DedicatedFormatWriterTest(unittest.TestCase):
 
         update_files = [f for msg in update_messages for f in msg.new_files]
         update_blob_files = [f for f in update_files if 
f.file_name.endswith('.blob')]
-        self.assertGreater(len(update_blob_files), 0)
+        self.assertEqual(len(update_blob_files), 1)
+        self.assertEqual(update_blob_files[0].row_count, 2)
+        self.assertEqual(update_blob_files[0].first_row_id, 0)
         self.assertTrue(all(f.write_cols == ['blob_data'] for f in 
update_files))
-        update_blob_lengths = []
         blob_fields = [field for field in table.fields if field.name == 
'blob_data']
-        for blob_file in update_blob_files:
-            blob_reader = FormatBlobReader(
-                file_io=table.file_io,
-                file_path=blob_file.file_path,
-                read_fields=['blob_data'],
-                full_fields=blob_fields,
-                push_down_predicate=None,
-                blob_as_descriptor=False,
-            )
-            update_blob_lengths.extend(blob_reader.blob_lengths)
-            blob_reader.close()
+        blob_reader = FormatBlobReader(
+            file_io=table.file_io,
+            file_path=update_blob_files[0].file_path,
+            read_fields=['blob_data'],
+            full_fields=blob_fields,
+            push_down_predicate=None,
+            blob_as_descriptor=False,
+        )
+        update_blob_lengths = list(blob_reader.blob_lengths)
+        blob_reader.close()
         self.assertEqual(
             update_blob_lengths.count(BlobFormatWriter.PLACE_HOLDER_LENGTH),
-            2,
+            1,
         )
 
         read_builder = table.new_read_builder()
@@ -1135,6 +1135,366 @@ class DedicatedFormatWriterTest(unittest.TestCase):
             3: b'blob-3',
         })
 
+    def test_blob_update_single_row_at_first_position(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_data', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_first_row', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_first_row')
+
+        initial = pa.Table.from_pydict({
+            'id': list(range(10)),
+            'blob_data': [f'blob-{i}'.encode() for i in range(10)],
+        }, schema=pa_schema)
+        write_builder = table.new_batch_write_builder()
+        writer = write_builder.new_write()
+        writer.write_arrow(initial)
+        write_builder.new_commit().commit(writer.prepare_commit())
+        writer.close()
+
+        table = self.catalog.get_table('test_db.blob_update_first_row')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_data'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array([0], type=pa.int64()),
+            'blob_data': pa.array([b'updated-0'], type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 1)
+        self.assertEqual(update_blob_files[0].row_count, 1)
+        self.assertEqual(update_blob_files[0].first_row_id, 0)
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        by_id = {
+            row['id']: row['blob_data']
+            for row in result.select(['id', 'blob_data']).to_pylist()
+        }
+        self.assertEqual(by_id[0], b'updated-0')
+        self.assertEqual(by_id[9], b'blob-9')
+
+    def test_blob_update_all_rows_full_span(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_data', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_full', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_full')
+
+        num_rows = 5
+        initial = pa.Table.from_pydict({
+            'id': list(range(num_rows)),
+            'blob_data': [f'blob-{i}'.encode() for i in range(num_rows)],
+        }, schema=pa_schema)
+        write_builder = table.new_batch_write_builder()
+        writer = write_builder.new_write()
+        writer.write_arrow(initial)
+        write_builder.new_commit().commit(writer.prepare_commit())
+        writer.close()
+
+        table = self.catalog.get_table('test_db.blob_update_full')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_data'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array(list(range(num_rows)), type=pa.int64()),
+            'blob_data': pa.array(
+                [f'updated-{i}'.encode() for i in range(num_rows)],
+                type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 1)
+        self.assertEqual(update_blob_files[0].row_count, num_rows)
+        self.assertEqual(update_blob_files[0].first_row_id, 0)
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        by_id = {
+            row['id']: row['blob_data']
+            for row in result.select(['id', 'blob_data']).to_pylist()
+        }
+        for i in range(num_rows):
+            self.assertEqual(by_id[i], f'updated-{i}'.encode())
+
+    def test_blob_update_multiple_blob_columns(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_a', pa.large_binary()),
+            ('blob_b', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_two_cols', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_two_cols')
+
+        initial = pa.Table.from_pydict({
+            'id': list(range(5)),
+            'blob_a': [f'a-{i}'.encode() for i in range(5)],
+            'blob_b': [f'b-{i}'.encode() for i in range(5)],
+        }, schema=pa_schema)
+        write_builder = table.new_batch_write_builder()
+        writer = write_builder.new_write()
+        writer.write_arrow(initial)
+        write_builder.new_commit().commit(writer.prepare_commit())
+        writer.close()
+
+        table = self.catalog.get_table('test_db.blob_update_two_cols')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_a', 'blob_b'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array([2], type=pa.int64()),
+            'blob_a': pa.array([b'a-updated'], type=pa.large_binary()),
+            'blob_b': pa.array([b'b-updated'], type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 2)
+        for f in update_blob_files:
+            self.assertEqual(f.row_count, 3)
+            self.assertEqual(f.first_row_id, 0)
+        cols_written = sorted(f.write_cols[0] for f in update_blob_files)
+        self.assertEqual(cols_written, ['blob_a', 'blob_b'])
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        rows = result.select(['id', 'blob_a', 'blob_b']).to_pylist()
+        by_id = {r['id']: r for r in rows}
+        self.assertEqual(by_id[2]['blob_a'], b'a-updated')
+        self.assertEqual(by_id[2]['blob_b'], b'b-updated')
+        self.assertEqual(by_id[0]['blob_a'], b'a-0')
+        self.assertEqual(by_id[4]['blob_b'], b'b-4')
+
+    def test_blob_update_across_multiple_file_groups(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_data', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_multi_group', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_multi_group')
+
+        # Two separate commits -> two data files with distinct first_row_id.
+        first = pa.Table.from_pydict({
+            'id': [0, 1, 2, 3, 4],
+            'blob_data': [f'a-{i}'.encode() for i in range(5)],
+        }, schema=pa_schema)
+        wb = table.new_batch_write_builder()
+        w = wb.new_write()
+        w.write_arrow(first)
+        wb.new_commit().commit(w.prepare_commit())
+        w.close()
+
+        table = self.catalog.get_table('test_db.blob_update_multi_group')
+        second = pa.Table.from_pydict({
+            'id': [5, 6, 7, 8, 9],
+            'blob_data': [f'b-{i}'.encode() for i in range(5)],
+        }, schema=pa_schema)
+        wb = table.new_batch_write_builder()
+        w = wb.new_write()
+        w.write_arrow(second)
+        wb.new_commit().commit(w.prepare_commit())
+        w.close()
+
+        # Touch one row in each group: _ROW_ID 0 (group A, pos 0)
+        # and _ROW_ID 7 (group B, pos 2).
+        table = self.catalog.get_table('test_db.blob_update_multi_group')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_data'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array([0, 7], type=pa.int64()),
+            'blob_data': pa.array(
+                [b'updated-0', b'updated-7'],
+                type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 2)
+        by_first = {f.first_row_id: f for f in update_blob_files}
+        self.assertEqual(set(by_first), {0, 5})
+        self.assertEqual(by_first[0].row_count, 1)
+        self.assertEqual(by_first[5].row_count, 3)
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        by_id = {
+            row['id']: row['blob_data']
+            for row in result.select(['id', 'blob_data']).to_pylist()
+        }
+        self.assertEqual(by_id[0], b'updated-0')
+        self.assertEqual(by_id[7], b'updated-7')
+        self.assertEqual(by_id[3], b'a-3')
+        self.assertEqual(by_id[5], b'b-0')
+
+    def test_blob_update_scattered_endpoints_spans_full_range(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_data', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_scattered', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_scattered')
+
+        num_rows = 100
+        initial = pa.Table.from_pydict({
+            'id': list(range(num_rows)),
+            'blob_data': [f'blob-{i}'.encode() for i in range(num_rows)],
+        }, schema=pa_schema)
+        write_builder = table.new_batch_write_builder()
+        writer = write_builder.new_write()
+        writer.write_arrow(initial)
+        write_builder.new_commit().commit(writer.prepare_commit())
+        writer.close()
+
+        table = self.catalog.get_table('test_db.blob_update_scattered')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_data'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array([0, 99], type=pa.int64()),
+            'blob_data': pa.array(
+                [b'updated-blob-0', b'updated-blob-99'],
+                type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 1)
+        self.assertEqual(update_blob_files[0].row_count, num_rows)
+        self.assertEqual(update_blob_files[0].first_row_id, 0)
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        by_id = {
+            row['id']: row['blob_data']
+            for row in result.select(['id', 'blob_data']).to_pylist()
+        }
+        self.assertEqual(by_id[0], b'updated-blob-0')
+        self.assertEqual(by_id[99], b'updated-blob-99')
+        self.assertEqual(by_id[50], b'blob-50')
+
+    def test_blob_update_shrinks_delta_to_max_updated_row(self):
+        from pypaimon import Schema
+
+        pa_schema = pa.schema([
+            ('id', pa.int32()),
+            ('blob_data', pa.large_binary()),
+        ])
+        schema = Schema.from_pyarrow_schema(pa_schema, options={
+            'row-tracking.enabled': 'true',
+            'data-evolution.enabled': 'true',
+        })
+        self.catalog.create_table(
+            'test_db.blob_update_max_span', schema, False)
+        table = self.catalog.get_table('test_db.blob_update_max_span')
+
+        num_rows = 100
+        initial = pa.Table.from_pydict({
+            'id': list(range(num_rows)),
+            'blob_data': [f'blob-{i}'.encode() for i in range(num_rows)],
+        }, schema=pa_schema)
+        write_builder = table.new_batch_write_builder()
+        writer = write_builder.new_write()
+        writer.write_arrow(initial)
+        write_builder.new_commit().commit(writer.prepare_commit())
+        writer.close()
+
+        table = self.catalog.get_table('test_db.blob_update_max_span')
+        update_builder = table.new_batch_write_builder()
+        table_update = update_builder.new_update().with_update_type(
+            ['blob_data'])
+        update_data = pa.Table.from_pydict({
+            '_ROW_ID': pa.array([50], type=pa.int64()),
+            'blob_data': pa.array(
+                [b'updated-blob-50'], type=pa.large_binary()),
+        })
+        update_messages = table_update.update_by_arrow_with_row_id(update_data)
+        update_builder.new_commit().commit(update_messages)
+
+        update_blob_files = [
+            f for msg in update_messages for f in msg.new_files
+            if f.file_name.endswith('.blob')
+        ]
+        self.assertEqual(len(update_blob_files), 1)
+        self.assertEqual(update_blob_files[0].row_count, 51)
+        self.assertEqual(update_blob_files[0].first_row_id, 0)
+
+        read_builder = table.new_read_builder()
+        result = read_builder.new_read().to_arrow(
+            read_builder.new_scan().plan().splits())
+        by_id = {
+            row['id']: row['blob_data']
+            for row in result.select(['id', 'blob_data']).to_pylist()
+        }
+        self.assertEqual(by_id[50], b'updated-blob-50')
+        self.assertEqual(by_id[0], b'blob-0')
+        self.assertEqual(by_id[99], b'blob-99')
+
     def test_update_blob_column_with_rolling_files(self):
         from pypaimon import Schema
 
@@ -1286,7 +1646,7 @@ class DedicatedFormatWriterTest(unittest.TestCase):
             blob_reader.close()
         self.assertEqual(
             update_blob_lengths.count(BlobFormatWriter.PLACE_HOLDER_LENGTH),
-            3,
+            2,
         )
 
         update_builder.new_commit().commit(update_messages)
diff --git a/paimon-python/pypaimon/write/table_update_by_row_id.py 
b/paimon-python/pypaimon/write/table_update_by_row_id.py
index 130d2763a2..d5653cce98 100644
--- a/paimon-python/pypaimon/write/table_update_by_row_id.py
+++ b/paimon-python/pypaimon/write/table_update_by_row_id.py
@@ -57,6 +57,12 @@ class TableUpdateByRowId:
 
     This update is designed for adding/updating specific columns in existing 
tables.
     Input data should contain _ROW_ID column.
+
+    Python is the writer-side source of truth for update-by-row-id. The Java
+    side ships the read path (``BlobFallbackRecordReader``) and pins the
+    on-disk blob delta layout via ``BlobUpdateTest`` only; there is no Java
+    writer. Changes to the blob delta layout here must stay compatible with
+    that reader.
     """
 
     FIRST_ROW_ID_COLUMN = '_FIRST_ROW_ID'
@@ -270,12 +276,22 @@ class TableUpdateByRowId:
             original_data: Optional[pa.Table],
             update_data: pa.Table,
             column_names: List[str],
-            first_row_id: int) -> Tuple[Optional[pa.Table], Dict[str, 
List[object]]]:
+            first_row_id: int,
+    ) -> Tuple[Optional[pa.Table], Dict[str, List[object]]]:
         """Merge update data with original data, preserving row order.
 
         For rows that have updates, use the update values.
         For rows without updates, use the original values (if available).
 
+        Blob delta files cover ``[first_row_id, first_row_id + 
max_updated_pos]``
+        — anchored at the original file's first_row_id, spanning up to and
+        including the last updated row. The span is NOT shrunk at the head:
+        ``BlobFallbackRecordReader`` resolves placeholders by relative offset
+        from the delta file's ``first_row_id``, so anchoring anywhere other
+        than the original ``first_row_id`` would misalign unchanged rows
+        before ``min_updated_pos`` with the older blob file. This anchor is
+        the same in every blob column being updated.
+
         Args:
             original_data: Original data from the file (may be None if no 
columns need to be read)
             update_data: Update data (may contain only partial rows)
@@ -283,7 +299,8 @@ class TableUpdateByRowId:
             first_row_id: The first_row_id of this file group
 
         Returns:
-            Normal merged PyArrow Table and blob values to write row-by-row.
+            Normal merged PyArrow Table, and per-blob-column values list. All
+            blob value lists have the same length (= ``max_updated_pos + 1``).
         """
 
         # Get the _ROW_ID values from update_data to determine which rows are 
being updated
@@ -298,7 +315,7 @@ class TableUpdateByRowId:
 
         # Build the merged table column by column
         merged_columns = {}
-        blob_columns = {}
+        blob_columns: Dict[str, List[object]] = {}
         update_by_col = {
             col_name: update_data[col_name].combine_chunks()
             for col_name in column_names
@@ -307,33 +324,36 @@ class TableUpdateByRowId:
             int(relative_index.as_py()): idx
             for idx, relative_index in enumerate(relative_indices)
         }
+        # Caller (_write_by_first_row_id) only enters this method with a
+        # non-empty group, so update_positions is non-empty here.
+        blob_row_count = max(update_positions) + 1
         for col_name in column_names:
             update_col = update_by_col[col_name]
-            original_col = original_data[col_name].combine_chunks()
-            if update_col.type != original_col.type:
-                update_col = self._coerce_column(
-                    update_col, original_col.type)
             if self._is_blob_column(col_name):
                 blob_columns[col_name] = [
                     update_col[update_positions[i]].as_py()
                     if i in update_positions
                     else Blob.PLACE_HOLDER
-                    for i in range(original_data.num_rows)
+                    for i in range(blob_row_count)
                 ]
-            else:
-                try:
-                    merged_columns[col_name] = pc.replace_with_mask(
-                        original_col, mask, update_col)
-                except pa.lib.ArrowNotImplementedError:
-                    n = original_data.num_rows
-                    combined = pa.concat_arrays(
-                        [original_col, update_col])
-                    offset = len(original_col)
-                    indices = np.arange(n, dtype=np.int64)
-                    for orig_pos, upd_idx in update_positions.items():
-                        indices[orig_pos] = offset + upd_idx
-                    merged_columns[col_name] = combined.take(
-                        pa.array(indices))
+                continue
+            original_col = original_data[col_name].combine_chunks()
+            if update_col.type != original_col.type:
+                update_col = self._coerce_column(
+                    update_col, original_col.type)
+            try:
+                merged_columns[col_name] = pc.replace_with_mask(
+                    original_col, mask, update_col)
+            except pa.lib.ArrowNotImplementedError:
+                n = original_data.num_rows
+                combined = pa.concat_arrays(
+                    [original_col, update_col])
+                offset = len(original_col)
+                indices = np.arange(n, dtype=np.int64)
+                for orig_pos, upd_idx in update_positions.items():
+                    indices[orig_pos] = offset + upd_idx
+                merged_columns[col_name] = combined.take(
+                    pa.array(indices))
 
         merged_table = pa.table(merged_columns) if merged_columns else None
 
@@ -419,7 +439,7 @@ class TableUpdateByRowId:
 
             if new_files:
                 self._assign_update_file_metadata(
-                    new_files, first_row_id, column_names, 
original_data.num_rows)
+                    new_files, first_row_id, column_names, blob_columns)
                 self.commit_messages.append(
                     CommitMessage(
                         partition=partition_tuple,
@@ -436,8 +456,15 @@ class TableUpdateByRowId:
 
     @staticmethod
     def _assign_update_file_metadata(new_files: List[DataFileMeta], 
first_row_id: int,
-                                     column_names: List[str], 
expected_row_count: int):
-        blob_end = first_row_id + expected_row_count
+                                     column_names: List[str],
+                                     blob_columns: Dict[str, List[object]]):
+        # All blob columns share the same anchored span (see
+        # _merge_update_with_original docstring), so any column's length is
+        # the per-blob delta-file row count.
+        blob_row_count = (
+            len(next(iter(blob_columns.values()))) if blob_columns else 0
+        )
+        blob_end = first_row_id + blob_row_count
         blob_starts = {}
         # BlobWriter.prepare_commit preserves write/rolling order, which is 
required
         # for assigning continuous row-id ranges to rolled blob files.

Reply via email to