21
21
flagRootHash string
22
22
flagFrom int
23
23
flagTo int
24
- flagOutputDir string
24
+ flagBackupDir string
25
+ flagTrimAsLatestWAL bool
25
26
)
26
27
27
28
// find trie root hash from the wal files.
@@ -50,7 +51,9 @@ func init() {
50
51
Cmd .Flags ().IntVar (& flagFrom , "from" , 0 , "from segment" )
51
52
Cmd .Flags ().IntVar (& flagTo , "to" , math .MaxInt32 , "to segment" )
52
53
53
- Cmd .Flags ().StringVar (& flagOutputDir , "output-dir" , "" , "output directory" )
54
+ Cmd .Flags ().StringVar (& flagBackupDir , "backup-dir" , "" , "directory for backup wal files. must be not exist or empty folder. required when --trim-as-latest-wal flag is set to true." )
55
+
56
+ Cmd .Flags ().BoolVar (& flagTrimAsLatestWAL , "trim-as-latest-wal" , false , "trim the wal file to the last record with the target trie root hash" )
54
57
}
55
58
56
59
func run (* cobra.Command , []string ) {
@@ -59,26 +62,46 @@ func run(*cobra.Command, []string) {
59
62
log .Fatal ().Err (err ).Msg ("cannot parse input" )
60
63
}
61
64
62
- if flagExecutionStateDir == flagOutputDir {
63
- log .Fatal ().Msg ("output directory cannot be the same as the execution state directory" )
65
+ if flagExecutionStateDir == flagBackupDir {
66
+ log .Fatal ().Msg ("backup directory cannot be the same as the execution state directory" )
64
67
}
65
68
66
69
segment , offset , err := searchRootHashInSegments (rootHash , flagExecutionStateDir , flagFrom , flagTo )
67
70
if err != nil {
68
71
log .Fatal ().Err (err ).Msg ("cannot find root hash in segments" )
69
72
}
70
- log .Info ().Msgf ("found root hash in segment %d at offset %d" , segment , offset )
71
73
72
- if len (flagOutputDir ) == 0 {
74
+ segmentFile := prometheusWAL .SegmentName (flagExecutionStateDir , segment )
75
+
76
+ log .Info ().Msgf ("found root hash in segment %d at offset %d, segment file: %v" , segment , offset , segmentFile )
77
+
78
+ if ! flagTrimAsLatestWAL {
79
+ log .Info ().Msg ("not trimming WAL. Exiting. to trim the WAL, use --trim-as-latest-wal flag" )
73
80
return
74
81
}
75
82
76
- err = copyWAL (flagExecutionStateDir , flagOutputDir , segment , rootHash )
83
+ if len (flagBackupDir ) == 0 {
84
+ log .Error ().Msgf ("backup directory is not provided" )
85
+ return
86
+ }
87
+
88
+ // genereate a segment file to the temporary folder with the root hash as its last record
89
+ newSegmentFile , err := findRootHashAndCreateTrimmed (flagExecutionStateDir , segment , rootHash )
77
90
if err != nil {
78
91
log .Fatal ().Err (err ).Msg ("cannot copy WAL" )
79
92
}
80
93
81
- log .Info ().Msgf ("copied WAL to %s" , flagOutputDir )
94
+ log .Info ().Msgf ("successfully copied WAL to the temporary folder %v" , newSegmentFile )
95
+
96
+ // before replacing the last wal file with the newly generated one, backup the rollbacked wals
97
+ // then move the last segment file to the execution state directory
98
+ err = backupRollbackedWALsAndMoveLastSegmentFile (segment , flagExecutionStateDir , flagBackupDir , newSegmentFile )
99
+ if err != nil {
100
+ log .Fatal ().Err (err ).Msg ("cannot backup rollbacked WALs" )
101
+ }
102
+
103
+ log .Info ().Msgf ("successfully trimmed WAL %v the trie root hash %v as its last record, original wal files are moved to %v" ,
104
+ segment , rootHash , flagBackupDir )
82
105
}
83
106
84
107
func parseInput (rootHashStr string ) (ledger.RootHash , error ) {
@@ -181,26 +204,33 @@ func searchRootHashInSegments(
181
204
return 0 , 0 , fmt .Errorf ("finish reading all segment files from %d to %d, but not found" , from , to )
182
205
}
183
206
184
- func copyWAL (dir , outputDir string , segment int , expectedRoot ledger.RootHash ) error {
185
- writer , err := prometheusWAL .NewSize (log .Logger , nil , outputDir , wal .SegmentSize , false )
207
+ // findRootHashAndCreateTrimmed finds the root hash in the segment file from the given dir folder
208
+ // and creates a new segment file with the expected root hash as the last record in a temporary folder.
209
+ // it return the path to the new segment file.
210
+ func findRootHashAndCreateTrimmed (dir string , segment int , expectedRoot ledger.RootHash ) (string , error ) {
211
+ tmpFolder , err := os .MkdirTemp ("" , "flow-last-segment-file" )
186
212
if err != nil {
187
- return fmt .Errorf ("cannot create writer WAL : %w" , err )
213
+ return "" , fmt .Errorf ("cannot create temporary folder : %w" , err )
188
214
}
189
215
190
- defer writer .Close ()
216
+ newSegmentFile := prometheusWAL .SegmentName (tmpFolder , segment )
217
+
218
+ log .Info ().Msgf ("writing new segment file to %v" , newSegmentFile )
191
219
192
- w , err := prometheusWAL .NewSize (log .Logger , nil , dir , wal .SegmentSize , false )
220
+ writer , err := prometheusWAL .NewSize (log .Logger , nil , tmpFolder , wal .SegmentSize , false )
193
221
if err != nil {
194
- return fmt .Errorf ("cannot create WAL: %w" , err )
222
+ return "" , fmt .Errorf ("cannot create writer WAL: %w" , err )
195
223
}
196
224
225
+ defer writer .Close ()
226
+
197
227
sr , err := prometheusWAL .NewSegmentsRangeReader (log .Logger , prometheusWAL.SegmentRange {
198
- Dir : w . Dir () ,
228
+ Dir : dir ,
199
229
First : segment ,
200
230
Last : segment ,
201
231
})
202
232
if err != nil {
203
- return fmt .Errorf ("cannot create WAL segments reader: %w" , err )
233
+ return "" , fmt .Errorf ("cannot create WAL segments reader: %w" , err )
204
234
}
205
235
206
236
defer sr .Close ()
@@ -211,7 +241,7 @@ func copyWAL(dir, outputDir string, segment int, expectedRoot ledger.RootHash) e
211
241
record := reader .Record ()
212
242
operation , _ , update , err := wal .Decode (record )
213
243
if err != nil {
214
- return fmt .Errorf ("cannot decode LedgerWAL record: %w" , err )
244
+ return "" , fmt .Errorf ("cannot decode LedgerWAL record: %w" , err )
215
245
}
216
246
217
247
switch operation {
@@ -220,23 +250,101 @@ func copyWAL(dir, outputDir string, segment int, expectedRoot ledger.RootHash) e
220
250
bytes := wal .EncodeUpdate (update )
221
251
_ , err = writer .Log (bytes )
222
252
if err != nil {
223
- return fmt .Errorf ("cannot write LedgerWAL record: %w" , err )
253
+ return "" , fmt .Errorf ("cannot write LedgerWAL record: %w" , err )
224
254
}
225
255
226
256
rootHash := update .RootHash
227
257
228
258
if rootHash .Equals (expectedRoot ) {
229
259
log .Info ().Msgf ("found expected trie root hash %v, finish writing" , rootHash )
230
- return nil
260
+ return newSegmentFile , nil
231
261
}
232
262
default :
233
263
}
234
264
235
265
err = reader .Err ()
236
266
if err != nil {
237
- return fmt .Errorf ("cannot read LedgerWAL: %w" , err )
267
+ return "" , fmt .Errorf ("cannot read LedgerWAL: %w" , err )
238
268
}
239
269
}
240
270
241
- return fmt .Errorf ("finish reading all segment files from %d to %d, but not found" , segment , segment )
271
+ return "" , fmt .Errorf ("finish reading all segment files from %d to %d, but not found" , segment , segment )
272
+ }
273
+
274
+ func checkFolderNotExistOrEmpty (folderPath string ) (bool , error ) {
275
+ // Check if the folder exists
276
+ info , err := os .Stat (folderPath )
277
+ if err != nil {
278
+ if os .IsNotExist (err ) {
279
+ return true , nil
280
+ }
281
+ return false , nil
282
+ }
283
+
284
+ // Check if the path is a directory
285
+ if ! info .IsDir () {
286
+ return false , fmt .Errorf ("The path is not a directory." )
287
+ }
288
+
289
+ // Check if the folder is empty
290
+ files , err := os .ReadDir (folderPath )
291
+ if err != nil {
292
+ return false , fmt .Errorf ("Cannot read the folder." )
293
+ }
294
+
295
+ return len (files ) == 0 , nil
296
+ }
297
+
298
+ // backup new wals before replacing
299
+ func backupRollbackedWALsAndMoveLastSegmentFile (
300
+ segment int , walDir , backupDir string , newSegmentFile string ) error {
301
+ // making sure the backup dir is empty
302
+ empty , err := checkFolderNotExistOrEmpty (backupDir )
303
+ if err != nil {
304
+ return fmt .Errorf ("cannot check backup directory: %w" , err )
305
+ }
306
+
307
+ if ! empty {
308
+ return fmt .Errorf ("backup directory %s is not empty" , backupDir )
309
+ }
310
+
311
+ // Create the backup directory
312
+ err = os .MkdirAll (backupDir , os .ModePerm )
313
+ if err != nil {
314
+ return fmt .Errorf ("cannot create backup directory: %w" , err )
315
+ }
316
+
317
+ first , last , err := prometheusWAL .Segments (walDir )
318
+ if err != nil {
319
+ return fmt .Errorf ("cannot get segments: %w" , err )
320
+ }
321
+
322
+ if segment < first {
323
+ return fmt .Errorf ("segment %d is less than the first segment %d" , segment , first )
324
+ }
325
+
326
+ // backup all the segment files that have higher number than the given segment, including
327
+ // the segment file itself, since it will be replaced.
328
+ for i := segment ; i <= last ; i ++ {
329
+ segmentFile := prometheusWAL .SegmentName (walDir , i )
330
+ backupFile := prometheusWAL .SegmentName (backupDir , i )
331
+
332
+ log .Info ().Msgf ("backup segment file %s to %s, %v/%v" , segmentFile , backupFile , i , last )
333
+ err := os .Rename (segmentFile , backupFile )
334
+ if err != nil {
335
+ return fmt .Errorf ("cannot move segment file %s to %s: %w" , segmentFile , backupFile , err )
336
+ }
337
+ }
338
+
339
+ // after backup the segment files, replace the last segment file
340
+ segmentToBeReplaced := prometheusWAL .SegmentName (walDir , segment )
341
+
342
+ log .Info ().Msgf ("moving segment file %s to %s" , newSegmentFile , segmentToBeReplaced )
343
+
344
+ err = os .Rename (newSegmentFile , segmentToBeReplaced )
345
+ if err != nil {
346
+ return fmt .Errorf ("cannot move segment file %s to %s: %w" , newSegmentFile , segmentToBeReplaced , err )
347
+ }
348
+
349
+ return nil
242
350
}
0 commit comments