github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/events/database/database.go (about) 1 package database 2 3 import ( 4 "bytes" 5 "context" 6 "database/sql" 7 "encoding/json" 8 "net" 9 "time" 10 11 "github.com/golang/glog" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/metrics" 14 "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" 15 "github.com/prebid/prebid-server/v2/stored_requests/events" 16 "github.com/prebid/prebid-server/v2/util/timeutil" 17 ) 18 19 func bytesNull() []byte { 20 return []byte{'n', 'u', 'l', 'l'} 21 } 22 23 var storedDataTypeMetricMap = map[config.DataType]metrics.StoredDataType{ 24 config.RequestDataType: metrics.RequestDataType, 25 config.CategoryDataType: metrics.CategoryDataType, 26 config.VideoDataType: metrics.VideoDataType, 27 config.AMPRequestDataType: metrics.AMPDataType, 28 config.AccountDataType: metrics.AccountDataType, 29 config.ResponseDataType: metrics.ResponseDataType, 30 } 31 32 type DatabaseEventProducerConfig struct { 33 Provider db_provider.DbProvider 34 RequestType config.DataType 35 CacheInitQuery string 36 CacheInitTimeout time.Duration 37 CacheUpdateQuery string 38 CacheUpdateTimeout time.Duration 39 MetricsEngine metrics.MetricsEngine 40 } 41 42 type DatabaseEventProducer struct { 43 cfg DatabaseEventProducerConfig 44 lastUpdate time.Time 45 invalidations chan events.Invalidation 46 saves chan events.Save 47 time timeutil.Time 48 } 49 50 func NewDatabaseEventProducer(cfg DatabaseEventProducerConfig) (eventProducer *DatabaseEventProducer) { 51 if cfg.Provider == nil { 52 glog.Fatalf("The Database Stored %s Loader needs a database connection to work.", cfg.RequestType) 53 } 54 55 return &DatabaseEventProducer{ 56 cfg: cfg, 57 lastUpdate: time.Time{}, 58 saves: make(chan events.Save, 1), 59 invalidations: make(chan events.Invalidation, 1), 60 time: &timeutil.RealTime{}, 61 } 62 } 63 64 func (e *DatabaseEventProducer) Run() error { 65 if e.lastUpdate.IsZero() { 66 return e.fetchAll() 67 } 68 69 return e.fetchDelta() 70 } 71 72 func (e *DatabaseEventProducer) Saves() <-chan events.Save { 73 return e.saves 74 } 75 76 func (e *DatabaseEventProducer) Invalidations() <-chan events.Invalidation { 77 return e.invalidations 78 } 79 80 func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { 81 timeout := e.cfg.CacheInitTimeout * time.Millisecond 82 ctx, cancel := context.WithTimeout(context.Background(), timeout) 83 defer cancel() 84 85 startTime := e.time.Now().UTC() 86 rows, err := e.cfg.Provider.QueryContext(ctx, e.cfg.CacheInitQuery) 87 elapsedTime := time.Since(startTime) 88 e.recordFetchTime(elapsedTime, metrics.FetchAll) 89 90 if err != nil { 91 glog.Warningf("Failed to fetch all Stored %s data from the DB: %v", e.cfg.RequestType, err) 92 if _, ok := err.(net.Error); ok { 93 e.recordError(metrics.StoredDataErrorNetwork) 94 } else { 95 e.recordError(metrics.StoredDataErrorUndefined) 96 } 97 return err 98 } 99 100 defer func() { 101 if err := rows.Close(); err != nil { 102 glog.Warningf("Failed to close the Stored %s DB connection: %v", e.cfg.RequestType, err) 103 e.recordError(metrics.StoredDataErrorUndefined) 104 fetchErr = err 105 } 106 }() 107 if err := e.sendEvents(rows); err != nil { 108 glog.Warningf("Failed to load all Stored %s data from the DB: %v", e.cfg.RequestType, err) 109 e.recordError(metrics.StoredDataErrorUndefined) 110 return err 111 } 112 113 e.lastUpdate = startTime 114 return nil 115 } 116 117 func (e *DatabaseEventProducer) fetchDelta() (fetchErr error) { 118 timeout := e.cfg.CacheUpdateTimeout * time.Millisecond 119 ctx, cancel := context.WithTimeout(context.Background(), timeout) 120 defer cancel() 121 122 startTime := e.time.Now().UTC() 123 124 params := []db_provider.QueryParam{ 125 {Name: "LAST_UPDATED", Value: e.lastUpdate}, 126 } 127 128 rows, err := e.cfg.Provider.QueryContext(ctx, e.cfg.CacheUpdateQuery, params...) 129 elapsedTime := time.Since(startTime) 130 e.recordFetchTime(elapsedTime, metrics.FetchDelta) 131 132 if err != nil { 133 glog.Warningf("Failed to fetch updated Stored %s data from the DB: %v", e.cfg.RequestType, err) 134 if _, ok := err.(net.Error); ok { 135 e.recordError(metrics.StoredDataErrorNetwork) 136 } else { 137 e.recordError(metrics.StoredDataErrorUndefined) 138 } 139 return err 140 } 141 142 defer func() { 143 if err := rows.Close(); err != nil { 144 glog.Warningf("Failed to close the Stored %s DB connection: %v", e.cfg.RequestType, err) 145 e.recordError(metrics.StoredDataErrorUndefined) 146 fetchErr = err 147 } 148 }() 149 if err := e.sendEvents(rows); err != nil { 150 glog.Warningf("Failed to load updated Stored %s data from the DB: %v", e.cfg.RequestType, err) 151 e.recordError(metrics.StoredDataErrorUndefined) 152 return err 153 } 154 155 e.lastUpdate = startTime 156 return nil 157 } 158 159 func (e *DatabaseEventProducer) recordFetchTime(elapsedTime time.Duration, fetchType metrics.StoredDataFetchType) { 160 e.cfg.MetricsEngine.RecordStoredDataFetchTime( 161 metrics.StoredDataLabels{ 162 DataType: storedDataTypeMetricMap[e.cfg.RequestType], 163 DataFetchType: fetchType, 164 }, elapsedTime) 165 } 166 167 func (e *DatabaseEventProducer) recordError(errorType metrics.StoredDataError) { 168 e.cfg.MetricsEngine.RecordStoredDataError( 169 metrics.StoredDataLabels{ 170 DataType: storedDataTypeMetricMap[e.cfg.RequestType], 171 Error: errorType, 172 }) 173 } 174 175 // sendEvents reads the rows and sends notifications into the channel for any updates. 176 // If it returns an error, then callers can be certain that no events were sent to the channels. 177 func (e *DatabaseEventProducer) sendEvents(rows *sql.Rows) (err error) { 178 storedRequestData := make(map[string]json.RawMessage) 179 storedImpData := make(map[string]json.RawMessage) 180 storedRespData := make(map[string]json.RawMessage) 181 182 var requestInvalidations []string 183 var impInvalidations []string 184 var respInvalidations []string 185 186 for rows.Next() { 187 var id string 188 var data []byte 189 var dataType string 190 191 // discard corrupted data so it is not saved in the cache 192 if err := rows.Scan(&id, &data, &dataType); err != nil { 193 return err 194 } 195 196 switch dataType { 197 case "request": 198 if len(data) == 0 || bytes.Equal(data, bytesNull()) { 199 requestInvalidations = append(requestInvalidations, id) 200 } else { 201 storedRequestData[id] = data 202 } 203 case "imp": 204 if len(data) == 0 || bytes.Equal(data, bytesNull()) { 205 impInvalidations = append(impInvalidations, id) 206 } else { 207 storedImpData[id] = data 208 } 209 case "response": 210 if len(data) == 0 || bytes.Equal(data, bytesNull()) { 211 respInvalidations = append(respInvalidations, id) 212 } else { 213 storedRespData[id] = data 214 } 215 default: 216 glog.Warningf("Stored Data with id=%s has invalid type: %s. This will be ignored.", id, dataType) 217 } 218 } 219 220 // discard corrupted data so it is not saved in the cache 221 if rows.Err() != nil { 222 return rows.Err() 223 } 224 225 if len(storedRequestData) > 0 || len(storedImpData) > 0 || len(storedRespData) > 0 { 226 e.saves <- events.Save{ 227 Requests: storedRequestData, 228 Imps: storedImpData, 229 Responses: storedRespData, 230 } 231 } 232 233 if (len(requestInvalidations) > 0 || len(impInvalidations) > 0 || len(respInvalidations) > 0) && !e.lastUpdate.IsZero() { 234 e.invalidations <- events.Invalidation{ 235 Requests: requestInvalidations, 236 Imps: impInvalidations, 237 Responses: respInvalidations, 238 } 239 } 240 241 return 242 }