Madagascar / projects / autoSMART / sql / migrate-v1-to-v2.sql
Newer Older
339 lines | 14.278kb
Bogdan Timofte authored 2 weeks ago
1
-- autoSMART Migration v1 → v2
2
-- Migrates existing data from smart_readings (blob JSONB) to new schema
3
-- Date: 2026-05-20
4
-- Execution time: ~5-15 minutes for 151K readings
5
-- NOTE: Only baseline + full readings are migrated (differential are empty due to bug)
6

            
7
BEGIN;
8

            
9
-- ============================================================================
10
-- PHASE 1: Populate smart_param_catalog from smart_thresholds + auto-discovery
11
-- ============================================================================
12

            
13
DO $$
14
DECLARE
15
    v_param TEXT;
16
    v_threshold_rec RECORD;
17
BEGIN
18
    RAISE NOTICE '[PHASE 1] Populating smart_param_catalog...';
19

            
20
    FOR v_threshold_rec IN
21
        SELECT parameter_name, warning_threshold, critical_threshold,
22
               weight, description
23
        FROM smart_thresholds
24
    LOOP
25
        INSERT INTO smart_param_catalog
26
            (param_name, description, warning_threshold, critical_threshold,
27
             health_weight, is_critical, unit)
28
        VALUES (
29
            v_threshold_rec.parameter_name,
30
            v_threshold_rec.description,
31
            v_threshold_rec.warning_threshold,
32
            v_threshold_rec.critical_threshold,
33
            v_threshold_rec.weight,
34
            (v_threshold_rec.weight >= 8.0),
35
            'count'
36
        )
37
        ON CONFLICT (param_name) DO UPDATE
38
            SET warning_threshold  = EXCLUDED.warning_threshold,
39
                critical_threshold = EXCLUDED.critical_threshold,
40
                health_weight      = EXCLUDED.health_weight,
41
                is_critical        = EXCLUDED.is_critical,
42
                description        = COALESCE(EXCLUDED.description, smart_param_catalog.description),
43
                updated_at         = NOW();
44
    END LOOP;
45

            
46
    RAISE NOTICE '[PHASE 1] smart_thresholds → smart_param_catalog: % parameters imported',
47
        (SELECT COUNT(*) FROM smart_param_catalog);
48

            
49
    FOR v_param IN
50
        SELECT DISTINCT jsonb_object_keys(parameters_json)
51
        FROM smart_readings
52
        WHERE parameters_json IS NOT NULL AND parameters_json != '{}'::jsonb
53
    LOOP
54
        INSERT INTO smart_param_catalog (param_name)
55
        VALUES (v_param)
56
        ON CONFLICT (param_name) DO NOTHING;
57
    END LOOP;
58

            
59
    RAISE NOTICE '[PHASE 1] Auto-discovery from JSONB complete: % total parameters in catalog',
60
        (SELECT COUNT(*) FROM smart_param_catalog);
61
END $$;
62

            
63
-- ============================================================================
64
-- PHASE 2: Migrate smart_readings → smart_collection_events (only full/baseline)
65
-- ============================================================================
66

            
67
DO $$
68
DECLARE
69
    v_count BIGINT;
70
BEGIN
71
    RAISE NOTICE '[PHASE 2] Migrating full/baseline readings to smart_collection_events...';
72

            
73
    INSERT INTO smart_collection_events
74
        (hdd_id, serial_number, node_id, collected_at,
75
         collection_ok, temperature, checksum, param_count)
76
    SELECT
77
        hdd_id,
78
        serial_number,
79
        COALESCE(node_id, 'unknown'),
80
        timestamp,
81
        COALESCE(collection_ok, true),
82
        temperature,
83
        checksum,
84
        CASE WHEN parameters_json IS NOT NULL AND parameters_json != '{}'::jsonb
85
             THEN (SELECT COUNT(*) FROM jsonb_object_keys(parameters_json))::SMALLINT
86
             ELSE 0::SMALLINT
87
        END
88
    FROM smart_readings
89
    WHERE reading_type IN ('baseline', 'full')
90
    ORDER BY timestamp;
91

            
92
    v_count := (SELECT COUNT(*) FROM smart_collection_events);
93
    RAISE NOTICE '[PHASE 2] Migrated % collection events', v_count;
94
END $$;
95

            
96
-- ============================================================================
97
-- PHASE 3: Create monthly partitions for historical data range
98
-- ============================================================================
99

            
100
DO $$
101
DECLARE
102
    v_min_date DATE;
103
    v_max_date DATE;
104
    v_d DATE;
105
BEGIN
106
    RAISE NOTICE '[PHASE 3] Creating monthly partitions for historical range...';
107

            
108
    SELECT DATE_TRUNC('month', MIN(timestamp))::DATE,
109
           DATE_TRUNC('month', MAX(timestamp))::DATE + INTERVAL '1 month'
110
    INTO v_min_date, v_max_date
111
    FROM smart_readings;
112

            
113
    v_d := v_min_date;
114
    WHILE v_d <= v_max_date LOOP
115
        PERFORM create_monthly_partition(
116
            EXTRACT(YEAR FROM v_d)::INTEGER,
117
            EXTRACT(MONTH FROM v_d)::INTEGER
118
        );
119
        v_d := v_d + INTERVAL '1 month';
120
    END LOOP;
121

            
122
    RAISE NOTICE '[PHASE 3] Partition creation complete';
123
END $$;
124

            
125
-- ============================================================================
126
-- PHASE 4: Populate smart_param_values from JSONB
127
-- ============================================================================
128

            
129
DO $$
130
DECLARE
131
    v_migrated BIGINT;
132
    v_errors   BIGINT;
133
BEGIN
134
    RAISE NOTICE '[PHASE 4] Migrating parameter values from smart_readings JSONB...';
135

            
136
    INSERT INTO smart_param_values
137
        (event_id, hdd_id, param_id, collected_at,
138
         raw_value, normalized_value, worst_value, threshold_value, when_failed)
139
    SELECT
140
        sce.id,
141
        sr.hdd_id,
142
        spc.id,
143
        sr.timestamp,
144
        -- Extract raw_value: handle both scalar {"param": 123} and object {"param": {"raw_value": 123}}
145
        CASE
146
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'number'
147
                THEN (sr.parameters_json->kv.key)::BIGINT
148
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'object'
149
                THEN (sr.parameters_json->kv.key->>'raw_value')::BIGINT
150
            ELSE NULL::BIGINT
151
        END,
152
        CASE
153
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'object'
154
                THEN (sr.parameters_json->kv.key->>'value')::SMALLINT
155
            ELSE NULL::SMALLINT
156
        END,
157
        CASE
158
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'object'
159
                THEN (sr.parameters_json->kv.key->>'worst')::SMALLINT
160
            ELSE NULL::SMALLINT
161
        END,
162
        CASE
163
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'object'
164
                THEN (sr.parameters_json->kv.key->>'thresh')::SMALLINT
165
            ELSE NULL::SMALLINT
166
        END,
167
        CASE
168
            WHEN jsonb_typeof(sr.parameters_json->kv.key) = 'object'
169
                THEN sr.parameters_json->kv.key->>'when_failed'
170
            ELSE NULL::VARCHAR
171
        END
172
    FROM smart_readings sr
173
    JOIN smart_collection_events sce
174
        ON sce.hdd_id = sr.hdd_id
175
        AND sce.collected_at = sr.timestamp
176
        AND sce.serial_number = sr.serial_number
177
    CROSS JOIN LATERAL jsonb_each_text(COALESCE(sr.parameters_json, '{}'::jsonb)) AS kv(key, value)
178
    LEFT JOIN smart_param_catalog spc ON spc.param_name = kv.key
179
    WHERE sr.reading_type IN ('baseline', 'full')
180
      AND sr.parameters_json IS NOT NULL
181
      AND sr.parameters_json != '{}'::jsonb
182
      AND spc.id IS NOT NULL;
183

            
184
    v_migrated := (SELECT COUNT(*) FROM smart_param_values);
185
    RAISE NOTICE '[PHASE 4] Migrated % parameter values', v_migrated;
186
END $$;
187

            
188
-- ============================================================================
189
-- PHASE 5: Populate param_id_ata from JSONB metadata
190
-- ============================================================================
191

            
192
DO $$
193
DECLARE
194
    v_updated INTEGER;
195
BEGIN
196
    RAISE NOTICE '[PHASE 5] Extracting ATA attribute IDs from JSONB...';
197

            
198
    UPDATE smart_param_catalog spc
199
    SET param_id_ata = subq.ata_id
200
    FROM (
201
        SELECT DISTINCT ON (kv.key)
202
            kv.key AS param_name,
203
            (sr.parameters_json->kv.key->>'id')::INTEGER AS ata_id
204
        FROM smart_readings sr
205
        CROSS JOIN LATERAL jsonb_each(sr.parameters_json) AS kv
206
        WHERE jsonb_typeof(sr.parameters_json->kv.key) = 'object'
207
          AND sr.parameters_json->kv.key->>'id' IS NOT NULL
208
        ORDER BY kv.key, sr.timestamp DESC
209
    ) subq
210
    WHERE spc.param_name = subq.param_name
211
      AND subq.ata_id IS NOT NULL
212
      AND spc.param_id_ata IS NULL;
213

            
214
    GET DIAGNOSTICS v_updated = ROW_COUNT;
215
    RAISE NOTICE '[PHASE 5] Updated % parameters with ATA IDs', v_updated;
216
END $$;
217

            
218
-- ============================================================================
219
-- PHASE 6: Integrity verification
220
-- ============================================================================
221

            
222
DO $$
223
DECLARE
224
    v_orig_full_readings    BIGINT;
225
    v_migrated_events       BIGINT;
226
    v_migrated_values       BIGINT;
227
    v_avg_params_per_event  NUMERIC;
228
    v_catalog_params        BIGINT;
229
BEGIN
230
    RAISE NOTICE '[PHASE 6] Verifying migration integrity...';
231

            
232
    SELECT COUNT(*) INTO v_orig_full_readings
233
    FROM smart_readings WHERE reading_type IN ('baseline', 'full');
234

            
235
    SELECT COUNT(*) INTO v_migrated_events
236
    FROM smart_collection_events;
237

            
238
    SELECT COUNT(*) INTO v_migrated_values
239
    FROM smart_param_values;
240

            
241
    SELECT COUNT(*) INTO v_catalog_params
242
    FROM smart_param_catalog;
243

            
244
    v_avg_params_per_event := ROUND(v_migrated_values::NUMERIC / NULLIF(v_migrated_events, 0), 2);
245

            
246
    RAISE NOTICE '═══════════════════════════════════════════════';
247
    RAISE NOTICE 'MIGRATION SUMMARY:';
248
    RAISE NOTICE '───────────────────────────────────────────────';
249
    RAISE NOTICE 'Original full/baseline readings:    %', v_orig_full_readings;
250
    RAISE NOTICE 'Migrated collection events:        %', v_migrated_events;
251
    RAISE NOTICE 'Migrated parameter values:         %', v_migrated_values;
252
    RAISE NOTICE 'Average params per event:          %', v_avg_params_per_event;
253
    RAISE NOTICE 'Parameters in catalog:             %', v_catalog_params;
254
    RAISE NOTICE '═══════════════════════════════════════════════';
255

            
256
    IF v_migrated_events = 0 THEN
257
        RAISE EXCEPTION 'ERROR: No events migrated! Check smart_readings data.';
258
    END IF;
259

            
260
    IF v_migrated_events < v_orig_full_readings THEN
261
        RAISE WARNING 'Event count (%) less than original (%). Possible: timestamp duplicates detected and consolidated.',
262
            v_migrated_events, v_orig_full_readings;
263
    END IF;
264

            
265
    IF v_migrated_values = 0 THEN
266
        RAISE WARNING 'No parameter values migrated! Check JSONB format.';
267
    END IF;
268

            
269
    RAISE NOTICE 'Migration integrity check: PASSED ✓';
270
END $$;
271

            
272
-- ============================================================================
273
-- PHASE 7: Sample data validation
274
-- ============================================================================
275

            
276
DO $$
277
DECLARE
278
    v_rec RECORD;
279
    v_count INTEGER;
280
BEGIN
281
    RAISE NOTICE '[PHASE 7] Sample data validation...';
282

            
283
    SELECT COUNT(*) INTO v_count
284
    FROM smart_param_values
285
    WHERE raw_value IS NOT NULL;
286

            
287
    RAISE NOTICE 'Records with raw_value: % / %', v_count,
288
        (SELECT COUNT(*) FROM smart_param_values);
289

            
290
    FOR v_rec IN
291
        SELECT serial_number, COUNT(*) as event_count, SUM(param_count) as total_params
292
        FROM smart_collection_events
293
        GROUP BY serial_number
294
        ORDER BY event_count DESC
295
        LIMIT 5
296
    LOOP
297
        RAISE NOTICE 'Disk: %, Events: %, Total params across events: %',
298
            v_rec.serial_number, v_rec.event_count, v_rec.total_params;
299
    END LOOP;
300

            
301
    RAISE NOTICE '[PHASE 7] Sample validation: PASSED ✓';
302
END $$;
303

            
304
-- ============================================================================
305
-- FINAL SUMMARY
306
-- ============================================================================
307

            
308
DO $$
309
BEGIN
310
    RAISE NOTICE '';
311
    RAISE NOTICE '╔════════════════════════════════════════════════════════════════╗';
312
    RAISE NOTICE '║ ✅ Migration v1 → v2 Complete!                                 ║';
313
    RAISE NOTICE '╠════════════════════════════════════════════════════════════════╣';
314
    RAISE NOTICE '║                                                                ║';
315
    RAISE NOTICE '║ New data is now in:                                            ║';
316
    RAISE NOTICE '║   • smart_collection_events (metadata per-collection)           ║';
317
    RAISE NOTICE '║   • smart_param_values (parameter values, partitioned)          ║';
318
    RAISE NOTICE '║   • smart_param_catalog (parameter registry)                    ║';
319
    RAISE NOTICE '║                                                                ║';
320
    RAISE NOTICE '║ Views available:                                               ║';
321
    RAISE NOTICE '║   • v_latest_param_values                                       ║';
322
    RAISE NOTICE '║   • v_drive_health_summary                                      ║';
323
    RAISE NOTICE '║   • v_param_trend                                               ║';
324
    RAISE NOTICE '║   • v_cluster_overview                                          ║';
325
    RAISE NOTICE '║   • v_smart_readings_compat (backward-compatible)               ║';
326
    RAISE NOTICE '║                                                                ║';
327
    RAISE NOTICE '║ Next steps:                                                    ║';
328
    RAISE NOTICE '║   1. Verify data: SELECT * FROM v_drive_health_summary;        ║';
329
    RAISE NOTICE '║   2. Run collation fix: ALTER DATABASE autosmart               ║';
330
    RAISE NOTICE '║      REFRESH COLLATION VERSION;                                ║';
331
    RAISE NOTICE '║   3. Archive old table: ALTER TABLE smart_readings             ║';
332
    RAISE NOTICE '║      RENAME TO smart_readings_archive_v1;                       ║';
333
    RAISE NOTICE '║   4. Update collectors (SmartCollector.pm, daemon)              ║';
334
    RAISE NOTICE '║                                                                ║';
335
    RAISE NOTICE '╚════════════════════════════════════════════════════════════════╝';
336
    RAISE NOTICE '';
337
END $$;
338

            
339
COMMIT;