Newer Older
f16725e 3 months ago History
389 lines | 15.545kb
Bogdan Timofte authored 3 months ago
1
-- autoSMART Database Schema - Fixed for PostgreSQL 15
2
-- This version removes problematic syntax and creates a working schema
3

            
4
-- Drop existing tables if they exist
5
DROP TABLE IF EXISTS smart_readings CASCADE;
6
DROP TABLE IF EXISTS predictions CASCADE;
7
DROP TABLE IF EXISTS alert_history CASCADE;
8
DROP TABLE IF EXISTS hdd_presence CASCADE;
9
DROP TABLE IF EXISTS hdd_inventory CASCADE;
10

            
11
-- Create required extensions
12
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
13
CREATE EXTENSION IF NOT EXISTS "btree_gin";
14

            
15
-- HDD Inventory Table (Hardware-based tracking)
16
CREATE TABLE hdd_inventory (
17
    id                  SERIAL PRIMARY KEY,
18
    serial_number       VARCHAR(100) NOT NULL,
19
    model_name          VARCHAR(200) NOT NULL,
20
    firmware            VARCHAR(50),
21
    size_gb             INTEGER,
22
    manufacturer        VARCHAR(100),
23
    current_device_path VARCHAR(50),
24
    current_node_id     VARCHAR(50),
25
    current_slot        VARCHAR(20),
26
    madagascar_id       VARCHAR(100),
27
    first_seen          TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
28
    last_seen           TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
29
    status              VARCHAR(20) DEFAULT 'active',
30
    status_changed_at   TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
31
    notes               TEXT,
32
    created_at          TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
33
    updated_at          TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
34

            
35
    -- Hardware identification constraint
36
    CONSTRAINT unique_hardware_id UNIQUE (serial_number, model_name)
37
);
38

            
39
-- Create index for device path (but allow NULLs and duplicates)
40
CREATE INDEX idx_hdd_inventory_device_path ON hdd_inventory(current_device_path) WHERE current_device_path IS NOT NULL;
41
CREATE INDEX idx_hdd_inventory_node ON hdd_inventory(current_node_id);
42
CREATE INDEX idx_hdd_inventory_status ON hdd_inventory(status);
43
CREATE INDEX idx_hdd_inventory_last_seen ON hdd_inventory(last_seen);
44

            
45
-- HDD Presence Table (tracks HDD mobility across nodes)
46
CREATE TABLE hdd_presence (
47
    id SERIAL PRIMARY KEY,
48
    serial_number VARCHAR(64) NOT NULL,
49
    node VARCHAR(64) NOT NULL,
50
    data_start TIMESTAMP NOT NULL,
51
    data_end TIMESTAMP NOT NULL,
52
    is_current BOOLEAN NOT NULL DEFAULT TRUE
53
);
54

            
55
CREATE INDEX idx_hdd_presence_serial_current ON hdd_presence(serial_number, is_current);
56
CREATE INDEX idx_hdd_presence_node ON hdd_presence(node);
57
CREATE INDEX idx_hdd_presence_data_end ON hdd_presence(data_end DESC);
58

            
59
-- SMART Readings Table (with differential storage)
60
CREATE TABLE smart_readings (
61
    id                   BIGSERIAL PRIMARY KEY,
62
    hdd_id               INTEGER REFERENCES hdd_inventory(id),
63
    serial_number        VARCHAR(100) NOT NULL,
64
    device_path          VARCHAR(50),
65
    node_id              VARCHAR(50),
66
    timestamp            TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
67
    collection_ok        BOOLEAN DEFAULT true,
68
    temperature          INTEGER,
69
    parameters_json      JSONB,
70
    reading_type         VARCHAR(20) DEFAULT 'full',
71
    changes_detected     BOOLEAN DEFAULT true,
72
    changed_parameters   JSONB,
73
    previous_reading_id  INTEGER REFERENCES smart_readings(id),
74
    checksum             VARCHAR(64)
75
);
76

            
77
CREATE INDEX idx_smart_readings_hdd_id ON smart_readings(hdd_id);
78
CREATE INDEX idx_smart_readings_timestamp ON smart_readings(timestamp DESC);
79
CREATE INDEX idx_smart_readings_serial ON smart_readings(serial_number);
80
CREATE INDEX idx_smart_readings_device_path ON smart_readings(device_path);
81
CREATE INDEX idx_smart_readings_type ON smart_readings(reading_type);
82
CREATE INDEX idx_smart_readings_checksum ON smart_readings(checksum);
83
CREATE INDEX idx_smart_readings_previous ON smart_readings(previous_reading_id);
84

            
85
-- GIN index for JSONB parameters
86
CREATE INDEX idx_smart_readings_parameters ON smart_readings USING GIN (parameters_json);
87
CREATE INDEX idx_smart_readings_changed_params ON smart_readings USING GIN (changed_parameters);
88

            
89
-- Predictions Table
90
CREATE TABLE predictions (
91
    id                    SERIAL PRIMARY KEY,
92
    hdd_id                INTEGER REFERENCES hdd_inventory(id),
93
    serial_number         VARCHAR(100) NOT NULL,
94
    device_path           VARCHAR(50),
95
    timestamp             TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
96
    risk_level            VARCHAR(20),
97
    failure_probability   DECIMAL(5,4),
98
    predicted_failure_date DATE,
99
    confidence_score      DECIMAL(5,4),
100
    analysis_summary      TEXT,
101
    recommendations       JSONB,
102
    openai_response       JSONB,
103
    created_at            TIMESTAMP WITH TIME ZONE DEFAULT NOW()
104
);
105

            
106
CREATE INDEX idx_predictions_hdd_id ON predictions(hdd_id);
107
CREATE INDEX idx_predictions_timestamp ON predictions(timestamp DESC);
108
CREATE INDEX idx_predictions_risk_level ON predictions(risk_level);
109
CREATE INDEX idx_predictions_serial ON predictions(serial_number);
110

            
111
-- Alert History Table
112
CREATE TABLE alert_history (
113
    id              SERIAL PRIMARY KEY,
114
    hdd_id          INTEGER REFERENCES hdd_inventory(id),
115
    serial_number   VARCHAR(100) NOT NULL,
116
    alert_type      VARCHAR(50),
117
    severity        VARCHAR(20),
118
    message         TEXT,
119
    sent_at         TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
120
    sent_to         TEXT,
121
    delivery_status VARCHAR(20) DEFAULT 'pending',
122
    related_reading_id BIGINT REFERENCES smart_readings(id),
123
    related_prediction_id INTEGER REFERENCES predictions(id)
124
);
125

            
126
CREATE INDEX idx_alert_history_hdd_id ON alert_history(hdd_id);
127
CREATE INDEX idx_alert_history_sent_at ON alert_history(sent_at DESC);
128
CREATE INDEX idx_alert_history_severity ON alert_history(severity);
129
CREATE INDEX idx_alert_history_serial ON alert_history(serial_number);
130

            
131
-- System Configuration Table
132
CREATE TABLE IF NOT EXISTS system_config (
133
    id          SERIAL PRIMARY KEY,
134
    config_key  VARCHAR(100) UNIQUE NOT NULL,
135
    value       TEXT,
136
    description TEXT,
137
    created_at  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
138
    updated_at  TIMESTAMP WITH TIME ZONE DEFAULT NOW()
139
);
140

            
141
-- Insert default configuration
142
INSERT INTO system_config (config_key, value, description) VALUES
143
('collection_interval_seconds', '1800', 'SMART data collection interval in seconds')
144
ON CONFLICT (config_key) DO NOTHING;
145

            
146
INSERT INTO system_config (config_key, value, description) VALUES
147
('differential_storage_enabled', 'true', 'Enable differential storage optimization'),
148
('forced_storage_interval_hours', '24', 'Hours between forced full readings'),
149
('critical_parameter_force_store', 'true', 'Force storage for critical parameter changes'),
150
('temperature_change_threshold', '5', 'Temperature change threshold for storage (Celsius)')
151
ON CONFLICT (config_key) DO NOTHING;
152

            
153
-- SMART Thresholds Table
154
CREATE TABLE IF NOT EXISTS smart_thresholds (
155
    id                SERIAL PRIMARY KEY,
156
    parameter_name    VARCHAR(100) NOT NULL,
157
    warning_threshold NUMERIC,
158
    critical_threshold NUMERIC,
159
    weight            NUMERIC DEFAULT 1.0,
160
    enabled           BOOLEAN DEFAULT true,
161
    description       TEXT,
162
    created_at        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
163
    updated_at        TIMESTAMP WITH TIME ZONE DEFAULT NOW()
164
);
165

            
166
-- Insert default SMART thresholds
167
INSERT INTO smart_thresholds (parameter_name, warning_threshold, critical_threshold, weight, description) VALUES
168
('Reallocated_Sector_Ct', 1, 5, 10.0, 'Reallocated sector count - critical for drive health'),
169
('Spin_Retry_Count', 1, 10, 8.0, 'Spindle motor retry attempts'),
170
('Reallocated_Event_Count', 1, 10, 9.0, 'Number of reallocation events'),
171
('Current_Pending_Sector', 1, 5, 9.5, 'Sectors waiting to be reallocated'),
172
('Offline_Uncorrectable', 1, 1, 10.0, 'Uncorrectable sectors found during offline scan'),
173
('UDMA_CRC_Error_Count', 10, 50, 5.0, 'Communication errors between drive and controller'),
174
('Raw_Read_Error_Rate', 100000, 1000000, 3.0, 'Raw read error rate (varies by manufacturer)'),
175
('Seek_Error_Rate', 100000, 1000000, 4.0, 'Seek error rate'),
176
('Power_On_Hours', 35000, 50000, 2.0, 'Total power-on time in hours'),
177
('Load_Cycle_Count', 100000, 300000, 2.0, 'Number of head load/unload cycles'),
178
('Temperature_Celsius', 50, 60, 3.0, 'Drive operating temperature'),
179
('Start_Stop_Count', 10000, 50000, 1.0, 'Drive start/stop cycles'),
180
('Power_Cycle_Count', 10000, 20000, 1.0, 'Number of power cycles')
181
ON CONFLICT (parameter_name) DO NOTHING;
182

            
183
-- Create view for reconstructed SMART data (handles differential storage)
184
CREATE VIEW smart_readings_reconstructed AS
185
WITH RECURSIVE reading_chain AS (
186
    -- Base case: get baseline readings
187
    SELECT
188
        id, hdd_id, serial_number, timestamp,
189
        parameters_json, temperature, reading_type,
190
        previous_reading_id, 1 as chain_level
191
    FROM smart_readings
192
    WHERE reading_type IN ('baseline', 'full')
193

            
194
    UNION ALL
195

            
196
    -- Recursive case: follow the chain of differential readings
197
    SELECT
198
        sr.id, sr.hdd_id, sr.serial_number, sr.timestamp,
199
        -- Merge parameters from previous reading with current changes
200
        COALESCE(rc.parameters_json, '{}'::jsonb) || sr.parameters_json as parameters_json,
201
        COALESCE(sr.temperature, rc.temperature) as temperature,
202
        sr.reading_type,
203
        sr.previous_reading_id,
204
        rc.chain_level + 1
205
    FROM smart_readings sr
206
    JOIN reading_chain rc ON sr.previous_reading_id = rc.id
207
    WHERE sr.reading_type = 'differential'
208
)
209
SELECT
210
    id, hdd_id, serial_number, timestamp,
211
    parameters_json, temperature, reading_type,
212
    chain_level
213
FROM reading_chain;
214

            
215
-- Latest SMART readings for all drives (using reconstructed differential data)
216
CREATE VIEW latest_smart_readings AS
217
SELECT DISTINCT ON (sr.hdd_id)
218
    sr.id,
219
    sr.hdd_id,
220
    sr.serial_number,
221
    sr.timestamp,
222
    sr.parameters_json,
223
    sr.temperature,
224
    hi.model_name,
225
    hi.manufacturer,
226
    hi.size_gb,
227
    hi.current_device_path,
228
    hi.current_node_id
229
FROM smart_readings_reconstructed sr
230
JOIN hdd_inventory hi ON sr.hdd_id = hi.id
231
ORDER BY sr.hdd_id, sr.timestamp DESC;
232

            
233
-- Drive health summary view
234
CREATE VIEW drive_health_summary AS
235
SELECT
236
    hi.id as hdd_id,
237
    hi.serial_number,
238
    hi.model_name,
239
    hi.manufacturer,
240
    hi.current_device_path,
241
    hi.current_node_id,
242
    hi.status,
243
    lsr.timestamp as last_reading,
244
    lsr.temperature,
245
    p.risk_level,
246
    p.failure_probability,
247
    p.predicted_failure_date,
248
    EXTRACT(EPOCH FROM (NOW() - lsr.timestamp))/3600 as hours_since_last_reading
249
FROM hdd_inventory hi
250
LEFT JOIN latest_smart_readings lsr ON hi.id = lsr.hdd_id
251
LEFT JOIN LATERAL (
252
    SELECT risk_level, failure_probability, predicted_failure_date
253
    FROM predictions
254
    WHERE hdd_id = hi.id
255
    ORDER BY timestamp DESC
256
    LIMIT 1
257
) p ON true
258
WHERE hi.status = 'active';
259

            
260
-- Function to check if SMART reading should be stored (simplified version)
261
CREATE OR REPLACE FUNCTION should_store_smart_reading(
262
    p_hdd_id INTEGER,
263
    p_parameters_json JSONB,
264
    p_checksum VARCHAR(64),
265
    p_timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
266
) RETURNS TABLE(
267
    should_store BOOLEAN,
268
    reading_type VARCHAR(20),
269
    changes_detected BOOLEAN,
270
    changed_parameters JSONB,
271
    previous_reading_id INTEGER
272
) AS $$
273
DECLARE
274
    v_last_reading RECORD;
275
    v_config_enabled BOOLEAN := true;
276
    v_force_interval_hours INTEGER := 24;
277
    v_temp_threshold INTEGER := 5;
278
BEGIN
279
    -- Get configuration
280
    SELECT (value::boolean) INTO v_config_enabled
281
    FROM system_config WHERE config_key = 'differential_storage_enabled';
282

            
283
    SELECT (value::integer) INTO v_force_interval_hours
284
    FROM system_config WHERE config_key = 'forced_storage_interval_hours';
285

            
286
    SELECT (value::integer) INTO v_temp_threshold
287
    FROM system_config WHERE config_key = 'temperature_change_threshold';
288

            
289
    -- If differential storage is disabled, always store as full
290
    IF v_config_enabled IS FALSE OR v_config_enabled IS NULL THEN
291
        RETURN QUERY SELECT true, 'full'::varchar(20), true, NULL::jsonb, NULL::integer;
292
        RETURN;
293
    END IF;
294

            
295
    -- Get the last reading for this HDD
296
    SELECT id, checksum, timestamp, parameters_json, temperature
297
    INTO v_last_reading
298
    FROM smart_readings
299
    WHERE hdd_id = p_hdd_id
300
    ORDER BY timestamp DESC
301
    LIMIT 1;
302

            
303
    -- If no previous reading, store as baseline
304
    IF v_last_reading IS NULL THEN
305
        RETURN QUERY SELECT true, 'baseline'::varchar(20), true, NULL::jsonb, NULL::integer;
306
        RETURN;
307
    END IF;
308

            
309
    -- If checksum matches, no changes detected
310
    IF v_last_reading.checksum = p_checksum THEN
311
        RETURN QUERY SELECT false, 'skipped'::varchar(20), false, NULL::jsonb, v_last_reading.id;
312
        RETURN;
313
    END IF;
314

            
315
    -- If forced interval exceeded, store as full
316
    IF p_timestamp > v_last_reading.timestamp + (v_force_interval_hours || ' hours')::interval THEN
317
        RETURN QUERY SELECT true, 'full'::varchar(20), true, NULL::jsonb, v_last_reading.id;
318
        RETURN;
319
    END IF;
320

            
321
    -- Otherwise, store as differential
322
    RETURN QUERY SELECT true, 'differential'::varchar(20), true, '[]'::jsonb, v_last_reading.id;
323
    RETURN;
324
END;
325
$$ LANGUAGE plpgsql;
326

            
327
-- Function to update HDD presence tracking
328
CREATE OR REPLACE FUNCTION update_hdd_presence(
329
    p_serial_number VARCHAR(64),
330
    p_node VARCHAR(64)
331
) RETURNS VOID AS $$
332
BEGIN
333
    -- Mark all previous presence records for this serial as historic
334
    UPDATE hdd_presence
335
    SET is_current = FALSE
336
    WHERE serial_number = p_serial_number AND is_current = TRUE AND node <> p_node;
337

            
338
    -- Check if there's already a current presence for this serial/node
339
    IF EXISTS (SELECT 1 FROM hdd_presence WHERE serial_number = p_serial_number AND node = p_node AND is_current = TRUE) THEN
340
        -- Update data_end for existing current presence
341
        UPDATE hdd_presence
342
        SET data_end = NOW()
343
        WHERE serial_number = p_serial_number AND node = p_node AND is_current = TRUE;
344
    ELSE
345
        -- Create new presence record
346
        INSERT INTO hdd_presence (serial_number, node, data_start, data_end, is_current)
347
        VALUES (p_serial_number, p_node, NOW(), NOW(), TRUE);
348
    END IF;
349
END;
350
$$ LANGUAGE plpgsql;
351

            
352
-- Function to update timestamps
353
CREATE OR REPLACE FUNCTION update_timestamp() RETURNS TRIGGER AS $$
354
BEGIN
355
    NEW.updated_at = NOW();
356
    RETURN NEW;
357
END;
358
$$ LANGUAGE plpgsql;
359

            
360
-- Create triggers for timestamp updates
361
CREATE TRIGGER update_hdd_inventory_timestamp
362
    BEFORE UPDATE ON hdd_inventory
363
    FOR EACH ROW EXECUTE FUNCTION update_timestamp();
364

            
365
CREATE TRIGGER update_smart_thresholds_timestamp
366
    BEFORE UPDATE ON smart_thresholds
367
    FOR EACH ROW EXECUTE FUNCTION update_timestamp();
368

            
369
CREATE TRIGGER update_system_config_timestamp
370
    BEFORE UPDATE ON system_config
371
    FOR EACH ROW EXECUTE FUNCTION update_timestamp();
372

            
373
-- Grant permissions to autosmart user
374
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO autosmart;
375
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO autosmart;
376
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO autosmart;
377

            
378
-- Grant specific permissions for hdd_presence table
379
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE hdd_presence TO autosmart;
380

            
381
-- Final message
382
DO $$
383
BEGIN
384
    RAISE NOTICE 'autoSMART database schema deployed successfully!';
385
    RAISE NOTICE 'Tables created: hdd_inventory, hdd_presence, smart_readings, predictions, smart_thresholds, alert_history, system_config';
386
    RAISE NOTICE 'Views created: smart_readings_reconstructed, latest_smart_readings, drive_health_summary';
387
    RAISE NOTICE 'Functions created: update_hdd_presence(), should_store_smart_reading()';
388
    RAISE NOTICE 'Permissions granted to autosmart user';
389
END $$;