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  }