Skip to content

Commit db9b0c5

Browse files
committed
[BugFix] FSE-v2 missed setting the schema for shared-data sorted schema change (backport #72235)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: wanpengfei-git <wanpengfei91@163.com> (cherry picked from commit d70921f) Signed-off-by: PengFei Li <lpengfei2016@gmail.com>
1 parent d9f4c90 commit db9b0c5

6 files changed

Lines changed: 226 additions & 8 deletions

File tree

be/src/storage/lake/delta_writer.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ class DeltaWriterImpl {
108108
int64_t schema_id, const PartialUpdateMode& partial_update_mode,
109109
const std::map<string, string>* column_to_expr_value, PUniqueId load_id,
110110
RuntimeProfile* profile, BundleWritableFileContext* bundle_writable_file_context,
111-
GlobalDictByNameMaps* global_dicts, bool is_multi_statements_txn)
111+
GlobalDictByNameMaps* global_dicts, bool is_multi_statements_txn,
112+
std::shared_ptr<const TabletSchema> tablet_schema)
112113
: _tablet_manager(tablet_manager),
113114
_tablet_id(tablet_id),
114115
_txn_id(txn_id),
@@ -119,6 +120,7 @@ class DeltaWriterImpl {
119120
_mem_tracker(mem_tracker),
120121
_slots(slots),
121122
_max_buffer_size(max_buffer_size > 0 ? max_buffer_size : config::write_buffer_size),
123+
_tablet_schema(std::move(tablet_schema)),
122124
_immutable_tablet_size(immutable_tablet_size),
123125
_merge_condition(std::move(merge_condition)),
124126
_miss_auto_increment_column(miss_auto_increment_column),
@@ -1276,11 +1278,15 @@ StatusOr<DeltaWriterBuilder::DeltaWriterPtr> DeltaWriterBuilder::build() {
12761278
if (UNLIKELY(_schema_id == 0)) {
12771279
return Status::InvalidArgument("schema_id not set");
12781280
}
1279-
auto impl =
1280-
new DeltaWriterImpl(_tablet_mgr, _tablet_id, _txn_id, _partition_id, _slots, _merge_condition,
1281-
_miss_auto_increment_column, _db_id, _table_id, _immutable_tablet_size, _mem_tracker,
1282-
_max_buffer_size, _schema_id, _partial_update_mode, _column_to_expr_value, _load_id,
1283-
_profile, _bundle_writable_file_context, _global_dicts, _is_multi_statements_txn);
1281+
if (UNLIKELY(_tablet_schema != nullptr && _tablet_schema->id() != _schema_id)) {
1282+
return Status::InvalidArgument(
1283+
fmt::format("tablet_schema id {} mismatches schema_id {}", _tablet_schema->id(), _schema_id));
1284+
}
1285+
auto impl = new DeltaWriterImpl(_tablet_mgr, _tablet_id, _txn_id, _partition_id, _slots, _merge_condition,
1286+
_miss_auto_increment_column, _db_id, _table_id, _immutable_tablet_size,
1287+
_mem_tracker, _max_buffer_size, _schema_id, _partial_update_mode,
1288+
_column_to_expr_value, _load_id, _profile, _bundle_writable_file_context,
1289+
_global_dicts, _is_multi_statements_txn, _tablet_schema);
12841290
return std::make_unique<DeltaWriter>(impl);
12851291
}
12861292

be/src/storage/lake/delta_writer.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,15 @@ class DeltaWriterBuilder {
272272
return *this;
273273
}
274274

275+
// Pre-set the tablet schema. When provided, DeltaWriter skips the schema lookup that
276+
// would otherwise consult TableSchemaService (cache + tablet metadata + FE RPC). Useful
277+
// when the caller already holds the exact schema and wants to avoid the dependency on
278+
// catalog ids and the schema cache, e.g. lake schema-change rewrites.
279+
DeltaWriterBuilder& set_tablet_schema(std::shared_ptr<const TabletSchema> tablet_schema) {
280+
_tablet_schema = std::move(tablet_schema);
281+
return *this;
282+
}
283+
275284
DeltaWriterBuilder& set_partial_update_mode(const PartialUpdateMode& partial_update_mode) {
276285
_partial_update_mode = partial_update_mode;
277286
return *this;
@@ -330,6 +339,7 @@ class DeltaWriterBuilder {
330339
BundleWritableFileContext* _bundle_writable_file_context{nullptr};
331340
GlobalDictByNameMaps* _global_dicts = nullptr;
332341
bool _is_multi_statements_txn = false;
342+
std::shared_ptr<const TabletSchema> _tablet_schema;
333343
};
334344

335345
} // namespace starrocks::lake

be/src/storage/lake/schema_change.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,19 @@ Status SortedSchemaChange::process(RowsetPtr rowset, RowsetMetadata* new_rowset_
282282
RETURN_IF_ERROR(reader->prepare());
283283
RETURN_IF_ERROR(reader->open(_read_params));
284284

285-
// create writer
285+
// Pass the new tablet schema directly so DeltaWriter does not consult TableSchemaService.
286+
// The schema-service lookup is keyed by (db_id, table_id, schema_id) and falls back to an
287+
// FE RPC; without catalog ids on the alter task the RPC goes out with zeros and FE replies
288+
// "Table not exist". The schema-change task already holds the exact schema, so we sidestep
289+
// the lookup entirely.
286290
ASSIGN_OR_RETURN(auto writer, DeltaWriterBuilder()
287291
.set_tablet_manager(_tablet_manager)
288292
.set_tablet_id(_new_tablet.id())
289293
.set_txn_id(_txn_id)
290294
.set_max_buffer_size(_max_buffer_size)
291295
.set_mem_tracker(CurrentThread::mem_tracker())
292-
.set_schema_id(_new_tablet_schema->id()) // TODO: pass tablet schema directly
296+
.set_schema_id(_new_tablet_schema->id())
297+
.set_tablet_schema(_new_tablet_schema)
293298
.build());
294299
RETURN_IF_ERROR(writer->open());
295300
DeferOp defer([&]() { writer->close(); });

be/test/storage/lake/schema_change_test.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include "storage/metadata_util.h"
3838
#include "testutil/assert.h"
3939
#include "testutil/id_generator.h"
40+
#include "testutil/sync_point.h"
41+
#include "util/defer_op.h"
4042

4143
namespace starrocks::lake {
4244

@@ -2765,4 +2767,128 @@ TEST_F(SchemaChangeBaseTabletReadSchemaTest, test_generated_column) {
27652767
ASSERT_EQ(3, row_count);
27662768
}
27672769

2770+
// Regression for the SortedSchemaChange path: DeltaWriter must not consult the schema
2771+
// service (which falls back to an FE RPC) because the schema-change task already holds
2772+
// the exact target schema. The fix passes the tablet schema directly into DeltaWriter,
2773+
// so the schema lookup -- and its FE RPC fallback -- is bypassed entirely.
2774+
TEST_F(SchemaChangeBaseTabletReadSchemaTest, test_sorted_schema_change_skips_schema_rpc) {
2775+
SyncPoint::GetInstance()->EnableProcessing();
2776+
DeferOp restore([] {
2777+
SyncPoint::GetInstance()->ClearAllCallBacks();
2778+
SyncPoint::GetInstance()->DisableProcessing();
2779+
});
2780+
2781+
std::atomic_int rpc_invocations = 0;
2782+
SyncPoint::GetInstance()->SetCallBack(
2783+
"TableSchemaService::_fetch_schema_via_rpc::test_hook",
2784+
[&rpc_invocations](void* arg) { rpc_invocations.fetch_add(1, std::memory_order_relaxed); });
2785+
2786+
auto base_metadata = create_base_tablet_metadata();
2787+
auto base_tablet_id = base_metadata->id();
2788+
CHECK_OK(_tablet_manager->put_tablet_metadata(*base_metadata));
2789+
2790+
// Build an AGG_KEYS new tablet so SchemaChangeUtils::parse_request flips sc_sorting=true.
2791+
auto new_metadata = std::make_shared<TabletMetadata>();
2792+
new_metadata->set_id(next_id());
2793+
new_metadata->set_version(1);
2794+
auto new_schema = new_metadata->mutable_schema();
2795+
new_schema->set_id(next_id());
2796+
new_schema->set_num_short_key_columns(1);
2797+
new_schema->set_keys_type(AGG_KEYS);
2798+
new_schema->set_num_rows_per_row_block(65535);
2799+
{
2800+
auto c0 = new_schema->add_column();
2801+
c0->set_unique_id(1);
2802+
c0->set_name("c0");
2803+
c0->set_type("INT");
2804+
c0->set_is_key(true);
2805+
c0->set_is_nullable(false);
2806+
}
2807+
{
2808+
auto c1 = new_schema->add_column();
2809+
c1->set_unique_id(2);
2810+
c1->set_name("c1");
2811+
c1->set_type("VARCHAR");
2812+
c1->set_length(20);
2813+
c1->set_is_key(true);
2814+
c1->set_is_nullable(false);
2815+
}
2816+
{
2817+
auto c2_sum = new_schema->add_column();
2818+
c2_sum->set_unique_id(9);
2819+
c2_sum->set_name("c2_sum");
2820+
c2_sum->set_type("BIGINT");
2821+
c2_sum->set_is_key(false);
2822+
c2_sum->set_is_nullable(true);
2823+
c2_sum->set_aggregation("SUM");
2824+
}
2825+
CHECK_OK(_tablet_manager->put_tablet_metadata(*new_metadata));
2826+
2827+
int64_t version = 1;
2828+
int64_t txn_id = next_id();
2829+
auto base_tablet_schema = TabletSchema::create(base_metadata->schema());
2830+
auto base_schema = std::make_shared<VSchema>(ChunkHelper::convert_schema(base_tablet_schema));
2831+
2832+
auto col_c0 = Int32Column::create();
2833+
auto col_c1 = BinaryColumn::create();
2834+
auto col_c2 = Int32Column::create();
2835+
auto col_c3 = Int32Column::create();
2836+
auto col_c4 = Int32Column::create();
2837+
col_c0->append_datum(Datum(1));
2838+
col_c1->append_datum(Datum(Slice("row0")));
2839+
col_c2->append_datum(Datum(100));
2840+
col_c3->append_datum(Datum(1000));
2841+
col_c4->append_datum(Datum(10));
2842+
VChunk chunk0({std::move(col_c0), std::move(col_c1), std::move(col_c2), std::move(col_c3), std::move(col_c4)},
2843+
base_schema);
2844+
uint32_t indexes[1] = {0};
2845+
2846+
ASSIGN_OR_ABORT(auto delta_writer, DeltaWriterBuilder()
2847+
.set_tablet_manager(_tablet_manager.get())
2848+
.set_tablet_id(base_tablet_id)
2849+
.set_txn_id(txn_id)
2850+
.set_partition_id(_partition_id)
2851+
.set_mem_tracker(_mem_tracker.get())
2852+
.set_schema_id(base_tablet_schema->id())
2853+
.set_tablet_schema(base_tablet_schema)
2854+
.build());
2855+
ASSERT_OK(delta_writer->open());
2856+
ASSERT_OK(delta_writer->write(chunk0, indexes, sizeof(indexes) / sizeof(indexes[0])));
2857+
ASSERT_OK(delta_writer->finish_with_txnlog());
2858+
delta_writer->close();
2859+
ASSERT_OK(TEST_publish_single_version(_tablet_manager.get(), base_tablet_id, version + 1, txn_id).status());
2860+
version++;
2861+
2862+
// Drop the BE schema cache so the only way DeltaWriter could resolve the new schema is
2863+
// an FE RPC. With the fix, the schema is preset and the lookup is skipped, so the hook
2864+
// above must observe zero invocations.
2865+
_tablet_manager->update_metacache_limit(0);
2866+
_tablet_manager->prune_metacache();
2867+
2868+
auto new_tablet_id = new_metadata->id();
2869+
auto alter_txn_id = next_id();
2870+
{
2871+
auto t_read_schema = create_base_tablet_read_schema(next_id());
2872+
2873+
TAlterTabletReqV2 request;
2874+
request.base_tablet_id = base_tablet_id;
2875+
request.new_tablet_id = new_tablet_id;
2876+
request.alter_version = version;
2877+
request.txn_id = alter_txn_id;
2878+
request.__set_base_tablet_read_schema(t_read_schema);
2879+
request.__set_base_table_column_names({"c0", "c1", "c6", "c2", "c4"});
2880+
TAlterMaterializedViewParam mv_param;
2881+
mv_param.__set_column_name("c2_sum");
2882+
mv_param.__set_origin_column_name("c2");
2883+
request.__set_materialized_view_params({mv_param});
2884+
2885+
SchemaChangeHandler handler(_tablet_manager.get());
2886+
ASSERT_OK(handler.process_alter_tablet(request));
2887+
}
2888+
ASSERT_OK(publish_version_for_schema_change(new_tablet_id, version + 1, alter_txn_id));
2889+
2890+
EXPECT_EQ(0, rpc_invocations.load(std::memory_order_relaxed))
2891+
<< "SortedSchemaChange must not call the FE schema RPC when DeltaWriter is given a preset schema";
2892+
}
2893+
27682894
} // namespace starrocks::lake
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
-- name: test_lake_alter_add_first_key_no_metacache @cloud
2+
update information_schema.be_configs set value = "0" where name = "lake_metadata_cache_limit";
3+
-- result:
4+
-- !result
5+
create database test_db_${uuid0};
6+
-- result:
7+
-- !result
8+
use test_db_${uuid0};
9+
-- result:
10+
-- !result
11+
create table t1 (k1 int, k2 int, v1 int) duplicate key(k1)
12+
distributed by hash(k1) buckets 1 properties("replication_num" = "1");
13+
-- result:
14+
-- !result
15+
insert into t1 values (1, 10, 100), (2, 20, 200), (3, 30, 300);
16+
-- result:
17+
-- !result
18+
alter table t1 add column k0 int key default "0" first;
19+
-- result:
20+
-- !result
21+
function: wait_alter_table_finish()
22+
-- result:
23+
None
24+
-- !result
25+
select * from t1 order by k1;
26+
-- result:
27+
0 1 10 100
28+
0 2 20 200
29+
0 3 30 300
30+
-- !result
31+
insert into t1 values (-1, 4, 40, 400);
32+
-- result:
33+
-- !result
34+
select * from t1 order by k1;
35+
-- result:
36+
0 1 10 100
37+
0 2 20 200
38+
0 3 30 300
39+
-1 4 40 400
40+
-- !result
41+
drop database test_db_${uuid0};
42+
-- result:
43+
-- !result
44+
update information_schema.be_configs set value = "2147483648" where name = "lake_metadata_cache_limit";
45+
-- result:
46+
-- !result
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- name: test_lake_alter_add_first_key_no_metacache @cloud
2+
-- Regression for the SortedSchemaChange path on lake tables: when the BE schema cache is
3+
-- disabled, the schema-change writer must still find the new schema. The fix passes the
4+
-- tablet schema directly into DeltaWriter, bypassing TableSchemaService entirely. Before
5+
-- the fix the writer fell back to an FE schema RPC and the alter was cancelled with
6+
-- "table not found, db id: 0, table id: 0" because the alter task carried no catalog ids.
7+
update information_schema.be_configs set value = "0" where name = "lake_metadata_cache_limit";
8+
create database test_db_${uuid0};
9+
use test_db_${uuid0};
10+
11+
create table t1 (k1 int, k2 int, v1 int) duplicate key(k1)
12+
distributed by hash(k1) buckets 1 properties("replication_num" = "1");
13+
insert into t1 values (1, 10, 100), (2, 20, 200), (3, 30, 300);
14+
15+
-- Trigger SortedSchemaChange by adding a key column at the head: this rewrites rowsets
16+
-- through DeltaWriter, the only schema-change path that built a writer without a schema.
17+
alter table t1 add column k0 int key default "0" first;
18+
function: wait_alter_table_finish()
19+
20+
select * from t1 order by k1;
21+
insert into t1 values (-1, 4, 40, 400);
22+
select * from t1 order by k1;
23+
24+
drop database test_db_${uuid0};
25+
update information_schema.be_configs set value = "2147483648" where name = "lake_metadata_cache_limit";

0 commit comments

Comments
 (0)