11package handle
22
33import (
4+ "fmt"
5+ "regexp"
46 "strings"
57
68 "github.com/selectdb/ccr_syncer/pkg/ccr"
@@ -18,6 +20,176 @@ type CreateTableHandle struct {
1820 IdempotentJobHandle [* record.CreateTable ]
1921}
2022
23+ // Check if error message indicates storage medium or capacity related issues
24+ func isStorageMediumError (errMsg string ) bool {
25+ log .Infof ("STORAGE_MEDIUM_DEBUG: Analyzing error message: %s" , errMsg )
26+
27+ patterns := []string {
28+ "capExceedLimit" ,
29+ "Failed to find enough backend" ,
30+ "not enough backend" ,
31+ "storage medium" ,
32+ "storage_medium" ,
33+ "avail capacity" ,
34+ "disk space" ,
35+ "not enough space" ,
36+ "replication num" ,
37+ "replication tag" ,
38+ }
39+
40+ for _ , pattern := range patterns {
41+ if strings .Contains (strings .ToLower (errMsg ), strings .ToLower (pattern )) {
42+ log .Infof ("STORAGE_MEDIUM_DEBUG: Found storage/capacity related pattern '%s' in error message" , pattern )
43+ return true
44+ }
45+ }
46+
47+ log .Infof ("STORAGE_MEDIUM_DEBUG: No storage/capacity related patterns found in error message" )
48+ return false
49+ }
50+
51+ // Extract storage_medium from CREATE TABLE SQL
52+ func extractStorageMediumFromCreateTableSql (createSql string ) string {
53+ pattern := `"storage_medium"\s*=\s*"([^"]*)"`
54+ re := regexp .MustCompile (pattern )
55+ matches := re .FindStringSubmatch (createSql )
56+ if len (matches ) >= 2 {
57+ medium := strings .ToLower (matches [1 ])
58+ log .Infof ("STORAGE_MEDIUM_DEBUG: Extracted storage medium: %s" , medium )
59+ return medium
60+ }
61+ log .Infof ("STORAGE_MEDIUM_DEBUG: No storage medium found in SQL" )
62+ return ""
63+ }
64+
65+ // Switch storage medium between SSD and HDD
66+ func switchStorageMedium (medium string ) string {
67+ switch strings .ToLower (medium ) {
68+ case "ssd" :
69+ return "hdd"
70+ case "hdd" :
71+ return "ssd"
72+ default :
73+ // Default to hdd if not standard medium
74+ return "hdd"
75+ }
76+ }
77+
78+ // Set specific storage_medium in CREATE TABLE SQL
79+ func setStorageMediumInCreateTableSql (createSql string , medium string ) string {
80+ // Remove existing storage_medium first
81+ createSql = ccr .FilterStorageMediumFromCreateTableSql (createSql )
82+
83+ // Check if PROPERTIES clause exists
84+ propertiesPattern := `PROPERTIES\s*\(`
85+ if matched , _ := regexp .MatchString (propertiesPattern , createSql ); matched {
86+ // Add storage_medium at the beginning of PROPERTIES
87+ pattern := `(PROPERTIES\s*\(\s*)`
88+ replacement := fmt .Sprintf (`${1}"storage_medium" = "%s", ` , medium )
89+ createSql = regexp .MustCompile (pattern ).ReplaceAllString (createSql , replacement )
90+ } else {
91+ // Add entire PROPERTIES clause
92+ pattern := `(\s*)$`
93+ replacement := fmt .Sprintf (` PROPERTIES ("storage_medium" = "%s")` , medium )
94+ createSql = regexp .MustCompile (pattern ).ReplaceAllString (createSql , replacement )
95+ }
96+
97+ return createSql
98+ }
99+
100+ // Process CREATE TABLE SQL according to medium sync policy
101+ func processCreateTableSqlByMediumPolicy (j * ccr.Job , createTable * record.CreateTable ) error {
102+ // Note: We need to access Job's medium sync policy and feature flags
103+ // For now, we'll implement basic logic based on what we know the Job should do
104+
105+ // Check if medium sync policy feature is enabled (we assume it's enabled for new handler)
106+ // This is a simplified version that handles the main cases
107+ mediumPolicy := j .MediumSyncPolicy
108+
109+ switch mediumPolicy {
110+ case ccr .MediumSyncPolicySameWithUpstream :
111+ // Keep upstream storage_medium unchanged
112+ log .Infof ("using same_with_upstream policy, keeping original storage_medium" )
113+ return nil
114+
115+ case ccr .MediumSyncPolicyHDD :
116+ // Force set to HDD
117+ log .Infof ("using hdd policy, setting storage_medium to hdd" )
118+ createTable .Sql = setStorageMediumInCreateTableSql (createTable .Sql , "hdd" )
119+ return nil
120+
121+ default :
122+ log .Warnf ("unknown medium sync policy: %s, falling back to filter storage_medium" , mediumPolicy )
123+ if ccr .FeatureFilterStorageMedium {
124+ createTable .Sql = ccr .FilterStorageMediumFromCreateTableSql (createTable .Sql )
125+ }
126+ return nil
127+ }
128+ }
129+
130+ // Create table with medium retry mechanism
131+ func createTableWithMediumRetry (j * ccr.Job , createTable * record.CreateTable , srcDb string ) error {
132+ originalSql := createTable .Sql
133+ log .Infof ("STORAGE_MEDIUM_DEBUG: Starting create table with medium retry for table: %s" , createTable .TableName )
134+
135+ // Process SQL according to medium policy
136+ if err := processCreateTableSqlByMediumPolicy (j , createTable ); err != nil {
137+ return err
138+ }
139+
140+ // First attempt
141+ err := j .IDest .CreateTableOrView (createTable , srcDb )
142+ if err == nil {
143+ log .Infof ("STORAGE_MEDIUM_DEBUG: Create table succeeded on first attempt" )
144+ return nil
145+ }
146+
147+ log .Warnf ("STORAGE_MEDIUM_DEBUG: First attempt failed: %s" , err .Error ())
148+
149+ // Check if it's storage related error and should retry
150+ if ! isStorageMediumError (err .Error ()) {
151+ log .Infof ("STORAGE_MEDIUM_DEBUG: Not a storage related error, no retry" )
152+ return err
153+ }
154+
155+ // Extract current medium and switch to the other one
156+ currentMedium := extractStorageMediumFromCreateTableSql (createTable .Sql )
157+ if currentMedium == "" {
158+ currentMedium = "ssd" // default
159+ }
160+
161+ switchedMedium := switchStorageMedium (currentMedium )
162+ log .Infof ("STORAGE_MEDIUM_DEBUG: Switching from %s to %s" , currentMedium , switchedMedium )
163+
164+ createTable .Sql = setStorageMediumInCreateTableSql (originalSql , switchedMedium )
165+
166+ // Second attempt with switched medium
167+ err = j .IDest .CreateTableOrView (createTable , srcDb )
168+ if err == nil {
169+ log .Infof ("STORAGE_MEDIUM_DEBUG: Create table succeeded after switching to %s" , switchedMedium )
170+ return nil
171+ }
172+
173+ log .Warnf ("STORAGE_MEDIUM_DEBUG: Second attempt with %s also failed: %s" , switchedMedium , err .Error ())
174+
175+ // Final attempt: remove storage_medium if still storage related error
176+ if isStorageMediumError (err .Error ()) {
177+ log .Infof ("STORAGE_MEDIUM_DEBUG: Removing storage_medium for final attempt" )
178+ createTable .Sql = ccr .FilterStorageMediumFromCreateTableSql (originalSql )
179+
180+ err = j .IDest .CreateTableOrView (createTable , srcDb )
181+ if err == nil {
182+ log .Infof ("STORAGE_MEDIUM_DEBUG: Create table succeeded after removing storage_medium" )
183+ return nil
184+ }
185+
186+ log .Warnf ("STORAGE_MEDIUM_DEBUG: Final attempt without storage_medium also failed: %s" , err .Error ())
187+ }
188+
189+ log .Errorf ("STORAGE_MEDIUM_DEBUG: All attempts failed, returning final error" )
190+ return err
191+ }
192+
21193func (h * CreateTableHandle ) Handle (j * ccr.Job , commitSeq int64 , createTable * record.CreateTable ) error {
22194 if j .SyncType != ccr .DBSync {
23195 return xerror .Errorf (xerror .Normal , "invalid sync type: %v" , j .SyncType )
@@ -68,12 +240,11 @@ func (h *CreateTableHandle) Handle(j *ccr.Job, commitSeq int64, createTable *rec
68240 }
69241 }
70242
71- if ccr .FeatureFilterStorageMedium {
72- createTable .Sql = ccr .FilterStorageMediumFromCreateTableSql (createTable .Sql )
73- }
243+ // Remove old storage_medium filtering logic, handled by new retry function
74244 createTable .Sql = ccr .FilterDynamicPartitionStoragePolicyFromCreateTableSql (createTable .Sql )
75245
76- if err := j .IDest .CreateTableOrView (createTable , j .Src .Database ); err != nil {
246+ // Use new create table function with medium retry mechanism
247+ if err := createTableWithMediumRetry (j , createTable , j .Src .Database ); err != nil {
77248 errMsg := err .Error ()
78249 if strings .Contains (errMsg , "Can not found function" ) {
79250 log .Warnf ("skip creating table/view because the UDF function is not supported yet: %s" , errMsg )
0 commit comments