Skip to content

Commit

Permalink
increase max conns to 50, update release code
Browse files Browse the repository at this point in the history
  • Loading branch information
joshghent committed Oct 9, 2024
1 parent 796a9a0 commit 56d9dd7
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 111 deletions.
2 changes: 1 addition & 1 deletion Ango/Get code prod droplet.bru
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ body:json {
{
"batchid": "11111111-1111-1111-1111-111111111111",
"clientid": "217be7c8-679c-4e08-bffc-db3451bdcdbf",
"customerid": "bc22e256-e902-4449-b660-ba9bebc1e7dc"
"customerid": "fba9230a-a521-430e-aaf8-8aefbf588072"
}
}
6 changes: 3 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func connectToDB() (*pgxpool.Pool, error) {

for i := 0; i < maxRetries; i++ {
config, _ := pgxpool.ParseConfig(databaseURL)
config.MaxConns = 20
config.MaxConnIdleTime = 30 * time.Second // Reduced from 30 minutes
config.MaxConnLifetime = 1 * time.Hour // Reduced from 2 hours
config.MaxConns = 50
config.MaxConnIdleTime = 30 * time.Second
config.MaxConnLifetime = 1 * time.Hour
config.HealthCheckPeriod = 1 * time.Minute
config.ConnConfig.ConnectTimeout = 5 * time.Second

Expand Down
214 changes: 107 additions & 107 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,140 +31,140 @@ type Rules struct {

type CachedRules struct {
Rules Rules
Expired bool
CacheTime time.Time
}

func getCode(ctx context.Context, req Request) (string, error) {
// Validate UUIDs
if _, err := uuid.Parse(req.BatchID); err != nil {
return "", gin.Error{
Err: errors.New("invalid batch_id format"),
Type: gin.ErrorTypePublic,
}
}
if _, err := uuid.Parse(req.ClientID); err != nil {
return "", gin.Error{
Err: errors.New("invalid client_id format"),
Type: gin.ErrorTypePublic,
}
}
if _, err := uuid.Parse(req.CustomerID); err != nil {
return "", gin.Error{
Err: errors.New("invalid customer_id format"),
Type: gin.ErrorTypePublic,
}
}

ctx, cancel := context.WithTimeout(ctx, 15*time.Second) // Extended timeout
defer cancel()

// Check batch expiration outside of the transaction
var batchExpired bool
err := db.QueryRow(ctx, `
SELECT expired
FROM batches
WHERE id = $1
`, req.BatchID).Scan(&batchExpired)
if err != nil {
if err == pgx.ErrNoRows {
return "", ErrNoBatchFound
}
return "", err
}
if batchExpired {
return "", ErrBatchExpired
}

// Begin transaction after initial check
tx, err := db.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
return "", err
}
defer tx.Rollback(ctx) // Ensure rollback if not committed

selectCodeTime := time.Now()

// Attempt to acquire a code
var code string
err = tx.QueryRow(ctx, `
SELECT code
FROM codes
WHERE batch_id = $1 AND client_id = $2 AND customer_id IS NULL
FOR NO KEY UPDATE SKIP LOCKED
LIMIT 1
`, req.BatchID, req.ClientID).Scan(&code)
if err != nil {
if err == pgx.ErrNoRows {
return "", ErrNoCodeFound
}
return "", err
}

if time.Since(selectCodeTime) > 100*time.Millisecond {
log.Printf("Queries for selecting code took too long (%v)ms", time.Since(selectCodeTime))
}

// Retrieve rules from cache or database
rules, err := getRulesForBatch(ctx, tx, req.BatchID)
if err != nil {
return "", err
}

if !checkRules(rules, req.CustomerID) {
return "", ErrConditionNotMet
}

updateCodesTime := time.Now()
_, err = tx.Exec(ctx, "UPDATE codes SET customer_id=$1 WHERE code=$2", req.CustomerID, code)
if err != nil {
return "", err
}
if time.Since(updateCodesTime) > 100*time.Millisecond {
log.Printf("Query for updating codes took too long (%v)ms", time.Since(updateCodesTime))
}

insertCodeUsageTime := time.Now()
_, err = tx.Exec(ctx, "INSERT INTO code_usage (code, batch_id, client_id, customer_id, used_at) VALUES ($1, $2, $3, $4, $5)", code, req.BatchID, req.ClientID, req.CustomerID, time.Now())
if err != nil {
return "", err
}
if time.Since(insertCodeUsageTime) > 100*time.Millisecond {
log.Printf("Query for inserting code usage took too long (%v)ms", time.Since(insertCodeUsageTime))
}

if err = tx.Commit(ctx); err != nil {
return "", err
}

return code, nil
// Validate UUIDs
if _, err := uuid.Parse(req.BatchID); err != nil {
return "", gin.Error{
Err: errors.New("invalid batch_id format"),
Type: gin.ErrorTypePublic,
}
}
if _, err := uuid.Parse(req.ClientID); err != nil {
return "", gin.Error{
Err: errors.New("invalid client_id format"),
Type: gin.ErrorTypePublic,
}
}
if _, err := uuid.Parse(req.CustomerID); err != nil {
return "", gin.Error{
Err: errors.New("invalid customer_id format"),
Type: gin.ErrorTypePublic,
}
}

ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()

// Check batch expiration from cache
rules, batchExpired, err := getRulesForBatch(ctx, req.BatchID)
if err != nil {
if err == pgx.ErrNoRows {
return "", ErrNoBatchFound
}
return "", err
}
if batchExpired {
return "", ErrBatchExpired
}

// Begin transaction after initial check
tx, err := db.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
return "", err
}
defer func() {
if tx != nil {
tx.Rollback(ctx) // Ensure rollback if not committed
}
}()

selectCodeTime := time.Now()

// Attempt to acquire a code
var code string
err = tx.QueryRow(ctx, `
SELECT code
FROM codes
WHERE batch_id = $1 AND client_id = $2 AND customer_id IS NULL
FOR NO KEY UPDATE SKIP LOCKED
LIMIT 1
`, req.BatchID, req.ClientID).Scan(&code)
if err != nil {
if err == pgx.ErrNoRows {
return "", ErrNoCodeFound
}
return "", err
}

if time.Since(selectCodeTime) > 100*time.Millisecond {
log.Printf("Queries for selecting code took too long (%v)ms", time.Since(selectCodeTime))
}

if !checkRules(rules, req.CustomerID) {
return "", ErrConditionNotMet
}

// Code usage updates
updateCodesTime := time.Now()
_, err = tx.Exec(ctx, "UPDATE codes SET customer_id=$1 WHERE code=$2", req.CustomerID, code)
if err != nil {
return "", err
}
if time.Since(updateCodesTime) > 100*time.Millisecond {
log.Printf("Query for updating codes took too long (%v)ms", time.Since(updateCodesTime))
}

// Remove code usage because it's not needed, will queue for later.
// insertCodeUsageTime := time.Now()
// _, err = tx.Exec(ctx, "INSERT INTO code_usage (code, batch_id, client_id, customer_id, used_at) VALUES ($1, $2, $3, $4, $5)", code, req.BatchID, req.ClientID, req.CustomerID, time.Now())
// if err != nil {
// return "", err
// }
// if time.Since(insertCodeUsageTime) > 100*time.Millisecond {
// log.Printf("Query for inserting code usage took too long (%v)ms", time.Since(insertCodeUsageTime))
// }

if err = tx.Commit(ctx); err != nil {
return "", err
}
tx = nil // Avoid rollback

return code, nil
}

func getRulesForBatch(ctx context.Context, tx pgx.Tx, batchID string) (Rules, error) {

func getRulesForBatch(ctx context.Context, batchID string) (Rules, bool, error) {
// Check cache first
if cached, found := batchCache.Load(batchID); found {
cachedRules := cached.(CachedRules)
// Check if the cache is still valid
if time.Since(cachedRules.CacheTime) < cacheExpiration {
return cachedRules.Rules, nil
return cachedRules.Rules, cachedRules.Expired, nil
}
// Cache expired, delete it
batchCache.Delete(batchID)
}

// If not in cache or cache expired, fetch from database
var rules Rules
err := tx.QueryRow(ctx, "SELECT rules FROM batches WHERE id=$1", batchID).Scan(&rules)
var expired bool
err := db.QueryRow(ctx, "SELECT rules, expired FROM batches WHERE id=$1", batchID).Scan(&rules, &expired)
if err != nil {
return Rules{}, err
return Rules{}, false, err
}

// Store the fetched rules in cache
batchCache.Store(batchID, CachedRules{
Rules: rules,
Expired: expired,
CacheTime: time.Now(),
})

return rules, nil
return rules, expired, nil
}

func getBatches(ctx context.Context) ([]Batch, error) {
Expand Down

0 comments on commit 56d9dd7

Please sign in to comment.