본문으로 바로가기
728x90

Background

it's on a short-lived transaction and can have multiple operations.

So the event is designed as an append-only event logging table. Since records can’t be modified.

 

Managing Transaction

SQL Guide : https://support.unicomsi.com/manuals/soliddb/100/index.html#page/SQL_Guide/5_ManagingTransactions.06.1.html%231126545 : https://support.unicomsi.com/manuals/soliddb/100/SQL_Guide/5_ManagingTransactions.06.3.html#1126545 : PESSIMISTIC vs. OPTIMISTIC concurrency control

 

Concurrency control and locking

Concurrency control and locking The purpose of concurrency control is to prevent two different users (or two different connections by the same user) from trying to update the same data at the same time. Concurrency control can also prevent one user from se

support.unicomsi.com

Pessimistic concurrency control (or pessimistic locking) is called pessimistic because the system assumes the worst — it assumes that two or more users will want to update the same record at the same time, and then prevents that possibility by locking the record, no matter how unlikely conflicts actually are. The locks are placed as soon as any piece of the row is accessed, making it impossible for two or more users to update the row at the same time. Depending on the lock mode (shared, exclusive, or update), other users might be able to read the data even though a lock has been placed.

Optimistic concurrency control (or optimistic locking) assumes that although conflicts are possible, they will be very rare. Instead of locking every record every time that it is used, the system merely looks for indications that two users actually did try to update the same record at the same time. If that evidence is found, then one user’s updates are discarded and the user is informed.

The pessimistic concurrency control mechanism is based on locking. A lock is a mechanism for limiting other users’ access to a piece of data. When one user has a lock on a record, the lock prevents other users from changing (and in some cases reading) that record. Optimistic concurrency control mechanism does not place locks but prevents the overwriting of data by using timestamps.

 

Reason why we choose OCC

When conflicts are rare, transactions can complete without the expense of managing locks and without having transactions wait for other transactions' locks to clear, leading to higher throughput than other concurrency control methods Implementation

 

  1. Get a recent revision number
  2. Increment it
  3. Insert a new event
  4. If it’s failed, go to 1

Code Implementation

// Transfer - repositories
func (w *walletToEscrowRepoSQL) Transfer(vendorID int64, escrowID string, amount int32, meta map[string]string) error {
	...
	latestRevision, err := findLatestRevision(o, vendorID, escrowID)
	
	...
	event := createTransferEvent(vendorID, escrowID, latestRevision+1, amount, meta)
	
    ... // transaction
    
	insertEvent(o, event, "insertion to transfer")

	return o.Commit()
}

func insertEvent(o orm.Ormer, event *models.EscrowEvent, purpose string) error {
	if _, err := o.Insert(event); err != nil {
		if isDuplicationError(err) {
			return CreateAbortError(errors.Wrapf(err, "revision conflict should retry: %d-%s-%d", event.VendorID, event.EscrowID, event.Revision))
		}
		return CreateInternalError(errors.Wrapf(err, "%s failed", purpose))
	}
	return nil
}

---------------
--------------- Service
func (e *EscrowService) Transfer(vendorID int64, escrowID string, amount int32, meta map[string]string) error {
	return retry(func() error {
	    ...
		if err := e.WalletToEscrow.Transfer(vendorID, escrowID, amount, meta); err != nil {
			return err
		}
		...
	})
}

func retry(f func() error) error {
	var err error

	for i := 0; i < 5; i++ {
		err = f()
		if err == nil {
			return nil
		}
		if !errors.Is(err, &repositories.ErrAbort{}) {
			return err
		}
	}

	return repositories.CreateInternalError(errors.Wrap(err, "Internal"))
}

BloomRPC - GetEscrow

# escrow_read

// newEscrowReadModel - Gets escrow events, then returns a new Escrow object
func newEscrowReadModel(events []*models.EscrowEvent) *entities.Escrow {
	...
	for _, event := range events {
		escrow.ID = event.EscrowID
		escrow.VendorID = event.VendorID

		cmd := &command{}
		if err := json.Unmarshal([]byte(event.Command), cmd); err != nil {
			return nil
		}
		meta := decodeMeta(event.Meta)
		dispatch(escrow, event, cmd, meta)
	}
	return escrow
}

func onTransfer(escrow *entities.Escrow, event *models.EscrowEvent, cmd *command, meta map[string]string) {
	amount := payDebt(escrow, cmd.Amount)
	ensureAccountBalance(escrow, cmd.Source)
	escrow.AccountBalance[cmd.Source] += amount

	escrow.Balance += cmd.Amount
	appendLog(escrow, event, entities.Transfer, cmd, meta)
}

func appendLog(escrow *entities.Escrow, event *models.EscrowEvent, code entities.OperationCode, cmd *command, meta map[string]string) {
	escrow.AuditLogs = append(escrow.AuditLogs, &entities.EscrowAuditLog{
		Code:      code,
		Source:    cmd.Source,
		Amount:    cmd.Amount,
		Meta:      meta,
		CreatedAt: *event.CreatedAt,
	})
}

'software engineering > backend' 카테고리의 다른 글

MongoDB - 문서의 갱신/쿼리  (0) 2023.06.24
MongoDB - 집계  (0) 2023.06.18
Rest? gRPC? GraphQL?  (0) 2022.08.31
ElasticSearch  (0) 2022.08.29
DynamoDB  (0) 2022.08.29