diff --git a/go/vt/vtorc/db/generate_base.go b/go/vt/vtorc/db/generate_base.go index 8baa9a12476..0527007dc22 100644 --- a/go/vt/vtorc/db/generate_base.go +++ b/go/vt/vtorc/db/generate_base.go @@ -29,9 +29,10 @@ var TableNames = []string{ "global_recovery_disable", "topology_recovery_steps", "database_instance_stale_binlog_coordinates", - "vitess_tablet", + "vitess_cell", "vitess_keyspace", "vitess_shard", + "vitess_tablet", } // vtorcBackend is a list of SQL statements required to build the vtorc backend @@ -307,6 +308,14 @@ CREATE TABLE vitess_shard ( PRIMARY KEY (keyspace, shard) )`, ` +DROP TABLE IF EXISTS vitess_cell +`, + ` +CREATE TABLE vitess_cell ( + cell varchar(128) NOT NULL, + PRIMARY KEY (cell) +)`, + ` CREATE INDEX source_host_port_idx_database_instance_database_instance on database_instance (source_host, source_port) `, ` diff --git a/go/vt/vtorc/inst/cell_dao.go b/go/vt/vtorc/inst/cell_dao.go new file mode 100644 index 00000000000..35d0ab85716 --- /dev/null +++ b/go/vt/vtorc/inst/cell_dao.go @@ -0,0 +1,45 @@ +/* +Copyright 2025 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package inst + +import ( + "vitess.io/vitess/go/vt/external/golib/sqlutils" + "vitess.io/vitess/go/vt/vtorc/db" +) + +// ReadCells reads all the vitess cell names. +func ReadCells() ([]string, error) { + cells := make([]string, 0) + query := `SELECT cell FROM vitess_cell` + err := db.QueryVTOrc(query, nil, func(row sqlutils.RowMap) error { + cells = append(cells, row.GetString("cell")) + return nil + }) + return cells, err +} + +// SaveCell saves the keyspace record against the keyspace name. +func SaveCell(cell string) error { + _, err := db.ExecVTOrc(`REPLACE INTO vitess_cell (cell) VALUES(?)`, cell) + return err +} + +// DeleteCell deletes a cell. +func DeleteCell(cell string) (err error) { + _, err = db.ExecVTOrc(`DELETE FROM vitess_cell WHERE cell = ?`, cell) + return err +} diff --git a/go/vt/vtorc/inst/cell_dao_test.go b/go/vt/vtorc/inst/cell_dao_test.go new file mode 100644 index 00000000000..d474319c1c5 --- /dev/null +++ b/go/vt/vtorc/inst/cell_dao_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package inst + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/vtorc/db" +) + +func TestSaveReadAndDeleteCells(t *testing.T) { + db.ClearVTOrcDatabase() + defer db.ClearVTOrcDatabase() + + cells := []string{"zone1", "zone2", "zone3"} + for _, cell := range cells { + require.NoError(t, SaveCell(cell)) + } + cellsRead, err := ReadCells() + require.NoError(t, err) + require.Equal(t, cells, cellsRead) + + require.NoError(t, DeleteCell("zone3")) + cellsRead, err = ReadCells() + require.NoError(t, err) + require.Equal(t, []string{"zone1", "zone2"}, cellsRead) +} diff --git a/go/vt/vtorc/logic/cell_discovery.go b/go/vt/vtorc/logic/cell_discovery.go new file mode 100644 index 00000000000..55974b6744c --- /dev/null +++ b/go/vt/vtorc/logic/cell_discovery.go @@ -0,0 +1,77 @@ +/* +Copyright 2025 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logic + +import ( + "context" + "sync" + + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/vtorc/inst" +) + +var saveAllCellsMu sync.Mutex + +// RefreshCells refreshes the list of cells. +func RefreshCells(ctx context.Context) error { + cellsCtx, cellsCancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout) + defer cellsCancel() + cells, err := ts.GetKnownCells(cellsCtx) + if err != nil { + return err + } + return saveAllCells(cells) +} + +// saveAllCells saves a slice representing all cells +// and removes stale cells that were not updated. +func saveAllCells(allCells []string) (err error) { + saveAllCellsMu.Lock() + defer saveAllCellsMu.Unlock() + + // save cells. + updated := make(map[string]bool, len(allCells)) + for _, cell := range allCells { + if err = inst.SaveCell(cell); err != nil { + log.Errorf("Failed to save cell %s: %+v", cell, err) + return err + } + updated[cell] = true + } + + // read all saved cells. the values should not be changing + // because we are holding a lock and updates originate + // from this func only. + cells, err := inst.ReadCells() + if err != nil { + log.Errorf("Failed to read all cells: %+v", err) + return err + } + + // delete cells that are stale. + for _, cell := range cells { + if updated[cell] { + continue + } + log.Infof("Forgetting stale cell %s", cell) + if err = inst.DeleteCell(cell); err != nil { + log.Errorf("Failed to delete cell %s: %+v", cell, err) + } + } + return nil +} diff --git a/go/vt/vtorc/logic/cell_discovery_test.go b/go/vt/vtorc/logic/cell_discovery_test.go new file mode 100644 index 00000000000..f1ff45f8758 --- /dev/null +++ b/go/vt/vtorc/logic/cell_discovery_test.go @@ -0,0 +1,53 @@ +/* +Copyright 2025 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logic + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/topo/memorytopo" + "vitess.io/vitess/go/vt/vtorc/db" + "vitess.io/vitess/go/vt/vtorc/inst" +) + +func TestRefreshCells(t *testing.T) { + db.ClearVTOrcDatabase() + oldTs := ts + defer func() { + db.ClearVTOrcDatabase() + ts = oldTs + }() + + cells := []string{"zone1", "zone2", "zone3"} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ts = memorytopo.NewServer(ctx, cells...) + + require.NoError(t, RefreshCells(ctx)) + cellsRead, err := inst.ReadCells() + require.NoError(t, err) + require.Equal(t, cells, cellsRead) + + require.NoError(t, ts.DeleteCellInfo(ctx, "zone3", true)) + require.NoError(t, RefreshCells(ctx)) + cellsRead, err = inst.ReadCells() + require.NoError(t, err) + require.Equal(t, []string{"zone1", "zone2"}, cellsRead) +} diff --git a/go/vt/vtorc/logic/tablet_discovery.go b/go/vt/vtorc/logic/tablet_discovery.go index c5c23df0cd0..18b7d116755 100644 --- a/go/vt/vtorc/logic/tablet_discovery.go +++ b/go/vt/vtorc/logic/tablet_discovery.go @@ -181,13 +181,14 @@ func refreshAllTablets(ctx context.Context) error { // refreshTabletsUsing refreshes tablets using a provided loader. func refreshTabletsUsing(ctx context.Context, loader func(tabletAlias string), forceRefresh bool) error { - // Get all cells. - ctx, cancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout) - defer cancel() - cells, err := ts.GetKnownCells(ctx) + cells, err := inst.ReadCells() if err != nil { return err } + if len(cells) == 0 { + log.Error("Found no cells") + return nil + } // Get all tablets from all cells. getTabletsCtx, getTabletsCancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout) @@ -236,7 +237,12 @@ func refreshTabletInfoOfShard(ctx context.Context, keyspace, shard string) { } func refreshTabletsInKeyspaceShard(ctx context.Context, keyspace, shard string, loader func(tabletAlias string), forceRefresh bool, tabletsToIgnore []string) { - tablets, err := ts.GetTabletsByShard(ctx, keyspace, shard) + cells, err := inst.ReadCells() + if err != nil { + log.Errorf("Error fetching cells: %v", err) + return + } + tablets, err := ts.GetTabletsByShardCell(ctx, keyspace, shard, cells) if err != nil { log.Errorf("Error fetching tablets for keyspace/shard %v/%v: %v", keyspace, shard, err) return diff --git a/go/vt/vtorc/logic/vtorc.go b/go/vt/vtorc/logic/vtorc.go index 5ac5af50d47..d9aec99830e 100644 --- a/go/vt/vtorc/logic/vtorc.go +++ b/go/vt/vtorc/logic/vtorc.go @@ -321,6 +321,11 @@ func refreshAllInformation(ctx context.Context) error { // Create an errgroup eg, ctx := errgroup.WithContext(ctx) + // Refresh all cells. + eg.Go(func() error { + return RefreshCells(ctx) + }) + // Refresh all keyspace information. eg.Go(func() error { return RefreshAllKeyspacesAndShards(ctx)