{"id":503207,"date":"2026-11-14T13:02:31","date_gmt":"2026-11-14T10:02:31","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=503207"},"modified":"2026-11-14T13:02:31","modified_gmt":"2026-11-14T10:02:31","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=503207","title":{"rendered":"Road to Highload #1. \u041d\u0430\u0438\u0432\u043d\u044b\u0439 checkout \u0438 \u0433\u043e\u043d\u043a\u0430 \u0437\u0430 \u043e\u0441\u0442\u0430\u0442\u043a\u043e\u043c"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u042f \u0437\u0430 \u0432\u0441\u044e \u0441\u0432\u043e\u044e \u043a\u0430\u0440\u044c\u0435\u0440\u0443 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u043b \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 highload. 500-1000 RPS \u0432\u0438\u0434\u0435\u043b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043e \u0432\u043b\u0430\u0436\u043d\u044b\u0445 \u0441\u043d\u0430\u0445. \u0418 \u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u0430 \u0438\u0434\u0435\u044f: \u0435\u0441\u043b\u0438 \u0433\u043e\u0440\u0430 \u043d\u0435 \u0438\u0434\u0451\u0442 \u043a \u041c\u0430\u0433\u043e\u043c\u0435\u0434\u0443, \u0442\u043e \u041c\u0430\u0433\u043e\u043c\u0435\u0434 \u0438\u0434\u0451\u0442 \u043a \u0433\u043e\u0440\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e \u0441\u0435\u0440\u0438\u044e \u0441\u0442\u0430\u0442\u0435\u0439 <strong>Road to Highload<\/strong>. \u0412 \u043d\u0435\u0439 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0441\u0432\u043e\u0438 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0438 \u043d\u043e\u0432\u043e\u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0451\u043d\u043d\u044b\u0435 \u043d\u0430\u0432\u044b\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c, \u043a\u0430\u043a \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e\u0434 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.<\/p>\n<p>\u0412 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u0441\u0442\u0430\u0442\u0435\u0439 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u043a\u0438\u0434\u044b\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u043b\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043f\u0440\u0438 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043b\u0438 \u043d\u0430\u0438\u0432\u043d\u043e\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0438, \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0435\u0441\u0442\u044c, \u0437\u0430\u043c\u0435\u0440\u044f\u0442\u044c \u0438\u0445 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041a\u0430\u0436\u0434\u0430\u044f \u0433\u043b\u0430\u0432\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u043c \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438, \u0442\u0430\u043a \u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u0443 \u0441\u0435\u0431\u044f.<\/p>\n<p>P.S. \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0441\u0432\u043e\u0435\u043c\u0443 \u043b\u0438\u0434\u0443 \u0421\u0435\u0440\u0433\u0435\u044e \u0437\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0443\u0440\u0443)<\/p>\n<h3>\u041f\u043e\u043b\u0438\u0433\u043e\u043d<\/h3>\n<ul>\n<li>\n<p><strong>\u0421\u0442\u0435\u043a:<\/strong> ASP.NET Core 10, Aspire, PostgreSQL.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0430\u0433\u0440\u0443\u0437\u043a\u0430:<\/strong> NBomber \u0438 bash-\u0432\u043e\u0440\u043a\u0435\u0440\u044b \u043d\u0430 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>\u0422\u0435\u043b\u0435\u043c\u0435\u0442\u0440\u0438\u044f:<\/strong> HTTP\/endpoint RPS \u0438 latency, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 (CPU, RSS), \u043c\u0435\u0442\u0440\u0438\u043a\u0438 PostgreSQL (<code>pg_stat<\/code> \/ exporter) \u0432 Aspire Dashboard \u0438 Grafana.<\/p>\n<\/li>\n<\/ul>\n<h3>\u041c\u0430\u0433\u0430\u0437\u0438\u043d \u043d\u0430 \u043a\u043e\u043b\u0435\u043d\u043a\u0435<\/h3>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u043c\u0430\u0433\u0430\u0437\u0438\u043d. \u041e\u0434\u0438\u043d \u0445\u043e\u0434\u043e\u0432\u043e\u0439 \u0442\u043e\u0432\u0430\u0440, \u043a\u0443\u0447\u0430 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0436\u043c\u0443\u0442 \u00ab\u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c\u00bb \u043f\u043e\u0447\u0442\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e. \u041f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434, \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u043e\u0432 \u0438 \u0440\u0443\u0447\u043d\u044b\u0445 \u043f\u0440\u0430\u0432\u043e\u043a \u043e\u0441\u0442\u0430\u0442\u043a\u0430 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442 &#8212; \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u043f\u043e\u043a\u0443\u043f\u043a\u0435.<\/p>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u043e\u0434\u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f: <strong>\u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c \u0437\u0430\u043a\u0430\u0437 \u0438 \u0441\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u043e\u0432\u0430\u0440<\/strong>. \u041f\u043e\u043a\u0430 \u043c\u0438\u0440 \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0434\u0432\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430:<\/p>\n<ol>\n<li>\n<p>\u041e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0435 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 \u043c\u0438\u043d\u0443\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0423\u0447\u0451\u0442 \u0441\u0445\u043e\u0434\u0438\u0442\u0441\u044f: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u0438\u0441\u0430\u043b\u0438 + \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c = \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u043b\u043e \u0432 \u043d\u0430\u0447\u0430\u043b\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u0445 \u043d\u0438\u0436\u0435 \u0443 \u0432\u0441\u0435\u0445 \u0437\u0430\u043a\u0430\u0437\u043e\u0432 <code>Quantity = 1<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443\u0447\u0451\u0442 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442\u0441\u044f \u0434\u043e:<\/p>\n<pre><code>stock + completed == initial_stock<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper\"><\/span><\/a><\/div><\/pre>\n<h3>\u0414\u0432\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c, \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u0432\u0443\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439: \u0442\u043e\u0432\u0430\u0440 \u0438 \u0437\u0430\u043a\u0430\u0437.<\/p>\n<h4>Product<\/h4>\n<pre><code>public class Product{    public Guid Id { get; set; }    public required string Name { get; set; }    public int Stock { get; set; }    public decimal Price { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041f\u043e\u043b\u0435<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0417\u0430\u0447\u0435\u043c<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Id<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0442\u043e\u0432\u0430\u0440\u0430<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Name<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041a\u0430\u043a \u0437\u043e\u0432\u0451\u043c \u043d\u0430 \u0432\u0438\u0442\u0440\u0438\u043d\u0435<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Stock<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a \u0435\u0449\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0430\u0442\u044c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Price<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0426\u0435\u043d\u0430 \u0437\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0443; \u043d\u0430 checkout \u0443\u0439\u0434\u0451\u0442 \u0432 \u0441\u0443\u043c\u043c\u0443 \u043f\u043b\u0430\u0442\u0435\u0436\u0430<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h4>Order<\/h4>\n<pre><code>public class Order{    public Guid Id { get; set; }    public required string UserId { get; set; }    public Guid ProductId { get; set; }    public int Quantity { get; set; }    public OrderStatus Status { get; set; } \/\/ Created | Completed | Rejected    public DateTimeOffset CreatedAt { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041f\u043e\u043b\u0435<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0417\u0430\u0447\u0435\u043c<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Id<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u043a\u0430\u0437\u0430<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>UserId<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0427\u0435\u0439 \u0437\u0430\u043a\u0430\u0437 (\u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b &#8212; \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0437 auth)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>ProductId<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041a\u0430\u043a\u043e\u0439 \u0442\u043e\u0432\u0430\u0440 \u043f\u043e\u043a\u0443\u043f\u0430\u0435\u043c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Quantity<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a \u043f\u0440\u043e\u0441\u0438\u043c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Status<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0416\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b: \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a \u2192 \u0443\u0441\u043f\u0435\u0445 \u0438\u043b\u0438 \u043e\u0442\u043a\u0430\u0437<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>CreatedAt<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041a\u043e\u0433\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u043b\u0438<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Happy-path \u043f\u043e\u043a\u0430 \u0442\u0430\u043a\u043e\u0439: \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0437\u0430\u043a\u0430\u0437 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 <code>Created<\/code> \u2192 \u043d\u0430 checkout \u043b\u0438\u0431\u043e \u0441\u043f\u0438\u0441\u0430\u043b\u0438 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043b\u0438 <code>Completed<\/code>, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438 \u0441 <code>Rejected<\/code>.<\/p>\n<h3>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0437\u0430\u043a\u0430\u0437 &#8212; \u043d\u0435 \u043e\u0441\u043e\u0431\u043e \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u044f\u0441\u044c<\/h3>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u0435\u0442\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f. \u041d\u0435 \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0441\u043a\u043b\u0430\u0434, \u043d\u0435 \u043b\u0435\u0437\u0435\u043c \u0432 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 &#8212; \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438\u0435 \u043a\u0443\u043f\u0438\u0442\u044c. \u042f\u0432\u043d\u043e\u0433\u043e <code>BEGIN<\/code>\u2026<code>COMMIT<\/code> \u0442\u043e\u0436\u0435 \u043d\u0435\u0442: \u043e\u0434\u0438\u043d <code>INSERT<\/code>, <code>SaveChanges<\/code> \u0441\u0430\u043c \u043e\u0442\u043a\u0440\u043e\u0435\u0442 \u043a\u043e\u0440\u043e\u0442\u043a\u0443\u044e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c.<\/p>\n<pre><code>public async Task&lt;(Order? Order, CreateOrderError Error)&gt; CreateAsync(    string userId, Guid productId, int quantity, CancellationToken ct){    if (quantity &lt;= 0)        return (null, CreateOrderError.InvalidQuantity);    var product = await db.Products.FindAsync([productId], ct);    if (product is null)        return (null, CreateOrderError.ProductNotFound);    var order = new Order    {        Id = Guid.NewGuid(),        UserId = userId,        ProductId = product.Id,        Quantity = quantity,        Status = OrderStatus.Created,        CreatedAt = DateTimeOffset.UtcNow    };    db.Orders.Add(order);    await db.SaveChangesAsync(ct);    return (order, CreateOrderError.None);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434 \u043f\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c:<\/p>\n<ol>\n<li>\n<p><code>quantity &lt;= 0<\/code> &#8212; \u043c\u0443\u0441\u043e\u0440 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435, \u0434\u0430\u0436\u0435 \u0434\u043e \u0411\u0414 \u043d\u0435 \u0445\u043e\u0434\u0438\u043c.<\/p>\n<\/li>\n<li>\n<p><code>FindAsync<\/code> &#8212; \u0442\u043e\u0432\u0430\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c <code>Order<\/code> \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c <code>Created<\/code> &#8212; \u044d\u0442\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u0440\u043e\u0434\u0430\u0436\u0430, \u0430 \u00ab\u043a\u043e\u0440\u0437\u0438\u043d\u0430 \u0441\u0442\u0430\u043b\u0430 \u0437\u0430\u043a\u0430\u0437\u043e\u043c\u00bb.<\/p>\n<\/li>\n<li>\n<p><code>SaveChanges<\/code> &#8212; \u043e\u0434\u0438\u043d insert.<\/p>\n<\/li>\n<\/ol>\n<h3>\u041d\u0430\u0438\u0432\u043d\u044b\u0439 checkout \u0431\u0435\u0437 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u00ab\u043e\u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0438 \u0441\u043f\u0438\u0441\u0430\u0442\u044c\u00bb. \u041f\u0438\u0448\u0435\u043c \u0432 \u043b\u043e\u0431, \u043a\u0430\u043a \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0443\u0447\u0435\u0431\u043d\u0438\u043a\u0430 \u043f\u043e EF: \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b\u0438 \u2192 \u0432\u044b\u0447\u043b\u0438 \u2192 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438. \u0411\u0435\u0437 <code>BeginTransaction<\/code>.<\/p>\n<pre><code>public async Task&lt;(bool Ok, string? Error, Order? Order)&gt; CheckoutAsync(    Guid orderId, string userId, CancellationToken ct){    var order = await db.Orders        .FirstOrDefaultAsync(o =&gt; o.Id == orderId &amp;&amp; o.UserId == userId, ct);    if (order is null)        return (false, \"Order not found\", null);    if (order.Status != OrderStatus.Created)        return (false, $\"Order is already {order.Status}\", order);    var product = await db.Products        .FirstOrDefaultAsync(p =&gt; p.Id == order.ProductId, ct);    if (product is null)        return (false, \"Product not found\", order);    if (product.Stock &lt; order.Quantity)    {        order.Status = OrderStatus.Rejected;        await db.SaveChangesAsync(ct);        return (false, \"Insufficient stock\", order);    }    db.Payments.Add(new Payment    {        Id = Guid.NewGuid(),        OrderId = order.Id,        Amount = product.Price * order.Quantity,        Status = PaymentStatus.Captured    });    await db.SaveChangesAsync(ct); \/\/ \u043f\u043b\u0430\u0442\u0451\u0436 \u0443\u0436\u0435 \u0432 \u0411\u0414    product.Stock -= order.Quantity;    order.Status = OrderStatus.Completed;    await db.SaveChangesAsync(ct); \/\/ \u0430 \u0442\u0443\u0442 \u043c\u043e\u0436\u0435\u043c \u0443\u043f\u0430\u0441\u0442\u044c    return (true, null, order);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0442\u0443\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u0448\u0430\u0433\u0430\u043c:<\/p>\n<ol>\n<li>\n<p>\u0414\u043e\u0441\u0442\u0430\u0451\u043c \u0437\u0430\u043a\u0430\u0437 \u0438 \u0443\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u0435\u0449\u0451 <code>Created<\/code> &#8212; \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 checkout \u0443\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043a\u0430\u0437\u0430 \u043d\u0435 \u043f\u0443\u0441\u043a\u0430\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0441\u0442\u0430\u0451\u043c \u0442\u043e\u0432\u0430\u0440. \u0412 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043b\u0435\u0436\u0438\u0442 <strong>\u0441\u043d\u0438\u043c\u043e\u043a<\/strong> <code>Stock<\/code> \u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u0447\u0442\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p><code>if (product.Stock &lt; order.Quantity)<\/code> &#8212; \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u00ab\u0445\u0432\u0430\u0442\u0438\u0442 \/ \u043d\u0435 \u0445\u0432\u0430\u0442\u0438\u0442\u00bb \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0441\u043d\u0438\u043c\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043d\u0435 \u0445\u0432\u0430\u0442\u0438\u043b\u043e &#8212; \u0441\u0442\u0430\u0432\u0438\u043c <code>Rejected<\/code> \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c. \u0421\u043a\u043b\u0430\u0434 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0445\u0432\u0430\u0442\u0438\u043b\u043e &#8212; \u0441\u043d\u0430\u0447\u0430\u043b\u0430 <code>SaveChanges<\/code> \u0441 <code>Payment<\/code>, \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c <code>Stock<\/code>, \u0441\u0442\u0430\u0432\u0438\u043c <code>Completed<\/code> \u0438 \u0435\u0449\u0451 \u043e\u0434\u0438\u043d <code>SaveChanges<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u0438\u0447\u043d\u043e. \u0414\u0432\u0430 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 &#8212; \u0443\u0436\u0435 \u0440\u0443\u043b\u0435\u0442\u043a\u0430.<\/p>\n<h4>\u0414\u0432\u0430 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435<\/h4>\n<p>\u041c\u0435\u043b\u043e\u0447\u044c \u043d\u0430 \u0433\u043b\u0430\u0437: <code>Stock = 1<\/code>, \u0434\u0432\u0430 \u0442\u0430\u043f\u0430 \u043f\u043e checkout (<code>PARALLEL=2<\/code>). \u0416\u0434\u0430\u043b \u0447\u0435\u0441\u0442\u043d\u044b\u0439 <code>completed=1<\/code>, <code>stock=0<\/code>.<\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u043b:<\/p>\n<pre><code>completed=2  stock=0  initial_stock=1assert: OVERSELL detected (Completed &gt; initial_stock)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u0414\u0432\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430 \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435. \u0421\u043a\u043b\u0430\u0434 \u0443\u0436\u0435 \u0441\u043e\u0432\u0440\u0430\u043b &#8212; \u0438 HTTP \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0437\u0435\u043b\u0451\u043d\u044b\u0439.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/13\/24\/a9\/1324a9e20cc5b62db3387152627ecb6b.png\" alt=\"\u0420\u0438\u0441. Naive \/ Endpoint, \u043f\u043e\u0441\u043b\u0435 \u0434\u0432\u0443\u0445 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout: Status 200, \u043e\u0448\u0438\u0431\u043e\u043a 0%\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/13\/24\/a9\/1324a9e20cc5b62db3387152627ecb6b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/13\/24\/a9\/1324a9e20cc5b62db3387152627ecb6b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. Naive \/ Endpoint, \u043f\u043e\u0441\u043b\u0435 \u0434\u0432\u0443\u0445 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout: Status 200, \u043e\u0448\u0438\u0431\u043e\u043a 0%<\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u0435 \u0445\u043e\u0440\u043e\u0448\u043e. \u041d\u043e \u0433\u043e\u043d\u043a\u0443 \u0441\u0440\u0430\u0437\u0443 \u0447\u0438\u043d\u0438\u0442\u044c \u043d\u0435 \u0431\u0440\u043e\u0441\u0430\u0435\u043c\u0441\u044f: \u043d\u0430 \u0443\u0441\u043f\u0435\u0445\u0435 \u0438 \u0442\u0430\u043a \u0434\u0432\u0430 <code>SaveChanges<\/code> \u043f\u043e\u0434\u0440\u044f\u0434. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0443\u0448\u0451\u043b \u0432 \u0411\u0414, \u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u043f\u0430\u043b &#8212; <code>Payment<\/code> \u0435\u0441\u0442\u044c, \u0441\u043a\u043b\u0430\u0434 \u043d\u0435\u0442. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u044d\u0442\u0443 \u0434\u044b\u0440\u0443.<\/p>\n<h3>\u0412\u0430\u0440\u0438\u0430\u043d\u0442 0.568261. \u00ab\u0410 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043e\u0431\u0435\u0440\u043d\u0451\u043c \u0432 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e\u00bb<\/h3>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c \u043f\u043e\u0441\u043b\u0435 \u0443\u0447\u0435\u0431\u043d\u0438\u043a\u0430: \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u0437\u0430\u0431\u044b\u043b\u0438 <code>BEGIN<\/code>\u2026<code>COMMIT<\/code>. \u041e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u043e\u0442 \u0436\u0435 RMW \u0432 \u044f\u0432\u043d\u0443\u044e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e &#8212; \u0438 \u0441\u043f\u0438\u043c \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u0435\u0435 \u043d\u0430\u0441\u0447\u0451\u0442 \u00ab\u043f\u043e\u043b\u043e\u0432\u0438\u043d\u044b\u00bb \u0437\u0430\u043f\u0438\u0441\u0438.<\/p>\n<pre><code>await using var tx = await db.Database.BeginTransactionAsync(ct);\/\/ \u2026 \u0442\u043e\u0442 \u0436\u0435 load \/ check \/ Stock -= \/ Payment \/ Completed \u2026await db.SaveChangesAsync(ct);await tx.CommitAsync(ct);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f: \u043b\u0438\u0431\u043e \u0437\u0430\u043a\u0430\u0437, \u043f\u043b\u0430\u0442\u0451\u0436 \u0438 \u043d\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>Stock<\/code> \u0443\u0439\u0434\u0443\u0442 \u0432 \u0411\u0414 \u0432\u043c\u0435\u0441\u0442\u0435, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u0441\u044f \u0432\u0435\u0441\u044c \u043d\u0430\u0448 \u0437\u0430\u043f\u0440\u043e\u0441. \u041e\u0434\u0438\u043d HTTP-checkout \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u00ab\u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443\u00bb \u0441\u0432\u043e\u0438\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439.<\/p>\n<p>\u0427\u0435\u0433\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f <strong>\u043d\u0435<\/strong> \u0434\u0435\u043b\u0430\u0435\u0442: \u043e\u043d\u0430 \u043d\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u0432\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u00ab\u043f\u043e\u0434\u043e\u0436\u0434\u0438, \u044f \u0435\u0449\u0451 \u0447\u0438\u0442\u0430\u044e Stock\u00bb. \u041f\u043e\u0434 \u043e\u0431\u044b\u0447\u043d\u044b\u043c <code>READ COMMITTED<\/code> \u043e\u0431\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0447\u0438\u0442\u0430\u044e\u0442 \u043e\u0434\u043d\u0443 \u0438 \u0442\u0443 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u0443 <code>Products<\/code>, \u043e\u0431\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 <code>if<\/code>, \u043e\u0431\u0430 \u0433\u043e\u0442\u043e\u0432\u044f\u0442 \u0441\u0432\u043e\u0439 <code>UPDATE<\/code>. \u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043d\u0435\u0442 &#8212; \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c <strong>\u0432\u043d\u0443\u0442\u0440\u0438<\/strong> \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p>\n<p>\u0414\u0432\u0435 \u043f\u043e\u043b\u043e\u043c\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043b\u0435\u0433\u043a\u043e \u0441\u043f\u0443\u0442\u0430\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><strong>Lost update<\/strong> &#8212; \u0434\u0432\u0430 \u043f\u043e\u0442\u043e\u043a\u0430 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u043e\u0434\u0438\u043d <code>Stock<\/code>, \u043e\u0431\u0430 \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0441\u0432\u043e\u0451; \u0447\u0430\u0441\u0442\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439 \u00ab\u0441\u0442\u0451\u0440\u043b\u0430\u0441\u044c\u00bb.<\/p>\n<\/li>\n<li>\n<p><strong>Oversell<\/strong> &#8212; \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u043a\u0430\u0437\u043e\u0432, \u0447\u0435\u043c \u0431\u044b\u043b\u043e \u0442\u043e\u0432\u0430\u0440\u0430 (<code>completed &gt; initial_stock<\/code>).<\/p>\n<\/li>\n<\/ul>\n<h4>\u0421\u043f\u043e\u043a\u043e\u0439\u043d\u044b\u0439 \u0434\u0435\u043d\u044c<\/h4>\n<p>\u041f\u043e\u043a\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0440\u0435\u0434\u043a\u0430\u044f \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0447\u0442\u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u044e\u0442\u0441\u044f, \u043d\u0430\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439 \u043d\u0435 \u0441\u043f\u043e\u0442\u044b\u043a\u0430\u0435\u0442\u0441\u044f. \u041c\u043e\u0436\u043d\u043e \u0436\u0438\u0442\u044c \u0438 \u0434\u0443\u043c\u0430\u0442\u044c, \u0447\u0442\u043e <code>BEGIN<\/code>\u2026<code>COMMIT<\/code> \u0432\u0441\u0451 \u0440\u0435\u0448\u0438\u043b. \u041f\u043e\u0440\u0430 \u043b\u043e\u043c\u0430\u0442\u044c.<\/p>\n<h4>100 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout<\/h4>\n<p>\u042f \u0432\u044b\u0441\u0442\u0430\u0432\u0438\u043b \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 100 \u0435\u0434\u0438\u043d\u0438\u0446 \u0438 \u043f\u043e\u0434\u043d\u044f\u043b <code>PARALLEL=100<\/code>: \u0441\u0442\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432 \u0433\u043e\u043d\u044f\u0442 \u0446\u0435\u043f\u043e\u0447\u043a\u0443 create order \u2192 checkout. \u0416\u0434\u0430\u043b \u0447\u0435\u0441\u0442\u043d\u044b\u0439 \u043d\u043e\u043b\u044c \u0438 \u0440\u043e\u0432\u043d\u043e 100 <code>Completed<\/code>.<\/p>\n<p>\u041d\u0435 \u0442\u0443\u0442-\u0442\u043e \u0431\u044b\u043b\u043e:<\/p>\n<pre><code>completed=100  stock=98  initial_stock=100assert \u00ab\u043d\u0435\u0442 oversell\u00bb: \u0437\u0435\u043b\u0451\u043d\u044b\u0439 (Completed &lt;= initial_stock)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>Assert \u0434\u043e\u0432\u043e\u043b\u0435\u043d: <code>Completed<\/code> \u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0441\u0442\u0430. Oversell \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043d\u0435\u0442. \u0410 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 <code>98<\/code>: \u0438\u0437 100 \u0435\u0434\u0438\u043d\u0438\u0446 \u0432 \u043e\u0441\u0442\u0430\u0442\u043a\u0435 \u043e\u0442\u0440\u0430\u0437\u0438\u043b\u043e\u0441\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 2, \u0445\u043e\u0442\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u043e\u0432 100. \u0420\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0443\u0447\u0451\u0442\u0430 &#8212; 98 (<code>stock + completed = 198 \u2260 100<\/code>). \u042d\u0442\u043e \u043a\u0430\u043a \u0440\u0430\u0437 <strong>lost update<\/strong> \u043d\u0430 \u0442\u043e\u043c \u0441\u0430\u043c\u043e\u043c <code>product.Stock -= order.Quantity<\/code> \u0438\u0437 \u043d\u0430\u0438\u0432\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 <code>Completed &lt;= initial_stock<\/code>.<\/p>\n<p>\u0412 Grafana \u043d\u0430 Endpoint \u0434\u043b\u044f <code>POST \u2026\/checkout<\/code> \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b: \u043f\u0430\u0447\u043a\u0430 200-\u0445, \u043e\u0448\u0438\u0431\u043e\u043a 0%. HTTP \u0438 assert \u0437\u0435\u043b\u0451\u043d\u044b\u0435, \u0430 \u0441\u043a\u043b\u0430\u0434 \u0443\u0436\u0435 \u0432\u0440\u0451\u0442.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/61\/4d\/7b\/614d7bae84598bff18f5879f9faf78ed.png\" alt=\"\u0420\u0438\u0441. Naive \/ Endpoint, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 100 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/61\/4d\/7b\/614d7bae84598bff18f5879f9faf78ed.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/61\/4d\/7b\/614d7bae84598bff18f5879f9faf78ed.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. Naive \/ Endpoint, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 100 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout<\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u0430 Resources \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0438\u043a.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/10\/c4\/86\/10c486631d9f822d6e42799168c1baf3.png\" alt=\"\u0420\u0438\u0441. Naive \/ Resources, \u0442\u043e\u0442 \u0436\u0435 \u043f\u0440\u043e\u0433\u043e\u043d\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/10\/c4\/86\/10c486631d9f822d6e42799168c1baf3.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/10\/c4\/86\/10c486631d9f822d6e42799168c1baf3.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. Naive \/ Resources, \u0442\u043e\u0442 \u0436\u0435 \u043f\u0440\u043e\u0433\u043e\u043d<\/figcaption><\/div>\n<\/figure>\n<h4>300 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u043d\u0430 100 \u0435\u0434\u0438\u043d\u0438\u0446<\/h4>\n<p>\u0416\u0451\u0441\u0442\u0447\u0435 (<code>INITIAL_STOCK=100<\/code>, <code>PARALLEL=300<\/code>): \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0442\u043e\u0432\u0430\u0440\u0430. Assert \u0443\u0436\u0435 \u043e\u0440\u0451\u0442:<\/p>\n<pre><code>assert: completed=300 stock=99 initial_stock=100assert: OVERSELL detected (Completed &gt; initial_stock)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u041d\u0430\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u0434 \u0443\u0436\u0435 \u043f\u0440\u043e\u0434\u0430\u0451\u0442 \u00ab\u0442\u0440\u0438\u0441\u0442\u0430 \u0438\u0437 \u0441\u0442\u0430\u00bb. \u0417\u0434\u0435\u0441\u044c \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0435 \u0431\u0435\u0434\u044b: <strong>oversell<\/strong> \u0438 \u0440\u0430\u0437\u0432\u0430\u043b\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0443\u0447\u0451\u0442. \u041e\u0441\u0442\u0430\u0442\u043e\u043a \u0441\u0442\u0430\u043b 99, \u0442\u043e \u0435\u0441\u0442\u044c \u0432 <code>Stock<\/code> \u043e\u0442\u0440\u0430\u0437\u0438\u043b\u043e\u0441\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0430 1, \u043f\u0440\u0438 300 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430\u0445. \u0420\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 &#8212; 299 (<code>300 + 99 \u2260 100<\/code>).<\/p>\n<p>\u0412\u0435\u0440\u043d\u0451\u043c\u0441\u044f \u043a \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u043c\u0435\u0442\u043e\u0434\u0430. \u041e\u0431\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u0435\u043b\u0430\u044e\u0442 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435:<\/p>\n<ol>\n<li>\n<p>\u0447\u0438\u0442\u0430\u044e\u0442 <code>Product<\/code> \u2192 \u0432\u0438\u0434\u044f\u0442, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>Stock = 1<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 <code>if (product.Stock &lt; order.Quantity)<\/code> &#8212; \u043e\u0431\u043e\u0438\u043c \u00ab\u0445\u0432\u0430\u0442\u0438\u0442\u00bb;<\/p>\n<\/li>\n<li>\n<p>\u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 <code>Stock = 0<\/code> \u0438 \u0433\u043e\u0442\u043e\u0432\u0438\u0442 <code>Completed<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u0430 \u043f\u0438\u0448\u0443\u0442 \u0432 \u0411\u0414. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 <code>UPDATE<\/code> \u043f\u043e <code>Id<\/code> \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u043b\u0430\u0434\u0451\u0442 \u0441\u0432\u043e\u0451 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u0435\u0440\u0445 \u0447\u0443\u0436\u043e\u0433\u043e &#8212; \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435 \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0441\u0447\u0435\u0437\u0430\u0435\u0442 (<strong>lost update<\/strong>). \u0410 \u0440\u0430\u0437 \u043e\u0431\u0430 \u0443\u0441\u043f\u0435\u043b\u0438 \u0441\u0442\u0430\u0442\u044c <code>Completed<\/code> \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 &#8212; \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0435\u0449\u0451 \u0438 <strong>oversell<\/strong>.<\/p>\n<\/li>\n<\/ol>\n<p>\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e:<\/p>\n<pre><code>\u0412\u0440\u0435\u043c\u044f   Request A              Request BT1      Stock=1 \u2192 OKT2                             Stock=1 \u2192 OKT3      Stock=0, CompletedT4                             \u0442\u043e\u0436\u0435 Completed<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u043e\u0434\u0438\u043d HTTP-\u0437\u0430\u043f\u0440\u043e\u0441 \u2260 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e read-modify-write. \u041f\u043e\u0440\u0430 \u0447\u0438\u043d\u0438\u0442\u044c.<\/p>\n<h3>\u0412\u0430\u0440\u0438\u0430\u043d\u0442 1. \u041b\u043e\u043a \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 (AppLock)<\/h3>\n<p>\u041e\u043a, \u043d\u0430\u0438\u0432\u043d\u044b\u0439 RMW \u043c\u044b \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u0432 \u0434\u0435\u043b\u0435. \u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c: \u0440\u0430\u0437 \u0433\u043e\u043d\u043a\u0430 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435, \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c checkout \u043f\u043e <code>productId<\/code> \u0447\u0435\u0440\u0435\u0437 <code>SemaphoreSlim<\/code>.<\/p>\n<pre><code>var gate = ProductLocks.GetOrAdd(productId, _ =&gt; new SemaphoreSlim(1, 1));await gate.WaitAsync(ct);try { return await naive.CheckoutAsync(orderId, userId, ct); }finally { gate.Release(); }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u041b\u043e\u043a \u0436\u0438\u0432\u0451\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 <strong>\u043e\u0434\u043d\u043e\u0433\u043e<\/strong> \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0443 \u0442\u0435\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0445\u043e\u0434\u044f\u0442 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0442 \u0436\u0435 <code>ConcurrentDictionary<\/code>. \u041d\u0430 \u043e\u0434\u043d\u043e\u043c <code>shop-api<\/code> oversell \u043d\u0430 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0435 \u043f\u0440\u043e\u043f\u0430\u043b: <code>completed=100<\/code>, <code>stock=0<\/code>.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/29\/a9\/2c\/29a92caadb83c4754430002c4f595e5b.png\" alt=\"\u0420\u0438\u0441. AppLock \/ Resources: \u043f\u0438\u043a \u043f\u043e\u0434 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0447\u0435\u0440\u0435\u0437* \" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/29\/a9\/2c\/29a92caadb83c4754430002c4f595e5b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/29\/a9\/2c\/29a92caadb83c4754430002c4f595e5b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. AppLock \/ Resources: \u043f\u0438\u043a \u043f\u043e\u0434 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0447\u0435\u0440\u0435\u0437* <\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0434\u0432\u0430 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 \u0437\u0430 reverse proxy. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0432\u043e\u0439 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0441\u0435\u043c\u0430\u0444\u043e\u0440\u043e\u0432. Request A \u043d\u0430 \u0440\u0435\u043f\u043b\u0438\u043a\u0435 1 \u0438 Request B \u043d\u0430 \u0440\u0435\u043f\u043b\u0438\u043a\u0435 2 \u0441\u043d\u043e\u0432\u0430 \u043c\u043e\u0433\u0443\u0442 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u043e\u0442 \u0436\u0435 RMW. \u0414\u043b\u044f \u0444\u0430\u0437\u044b \u00ab\u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u00bb AppLock \u043e\u043a \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c. \u0414\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0440\u0435\u043f\u043b\u0438\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u0431\u043e\u0433\u0443 \u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u043f\u043e\u043a\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u044f \u043d\u0435 \u0443\u0439\u0434\u0451\u0442 \u0432 \u0411\u0414 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u043e\u0431\u0449\u0438\u0439 \u043b\u043e\u043a.<\/p>\n<h3>\u0412\u0430\u0440\u0438\u0430\u043d\u0442 2. \u0410\u0442\u043e\u043c\u0430\u0440\u043d\u044b\u0439 UPDATE \u0432 \u0411\u0414 (DbAtomic)<\/h3>\n<p>\u0420\u0430\u0437 \u0434\u0432\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043e\u0434\u0438\u043d \u0441\u0435\u043c\u0430\u0444\u043e\u0440 \u043d\u0435 \u0434\u0435\u043b\u044f\u0442, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0448\u0430\u0433: \u043f\u0443\u0441\u0442\u044c Postgres \u0441\u0430\u043c \u0440\u0435\u0448\u0438\u0442 \u00ab\u0445\u0432\u0430\u0442\u0438\u0442 \u043b\u0438\u00bb \u0432 \u043e\u0434\u043d\u043e\u043c SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u0435.<\/p>\n<pre><code>var rows = await db.Database.ExecuteSqlInterpolatedAsync(    $\"\"\"     UPDATE \"Products\"     SET \"Stock\" = \"Stock\" - {qty}     WHERE \"Id\" = {productId} AND \"Stock\" &gt;= {qty}     \"\"\",    ct);if (rows == 0){    order.Status = OrderStatus.Rejected;    await db.SaveChangesAsync(ct);    await tx.CommitAsync(ct);    return (false, \"Insufficient stock\", order);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 <code>Stock &gt;= qty<\/code> \u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 \u0438\u0434\u0443\u0442 \u0432 \u043e\u0434\u043d\u043e\u0439 SQL-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438. \u041f\u043e\u0434 READ COMMITTED \u0432 PostgreSQL \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u044b\u0439 <code>UPDATE<\/code> \u0442\u043e\u0439 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0436\u0434\u0451\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439, \u043f\u043e\u0442\u043e\u043c \u0443\u0441\u043b\u043e\u0432\u0438\u0435 \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0443\u0436\u0435 \u043f\u043e \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e. <code>rows == 0<\/code> &#8212; \u043d\u0435 \u0441\u043f\u0438\u0441\u0430\u043b\u0438 (\u043d\u0435 \u0445\u0432\u0430\u0442\u0438\u043b\u043e \u0438\u043b\u0438 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0435\u0442), <code>rows == 1<\/code> &#8212; \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0448\u043b\u043e.<\/p>\n<p>\u0421\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u043a\u0430\u0437\u0430 \u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c <code>Payment<\/code> \u0432 Postgres \u0436\u0438\u0432\u0443\u0442 \u0432 \u0442\u043e\u0439 \u0436\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0447\u0442\u043e \u0438 UPDATE.<\/p>\n<p>\u041d\u0430 <code>PARALLEL=100<\/code> \u0441\u043d\u043e\u0432\u0430 <code>completed=100<\/code>, <code>stock=0<\/code>.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/ca\/b7\/2d\/cab72d58388119a74122f95e684bded5.png\" alt=\"\u0420\u0438\u0441. DbAtomic \/ Resources\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/ca\/b7\/2d\/cab72d58388119a74122f95e684bded5.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/ca\/b7\/2d\/cab72d58388119a74122f95e684bded5.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. DbAtomic \/ Resources<\/figcaption><\/div>\n<\/figure>\n<h3>\u0412\u0430\u0440\u0438\u0430\u043d\u0442 3. SELECT \u2026 FOR UPDATE (DbForUpdate)<\/h3>\n<p>\u041a\u043b\u0430\u0441\u0441\u0438\u043a\u0430: row lock \u0438 \u0447\u0442\u0435\u043d\u0438\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e <code>Product<\/code> \u043e\u0434\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c, \u043f\u043e\u0442\u043e\u043c \u043e\u0431\u044b\u0447\u043d\u044b\u0439 check\/decrement. \u0412\u0441\u0451 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438.<\/p>\n<pre><code>var product = await db.Products    .FromSqlInterpolated(        $\"\"\"SELECT * FROM \"Products\" WHERE \"Id\" = {productId} FOR UPDATE\"\"\")    .FirstOrDefaultAsync(ct);return await CheckoutSupport.CompleteRmwAsync(db, order, product, tx, ct);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p><code>FOR UPDATE<\/code> \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0434\u043e <code>Commit<\/code>\/<code>Rollback<\/code>, \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0441\u0440\u0430\u0437\u0443 tracked \u0441 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u043c <code>Stock<\/code>. \u041d\u0430 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c <code>completed=100<\/code>, <code>stock=0<\/code>.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/aa\/11\/7a\/aa117a6395f0ce2ca73fb303b108c964.png\" alt=\"\u0420\u0438\u0441. DbForUpdate \/ Resources\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/aa\/11\/7a\/aa117a6395f0ce2ca73fb303b108c964.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/aa\/11\/7a\/aa117a6395f0ce2ca73fb303b108c964.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. DbForUpdate \/ Resources<\/figcaption><\/div>\n<\/figure>\n<h3>\u0412\u0430\u0440\u0438\u0430\u043d\u0442 4. Optimistic concurrency token (Optimistic)<\/h3>\n<p><code>DbAtomic<\/code> \u0443\u0436\u0435 \u0431\u044b\u043b \u00ab\u043e\u043f\u0442\u0438\u043c\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u043c\u00bb \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 SQL: \u043e\u0434\u0438\u043d \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0439 <code>UPDATE<\/code>. \u0417\u0434\u0435\u0441\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u0432\u043a\u0443\u0441 \u0442\u043e\u0439 \u0436\u0435 \u0438\u0434\u0435\u0438 &#8212; \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 EF RMW \u0441 \u0442\u043e\u043a\u0435\u043d\u043e\u043c \u0432\u0435\u0440\u0441\u0438\u0438.<\/p>\n<p>\u041f\u043e\u043c\u043d\u0438\u0442\u0435 <code>Product<\/code> \u0431\u0435\u0437 \u043b\u0438\u0448\u043d\u0438\u0445 \u043f\u043e\u043b\u0435\u0439? \u0412\u043e\u0442 \u0442\u0443\u0442 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <code>UpdatedAt<\/code>. \u0412\u0435\u0448\u0430\u0435\u043c \u043d\u0430 \u043d\u0435\u0433\u043e concurrency token:<\/p>\n<pre><code>public class Product{    ...    public DateTimeOffset UpdatedAt { get; set; }}\/\/ AppDbContext.OnModelCreatinge.Property(x =&gt; x.UpdatedAt).IsConcurrencyToken();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>\u041a\u043e\u043b\u043e\u043d\u043a\u0430 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0435\u0441\u0442\u044c \u0432\u0441\u0435\u0433\u0434\u0430. \u041f\u0435\u0440\u0435\u0434 <code>SaveChanges<\/code> \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440 \u0448\u0442\u0430\u043c\u043f\u0443\u0435\u0442 \u0432\u0440\u0435\u043c\u044f <strong>\u0442\u043e\u043b\u044c\u043a\u043e<\/strong> \u0435\u0441\u043b\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u0430 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f <code>Optimistic<\/code> &#8212; \u0438\u043d\u0430\u0447\u0435 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f, \u0438 Naive \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043c\u043e\u0436\u0435\u0442 \u0441\u043b\u043e\u0432\u0438\u0442\u044c lost update. \u0421\u0430\u043c checkout \u043b\u043e\u0432\u0438\u0442 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442 \u0438 \u0440\u0435\u0442\u0440\u0430\u0438\u0442:<\/p>\n<pre><code>product.Stock -= order.Quantity;order.Status = OrderStatus.Completed;try{    await db.SaveChangesAsync(ct); \/\/ WHERE UpdatedAt = original    await tx.CommitAsync(ct);    return (true, null, order);}catch (DbUpdateConcurrencyException){    await db.Entry(product).ReloadAsync(ct);    order.Status = OrderStatus.Created;    \/\/ \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430: \u0441\u043d\u043e\u0432\u0430 check stock \/ payment \/ SaveChanges}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><span class=\"sc-logo-wrapper icon-only\"><\/span><\/a><\/div><\/pre>\n<p>EF \u043f\u0438\u0448\u0435\u0442 <code>UPDATE \u2026 SET Stock, UpdatedAt WHERE Id AND UpdatedAt = @original<\/code>. \u041d\u043e\u043b\u044c \u0441\u0442\u0440\u043e\u043a \u2192 <code>DbUpdateConcurrencyException<\/code> \u2192 <code>Reload<\/code> \u0438 \u0435\u0449\u0451 \u043a\u0440\u0443\u0433 (\u0434\u043e \u043b\u0438\u043c\u0438\u0442\u0430 \u043f\u043e\u043f\u044b\u0442\u043e\u043a). \u042d\u0442\u043e \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 \u0432\u0441\u044e tx, \u043a\u0430\u043a <code>FOR UPDATE<\/code>, \u0438 \u043d\u0435 \u00ab\u0445\u0432\u0430\u0442\u0438\u0442 \u043b\u0438 \u043e\u0441\u0442\u0430\u0442\u043a\u0430\u00bb \u0432 \u043e\u0434\u043d\u043e\u043c SQL, \u043a\u0430\u043a <code>DbAtomic<\/code>: \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 <code>Stock<\/code> \u0441\u043d\u043e\u0432\u0430 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438, \u0430 \u0411\u0414 \u043b\u0438\u0448\u044c \u043e\u0442\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0441\u0442\u0440\u043e\u043a\u0438.<\/p>\n<p>\u041d\u0430 <code>PARALLEL=100<\/code> \u0441\u043d\u043e\u0432\u0430 <code>completed=100<\/code>, <code>stock=0<\/code>. Endpoint \u0437\u0435\u043b\u0451\u043d\u044b\u0439 (200), \u043d\u043e p50 \u0443\u0436\u0435 \u043e\u043a\u043e\u043b\u043e \u043f\u043e\u043b\u0443\u0442\u043e\u0440\u0430 \u0441\u0435\u043a\u0443\u043d\u0434 &#8212; \u0447\u0430\u0441\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a\u0440\u0443\u0442\u0438\u0442 retry \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0446\u0438\u0435\u0439 \u0437\u0430 \u043e\u0434\u0438\u043d \u0442\u043e\u0432\u0430\u0440.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/73\/d1\/6e\/73d16ed0c4f40996681125b0677b9cfa.png\" alt=\"\u0420\u0438\u0441. Optimistic \/ Endpoint: Status 200, latency p50 \u2248 1.5s\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/73\/d1\/6e\/73d16ed0c4f40996681125b0677b9cfa.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/73\/d1\/6e\/73d16ed0c4f40996681125b0677b9cfa.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. Optimistic \/ Endpoint: Status 200, latency p50 \u2248 1.5s<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/extt\/a0\/84\/0f\/a0840fa9a5bda37f94b889c45a3c20da.png\" alt=\"\u0420\u0438\u0441. Optimistic \/ Resources: \u043f\u0438\u043a working set \u0438 thread pool \u043f\u043e\u0434 \u043f\u0430\u0447\u043a\u043e\u0439 checkout\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/extt\/a0\/84\/0f\/a0840fa9a5bda37f94b889c45a3c20da.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/extt\/a0\/84\/0f\/a0840fa9a5bda37f94b889c45a3c20da.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. Optimistic \/ Resources: \u043f\u0438\u043a working set \u0438 thread pool \u043f\u043e\u0434 \u043f\u0430\u0447\u043a\u043e\u0439 checkout<\/figcaption><\/div>\n<\/figure>\n<h3>\u0421\u0432\u043e\u0434\u043a\u0430 \u0441 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0430<\/h3>\n<p>\u0421\u0440\u0430\u0432\u043d\u0438\u043b \u043f\u044f\u0442\u044c \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0439. \u0423\u0447\u0451\u0442 \u0438 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430 <code>shop-api<\/code> &#8212; \u0441 compare <code>INITIAL_STOCK=100<\/code>, <code>PARALLEL=300<\/code> (Naive\u2026DbForUpdate \u043e\u0434\u043d\u0438\u043c \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u043c; <code>Optimistic<\/code> \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e). \u0420\u0435\u0441\u0443\u0440\u0441\u044b Postgres &#8212; \u0441 \u0442\u0435\u0445 \u0436\u0435 Grafana-\u0441\u043a\u0440\u0438\u043d\u043e\u0432 PostgreSQL \u043f\u043e\u0441\u043b\u0435 <code>PARALLEL=100<\/code>, \u0447\u0442\u043e \u0432\u044b\u0448\u0435 \u043f\u043e \u0442\u0435\u043a\u0441\u0442\u0443 (CPU\/\u043f\u0430\u043c\u044f\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0411\u0414, \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0435\u0441\u0441\u0438\u0438).<\/p>\n<h4>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438\u043d\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">Oversell?<\/p>\n<\/th>\n<th>\n<p align=\"left\">Completed<\/p>\n<\/th>\n<th>\n<p align=\"left\">Stock<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Naive<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0434\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">300<\/p>\n<\/td>\n<td>\n<p align=\"left\">99<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">AppLock<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043d\u0435\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\">100<\/p>\n<\/td>\n<td>\n<p align=\"left\">0<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbAtomic<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043d\u0435\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\">100<\/p>\n<\/td>\n<td>\n<p align=\"left\">0<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbForUpdate<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043d\u0435\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\">100<\/p>\n<\/td>\n<td>\n<p align=\"left\">0<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Optimistic<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043d\u0435\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\">100<\/p>\n<\/td>\n<td>\n<p align=\"left\">0<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0418\u043d\u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u00ab\u0437\u0435\u043b\u0451\u043d\u044b\u0445\u00bb \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0439: <code>completed=100<\/code>, <code>stock=0<\/code> (\u0438 <code>stock + completed == initial_stock<\/code>).<\/p>\n<h4>\u041c\u0435\u0442\u0440\u0438\u043a\u0438 \u0441 \u0411\u0414<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">CPU \u043c\u0430\u043a\u0441., \u0441<\/p>\n<\/th>\n<th>\n<p align=\"left\">Resident Mem \u043c\u0430\u043a\u0441., \u041c\u0438\u0411<\/p>\n<\/th>\n<th>\n<p align=\"left\">Active sessions (\u043f\u0438\u043a)<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Naive<\/p>\n<\/td>\n<td>\n<p align=\"left\">3.25<\/p>\n<\/td>\n<td>\n<p align=\"left\">2.05<\/p>\n<\/td>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">AppLock<\/p>\n<\/td>\n<td>\n<p align=\"left\">3.56<\/p>\n<\/td>\n<td>\n<p align=\"left\">2.15<\/p>\n<\/td>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbAtomic<\/p>\n<\/td>\n<td>\n<p align=\"left\">3.56<\/p>\n<\/td>\n<td>\n<p align=\"left\">1.63<\/p>\n<\/td>\n<td>\n<p align=\"left\">9<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbForUpdate<\/p>\n<\/td>\n<td>\n<p align=\"left\">3.56<\/p>\n<\/td>\n<td>\n<p align=\"left\">1.93<\/p>\n<\/td>\n<td>\n<p align=\"left\">9<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Optimistic<\/p>\n<\/td>\n<td>\n<p align=\"left\">3.02<\/p>\n<\/td>\n<td>\n<p align=\"left\">1.58<\/p>\n<\/td>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>CPU \u0437\u0434\u0435\u0441\u044c &#8212; \u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u043d\u043e\u0435 CPU time \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 Postgres \u043d\u0430 \u0433\u0440\u0430\u0444\u0438\u043a\u0435 \u0437\u0430 \u043e\u043a\u043d\u043e.<\/p>\n<p>\u041d\u0430 <code>DbAtomic<\/code> \/ <code>DbForUpdate<\/code> \u0432 \u043f\u0438\u043a\u0435 \u0432\u0438\u0434\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043a <code>appdb<\/code> &#8212; \u043b\u043e\u0433\u0438\u0447\u043d\u043e: \u0440\u0430\u0431\u043e\u0442\u0430 \u0443\u0435\u0437\u0436\u0430\u0435\u0442 \u0432 \u0411\u0414 (\u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0439 <code>UPDATE<\/code> \u0438\u043b\u0438 <code>FOR UPDATE<\/code>).<\/p>\n<h4>\u041c\u0435\u0442\u0440\u0438\u043a\u0438 \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0412\u0440\u0435\u043c\u044f, \u0441<\/p>\n<\/th>\n<th>\n<p align=\"left\">CPU \u0441\u0440.<\/p>\n<\/th>\n<th>\n<p align=\"left\">CPU \u043c\u0430\u043a\u0441.<\/p>\n<\/th>\n<th>\n<p align=\"left\">RSS \u043c\u0430\u043a\u0441., \u041c\u0438\u0411<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Naive<\/p>\n<\/td>\n<td>\n<p align=\"left\">12.1<\/p>\n<\/td>\n<td>\n<p align=\"left\">66.4<\/p>\n<\/td>\n<td>\n<p align=\"left\">148.8<\/p>\n<\/td>\n<td>\n<p align=\"left\">281<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">AppLock<\/p>\n<\/td>\n<td>\n<p align=\"left\">9.7<\/p>\n<\/td>\n<td>\n<p align=\"left\">52.7<\/p>\n<\/td>\n<td>\n<p align=\"left\">161.7<\/p>\n<\/td>\n<td>\n<p align=\"left\">330<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbAtomic<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>8.6<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>29.3<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>73.4<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">408<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbForUpdate<\/p>\n<\/td>\n<td>\n<p align=\"left\">10.1<\/p>\n<\/td>\n<td>\n<p align=\"left\">30.2<\/p>\n<\/td>\n<td>\n<p align=\"left\">91.6<\/p>\n<\/td>\n<td>\n<p align=\"left\">481<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Optimistic<\/p>\n<\/td>\n<td>\n<p align=\"left\">5.1<\/p>\n<\/td>\n<td>\n<p align=\"left\">0.0<\/p>\n<\/td>\n<td>\n<p align=\"left\">0.0<\/p>\n<\/td>\n<td>\n<p align=\"left\">72<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h4>\u041c\u0435\u0442\u0440\u0438\u043a\u0438 \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: latency checkout<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">p50<\/p>\n<\/th>\n<th>\n<p align=\"left\">p99<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Naive<\/p>\n<\/td>\n<td>\n<p align=\"left\">50 ms<\/p>\n<\/td>\n<td>\n<p align=\"left\">2.17 s<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">AppLock<\/p>\n<\/td>\n<td>\n<p align=\"left\">328 ms<\/p>\n<\/td>\n<td>\n<p align=\"left\">2.44 s<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbAtomic<\/p>\n<\/td>\n<td>\n<p align=\"left\">178 ms<\/p>\n<\/td>\n<td>\n<p align=\"left\">2.28 s<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">DbForUpdate<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>87 ms<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>925 ms<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Optimistic<\/p>\n<\/td>\n<td>\n<p align=\"left\">1.54 s<\/p>\n<\/td>\n<td>\n<p align=\"left\">4.22 s<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0412\u043e\u0442 \u0433\u0434\u0435 Optimistic \u00ab\u0434\u043e\u0440\u043e\u0436\u0435\u00bb: \u043c\u0435\u0434\u0438\u0430\u043d\u0430 \u0443\u0436\u0435 \u043f\u043e\u043b\u0442\u043e\u0440\u044b \u0441\u0435\u043a\u0443\u043d\u0434\u044b, p99 \u0437\u0430 \u0447\u0435\u0442\u044b\u0440\u0435 &#8212; \u044d\u0442\u043e retry \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0446\u0438\u0435\u0439 \u0437\u0430 \u043e\u0434\u0438\u043d <code>Stock<\/code>.<\/p>\n<h3>\u0412\u043c\u0435\u0441\u0442\u043e \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0427\u0442\u043e \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043b:<\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0438\u0432\u043d\u044b\u0439 RMW \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u0443\u0436\u0435 \u043d\u0430 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0439 \u043f\u0430\u0447\u043a\u0435, \u0431\u0435\u0437 \u00ab\u0442\u044b\u0441\u044f\u0447\u0438 RPS\u00bb.<\/p>\n<\/li>\n<li>\n<p>HTTP 200 \u0438 <code>Completed &lt;= initial_stock<\/code> \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e: \u043d\u0430 <code>PARALLEL=100<\/code> \u0443\u0447\u0451\u0442 \u0440\u0430\u0437\u044a\u0435\u0445\u0430\u043b\u0441\u044f (<code>stock=98<\/code> \u043f\u0440\u0438 100 Completed) \u043f\u0440\u0438 \u0437\u0435\u043b\u0451\u043d\u043e\u043c assert.<\/p>\n<\/li>\n<li>\n<p><code>BEGIN<\/code>\u2026<code>COMMIT<\/code> \u043d\u0430 \u043e\u0434\u0438\u043d \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u044e \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u044d\u0442\u043e\u0442 \u0437\u0430\u0435\u0437\u0434:<\/p>\n<ol>\n<li>\n<p>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u043e\u0434\u0438\u043d HTTP-\u0437\u0430\u043f\u0440\u043e\u0441 \u2260 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e read-modify-write.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e HTTP 200 \u0438 <code>Completed &lt;= initial_stock<\/code>, \u0430 <code>stock + sold == initial_stock<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u041b\u043e\u043a \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0447\u0438\u043d\u0438\u0442 \u043e\u0434\u0438\u043d \u0438\u043d\u0441\u0442\u0430\u043d\u0441, \u043d\u0435 \u043a\u043b\u0430\u0441\u0442\u0435\u0440.<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437 \u0440\u0435\u0448\u0435\u043d\u0438\u0439 &#8212; \u043b\u0438\u0448\u044c \u0431\u043e\u0440\u044c\u0431\u0430 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u043b\u0438\u0431\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043b\u0438\u0431\u043e \u0411\u0414: <code>AppLock<\/code> \u0443\u043f\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441, <code>DbAtomic<\/code> \/ <code>FOR UPDATE<\/code> \u0433\u0440\u0443\u0437\u044f\u0442 \u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0442 Postgres, optimistic token + retry \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u044e\u0442 latency \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043e\u0434 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0430: <a href=\"https:\/\/github.com\/LittleDeveloper-CSharp\/Road-to-highload\" rel=\"noopener nofollow\">github.com\/LittleDeveloper-CSharp\/Road-to-highload<\/a>.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1107728\/\">https:\/\/habr.com\/ru\/articles\/1107728\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u042f \u0437\u0430 \u0432\u0441\u044e \u0441\u0432\u043e\u044e \u043a\u0430\u0440\u044c\u0435\u0440\u0443 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u043b \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 highload. 500-1000 RPS \u0432\u0438\u0434\u0435\u043b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043e \u0432\u043b\u0430\u0436\u043d\u044b\u0445 \u0441\u043d\u0430\u0445. \u0418 \u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u0430 \u0438\u0434\u0435\u044f: \u0435\u0441\u043b\u0438 \u0433\u043e\u0440\u0430 \u043d\u0435 \u0438\u0434\u0451\u0442 \u043a \u041c\u0430\u0433\u043e\u043c\u0435\u0434\u0443, \u0442\u043e \u041c\u0430\u0433\u043e\u043c\u0435\u0434 \u0438\u0434\u0451\u0442 \u043a \u0433\u043e\u0440\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e \u0441\u0435\u0440\u0438\u044e \u0441\u0442\u0430\u0442\u0435\u0439 Road to Highload. \u0412 \u043d\u0435\u0439 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0441\u0432\u043e\u0438 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0438 \u043d\u043e\u0432\u043e\u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0451\u043d\u043d\u044b\u0435 \u043d\u0430\u0432\u044b\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c, \u043a\u0430\u043a \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e\u0434 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.\u0412 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u0441\u0442\u0430\u0442\u0435\u0439 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u043a\u0438\u0434\u044b\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u043b\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043f\u0440\u0438 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043b\u0438 \u043d\u0430\u0438\u0432\u043d\u043e\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0438, \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0435\u0441\u0442\u044c, \u0437\u0430\u043c\u0435\u0440\u044f\u0442\u044c \u0438\u0445 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041a\u0430\u0436\u0434\u0430\u044f \u0433\u043b\u0430\u0432\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u043c \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438, \u0442\u0430\u043a \u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u0443 \u0441\u0435\u0431\u044f.P.S. \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0441\u0432\u043e\u0435\u043c\u0443 \u043b\u0438\u0434\u0443 \u0421\u0435\u0440\u0433\u0435\u044e \u0437\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0443\u0440\u0443)\u041f\u043e\u043b\u0438\u0433\u043e\u043d\u0421\u0442\u0435\u043a: ASP.NET Core 10, Aspire, PostgreSQL.\u041d\u0430\u0433\u0440\u0443\u0437\u043a\u0430: NBomber \u0438 bash-\u0432\u043e\u0440\u043a\u0435\u0440\u044b \u043d\u0430 \u043f\u043e\u043b\u0438\u0433\u043e\u043d\u0435.\u0422\u0435\u043b\u0435\u043c\u0435\u0442\u0440\u0438\u044f: HTTP\/endpoint RPS \u0438 latency, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 (CPU, RSS), \u043c\u0435\u0442\u0440\u0438\u043a\u0438 PostgreSQL (pg_stat \/ exporter) \u0432 Aspire Dashboard \u0438 Grafana.\u041c\u0430\u0433\u0430\u0437\u0438\u043d \u043d\u0430 \u043a\u043e\u043b\u0435\u043d\u043a\u0435\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u043c\u0430\u0433\u0430\u0437\u0438\u043d. \u041e\u0434\u0438\u043d \u0445\u043e\u0434\u043e\u0432\u043e\u0439 \u0442\u043e\u0432\u0430\u0440, \u043a\u0443\u0447\u0430 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0436\u043c\u0443\u0442 \u00ab\u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c\u00bb \u043f\u043e\u0447\u0442\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e. \u041f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434, \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u043e\u0432 \u0438 \u0440\u0443\u0447\u043d\u044b\u0445 \u043f\u0440\u0430\u0432\u043e\u043a \u043e\u0441\u0442\u0430\u0442\u043a\u0430 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442 &#8212; \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u043f\u043e\u043a\u0443\u043f\u043a\u0435.\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u043e\u0434\u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f: \u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c \u0437\u0430\u043a\u0430\u0437 \u0438 \u0441\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u043e\u0432\u0430\u0440. \u041f\u043e\u043a\u0430 \u043c\u0438\u0440 \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0434\u0432\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430:\u041e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0435 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 \u043c\u0438\u043d\u0443\u0441.\u0423\u0447\u0451\u0442 \u0441\u0445\u043e\u0434\u0438\u0442\u0441\u044f: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u0438\u0441\u0430\u043b\u0438 + \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c = \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u043b\u043e \u0432 \u043d\u0430\u0447\u0430\u043b\u0435.\u0412 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u0445 \u043d\u0438\u0436\u0435 \u0443 \u0432\u0441\u0435\u0445 \u0437\u0430\u043a\u0430\u0437\u043e\u0432 Quantity = 1, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443\u0447\u0451\u0442 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442\u0441\u044f \u0434\u043e:stock + completed == initial_stock\u0414\u0432\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\u0427\u0442\u043e\u0431\u044b \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c, \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u0432\u0443\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439: \u0442\u043e\u0432\u0430\u0440 \u0438 \u0437\u0430\u043a\u0430\u0437.Productpublic class Product{    public Guid Id { get; set; }    public required string Name { get; set; }    public int Stock { get; set; }    public decimal Price { get; set; }}\u041f\u043e\u043b\u0435\u0417\u0430\u0447\u0435\u043cId\u041f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0442\u043e\u0432\u0430\u0440\u0430Name\u041a\u0430\u043a \u0437\u043e\u0432\u0451\u043c \u043d\u0430 \u0432\u0438\u0442\u0440\u0438\u043d\u0435Stock\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a \u0435\u0449\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0430\u0442\u044cPrice\u0426\u0435\u043d\u0430 \u0437\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0443; \u043d\u0430 checkout \u0443\u0439\u0434\u0451\u0442 \u0432 \u0441\u0443\u043c\u043c\u0443 \u043f\u043b\u0430\u0442\u0435\u0436\u0430Orderpublic class Order{    public Guid Id { get; set; }    public required string UserId { get; set; }    public Guid ProductId { get; set; }    public int Quantity { get; set; }    public OrderStatus Status { get; set; } \/\/ Created | Completed | Rejected    public DateTimeOffset CreatedAt { get; set; }}\u041f\u043e\u043b\u0435\u0417\u0430\u0447\u0435\u043cId\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u043a\u0430\u0437\u0430UserId\u0427\u0435\u0439 \u0437\u0430\u043a\u0430\u0437 (\u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b &#8212; \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0437 auth)ProductId\u041a\u0430\u043a\u043e\u0439 \u0442\u043e\u0432\u0430\u0440 \u043f\u043e\u043a\u0443\u043f\u0430\u0435\u043cQuantity\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a \u043f\u0440\u043e\u0441\u0438\u043cStatus\u0416\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b: \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a \u2192 \u0443\u0441\u043f\u0435\u0445 \u0438\u043b\u0438 \u043e\u0442\u043a\u0430\u0437CreatedAt\u041a\u043e\u0433\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u043b\u0438Happy-path \u043f\u043e\u043a\u0430 \u0442\u0430\u043a\u043e\u0439: \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0437\u0430\u043a\u0430\u0437 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 Created \u2192 \u043d\u0430 checkout \u043b\u0438\u0431\u043e \u0441\u043f\u0438\u0441\u0430\u043b\u0438 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043b\u0438 Completed, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438 \u0441 Rejected.\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0437\u0430\u043a\u0430\u0437 &#8212; \u043d\u0435 \u043e\u0441\u043e\u0431\u043e \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u044f\u0441\u044c\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u0435\u0442\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f. \u041d\u0435 \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0441\u043a\u043b\u0430\u0434, \u043d\u0435 \u043b\u0435\u0437\u0435\u043c \u0432 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 &#8212; \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438\u0435 \u043a\u0443\u043f\u0438\u0442\u044c. \u042f\u0432\u043d\u043e\u0433\u043e BEGIN\u2026COMMIT \u0442\u043e\u0436\u0435 \u043d\u0435\u0442: \u043e\u0434\u0438\u043d INSERT, SaveChanges \u0441\u0430\u043c \u043e\u0442\u043a\u0440\u043e\u0435\u0442 \u043a\u043e\u0440\u043e\u0442\u043a\u0443\u044e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c.public async Task&lt;(Order? Order, CreateOrderError Error)&gt; CreateAsync(    string userId, Guid productId, int quantity, CancellationToken ct){    if (quantity &lt;= 0)        return (null, CreateOrderError.InvalidQuantity);    var product = await db.Products.FindAsync([productId], ct);    if (product is null)        return (null, CreateOrderError.ProductNotFound);    var order = new Order    {        Id = Guid.NewGuid(),        UserId = userId,        ProductId = product.Id,        Quantity = quantity,        Status = OrderStatus.Created,        CreatedAt = DateTimeOffset.UtcNow    };    db.Orders.Add(order);    await db.SaveChangesAsync(ct);    return (order, CreateOrderError.None);}\u041f\u0440\u043e\u0445\u043e\u0434 \u043f\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c:quantity &lt;= 0 &#8212; \u043c\u0443\u0441\u043e\u0440 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435, \u0434\u0430\u0436\u0435 \u0434\u043e \u0411\u0414 \u043d\u0435 \u0445\u043e\u0434\u0438\u043c.FindAsync &#8212; \u0442\u043e\u0432\u0430\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c Order \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c Created &#8212; \u044d\u0442\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u0440\u043e\u0434\u0430\u0436\u0430, \u0430 \u00ab\u043a\u043e\u0440\u0437\u0438\u043d\u0430 \u0441\u0442\u0430\u043b\u0430 \u0437\u0430\u043a\u0430\u0437\u043e\u043c\u00bb.SaveChanges &#8212; \u043e\u0434\u0438\u043d insert.\u041d\u0430\u0438\u0432\u043d\u044b\u0439 checkout \u0431\u0435\u0437 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438\u0422\u0435\u043f\u0435\u0440\u044c \u00ab\u043e\u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0438 \u0441\u043f\u0438\u0441\u0430\u0442\u044c\u00bb. \u041f\u0438\u0448\u0435\u043c \u0432 \u043b\u043e\u0431, \u043a\u0430\u043a \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0443\u0447\u0435\u0431\u043d\u0438\u043a\u0430 \u043f\u043e EF: \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b\u0438 \u2192 \u0432\u044b\u0447\u043b\u0438 \u2192 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438. \u0411\u0435\u0437 BeginTransaction.public async Task&lt;(bool Ok, string? Error, Order? Order)&gt; CheckoutAsync(    Guid orderId, string userId, CancellationToken ct){    var order = await db.Orders        .FirstOrDefaultAsync(o =&gt; o.Id == orderId &amp;&amp; o.UserId == userId, ct);    if (order is null)        return (false, &#171;Order not found&#187;, null);    if (order.Status != OrderStatus.Created)        return (false, $&#187;Order is already {order.Status}&#187;, order);    var product = await db.Products        .FirstOrDefaultAsync(p =&gt; p.Id == order.ProductId, ct);    if (product is null)        return (false, &#171;Product not found&#187;, order);    if (product.Stock &lt; order.Quantity)    {        order.Status = OrderStatus.Rejected;        await db.SaveChangesAsync(ct);        return (false, &#171;Insufficient stock&#187;, order);    }    db.Payments.Add(new Payment    {        Id = Guid.NewGuid(),        OrderId = order.Id,        Amount = product.Price * order.Quantity,        Status = PaymentStatus.Captured    });    await db.SaveChangesAsync(ct); \/\/ \u043f\u043b\u0430\u0442\u0451\u0436 \u0443\u0436\u0435 \u0432 \u0411\u0414    product.Stock -= order.Quantity;    order.Status = OrderStatus.Completed;    await db.SaveChangesAsync(ct); \/\/ \u0430 \u0442\u0443\u0442 \u043c\u043e\u0436\u0435\u043c \u0443\u043f\u0430\u0441\u0442\u044c    return (true, null, order);}\u0427\u0442\u043e \u0442\u0443\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u0448\u0430\u0433\u0430\u043c:\u0414\u043e\u0441\u0442\u0430\u0451\u043c \u0437\u0430\u043a\u0430\u0437 \u0438 \u0443\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u0435\u0449\u0451 Created &#8212; \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 checkout \u0443\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043a\u0430\u0437\u0430 \u043d\u0435 \u043f\u0443\u0441\u043a\u0430\u0435\u043c.\u0414\u043e\u0441\u0442\u0430\u0451\u043c \u0442\u043e\u0432\u0430\u0440. \u0412 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043b\u0435\u0436\u0438\u0442 \u0441\u043d\u0438\u043c\u043e\u043a Stock \u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u0447\u0442\u0435\u043d\u0438\u044f.if (product.Stock &lt; order.Quantity) &#8212; \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u00ab\u0445\u0432\u0430\u0442\u0438\u0442 \/ \u043d\u0435 \u0445\u0432\u0430\u0442\u0438\u0442\u00bb \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0441\u043d\u0438\u043c\u043a\u0443.\u0415\u0441\u043b\u0438 \u043d\u0435 \u0445\u0432\u0430\u0442\u0438\u043b\u043e &#8212; \u0441\u0442\u0430\u0432\u0438\u043c Rejected \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c. \u0421\u043a\u043b\u0430\u0434 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c.\u0415\u0441\u043b\u0438 \u0445\u0432\u0430\u0442\u0438\u043b\u043e &#8212; \u0441\u043d\u0430\u0447\u0430\u043b\u0430 SaveChanges \u0441 Payment, \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c Stock, \u0441\u0442\u0430\u0432\u0438\u043c Completed \u0438 \u0435\u0449\u0451 \u043e\u0434\u0438\u043d SaveChanges.\u041d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u0438\u0447\u043d\u043e. \u0414\u0432\u0430 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 &#8212; \u0443\u0436\u0435 \u0440\u0443\u043b\u0435\u0442\u043a\u0430.\u0414\u0432\u0430 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u041c\u0435\u043b\u043e\u0447\u044c \u043d\u0430 \u0433\u043b\u0430\u0437: Stock = 1, \u0434\u0432\u0430 \u0442\u0430\u043f\u0430 \u043f\u043e checkout (PARALLEL=2). \u0416\u0434\u0430\u043b \u0447\u0435\u0441\u0442\u043d\u044b\u0439 completed=1, stock=0.\u041f\u043e\u043b\u0443\u0447\u0438\u043b:completed=2  stock=0  initial_stock=1assert: OVERSELL detected (Completed &gt; initial_stock)\u0414\u0432\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430 \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435. \u0421\u043a\u043b\u0430\u0434 \u0443\u0436\u0435 \u0441\u043e\u0432\u0440\u0430\u043b &#8212; \u0438 HTTP \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0437\u0435\u043b\u0451\u043d\u044b\u0439.\u0420\u0438\u0441. Naive \/ Endpoint, \u043f\u043e\u0441\u043b\u0435 \u0434\u0432\u0443\u0445 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout: Status 200, \u043e\u0448\u0438\u0431\u043e\u043a 0%\u041d\u0435 \u0445\u043e\u0440\u043e\u0448\u043e. \u041d\u043e \u0433\u043e\u043d\u043a\u0443 \u0441\u0440\u0430\u0437\u0443 \u0447\u0438\u043d\u0438\u0442\u044c \u043d\u0435 \u0431\u0440\u043e\u0441\u0430\u0435\u043c\u0441\u044f: \u043d\u0430 \u0443\u0441\u043f\u0435\u0445\u0435 \u0438 \u0442\u0430\u043a \u0434\u0432\u0430 SaveChanges \u043f\u043e\u0434\u0440\u044f\u0434. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0443\u0448\u0451\u043b \u0432 \u0411\u0414, \u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u043f\u0430\u043b &#8212; Payment \u0435\u0441\u0442\u044c, \u0441\u043a\u043b\u0430\u0434 \u043d\u0435\u0442. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u044d\u0442\u0443 \u0434\u044b\u0440\u0443.\u0412\u0430\u0440\u0438\u0430\u043d\u0442 0.568261. \u00ab\u0410 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043e\u0431\u0435\u0440\u043d\u0451\u043c \u0432 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e\u00bb\u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c \u043f\u043e\u0441\u043b\u0435 \u0443\u0447\u0435\u0431\u043d\u0438\u043a\u0430: \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u0437\u0430\u0431\u044b\u043b\u0438 BEGIN\u2026COMMIT. \u041e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u043e\u0442 \u0436\u0435 RMW \u0432 \u044f\u0432\u043d\u0443\u044e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e &#8212; \u0438 \u0441\u043f\u0438\u043c \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u0435\u0435 \u043d\u0430\u0441\u0447\u0451\u0442 \u00ab\u043f\u043e\u043b\u043e\u0432\u0438\u043d\u044b\u00bb \u0437\u0430\u043f\u0438\u0441\u0438.await using var tx = await db.Database.BeginTransactionAsync(ct);\/\/ \u2026 \u0442\u043e\u0442 \u0436\u0435 load \/ check \/ Stock -= \/ Payment \/ Completed \u2026await db.SaveChangesAsync(ct);await tx.CommitAsync(ct);\u0427\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f: \u043b\u0438\u0431\u043e \u0437\u0430\u043a\u0430\u0437, \u043f\u043b\u0430\u0442\u0451\u0436 \u0438 \u043d\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 Stock \u0443\u0439\u0434\u0443\u0442 \u0432 \u0411\u0414 \u0432\u043c\u0435\u0441\u0442\u0435, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u0441\u044f \u0432\u0435\u0441\u044c \u043d\u0430\u0448 \u0437\u0430\u043f\u0440\u043e\u0441. \u041e\u0434\u0438\u043d HTTP-checkout \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u00ab\u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443\u00bb \u0441\u0432\u043e\u0438\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439.\u0427\u0435\u0433\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442: \u043e\u043d\u0430 \u043d\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u0432\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u00ab\u043f\u043e\u0434\u043e\u0436\u0434\u0438, \u044f \u0435\u0449\u0451 \u0447\u0438\u0442\u0430\u044e Stock\u00bb. \u041f\u043e\u0434 \u043e\u0431\u044b\u0447\u043d\u044b\u043c READ COMMITTED \u043e\u0431\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0447\u0438\u0442\u0430\u044e\u0442 \u043e\u0434\u043d\u0443 \u0438 \u0442\u0443 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u0443 Products, \u043e\u0431\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 if, \u043e\u0431\u0430 \u0433\u043e\u0442\u043e\u0432\u044f\u0442 \u0441\u0432\u043e\u0439 UPDATE. \u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043d\u0435\u0442 &#8212; \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430.\u0414\u0432\u0435 \u043f\u043e\u043b\u043e\u043c\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043b\u0435\u0433\u043a\u043e \u0441\u043f\u0443\u0442\u0430\u0442\u044c:Lost update &#8212; \u0434\u0432\u0430 \u043f\u043e\u0442\u043e\u043a\u0430 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u043e\u0434\u0438\u043d Stock, \u043e\u0431\u0430 \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0441\u0432\u043e\u0451; \u0447\u0430\u0441\u0442\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439 \u00ab\u0441\u0442\u0451\u0440\u043b\u0430\u0441\u044c\u00bb.Oversell &#8212; \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u043a\u0430\u0437\u043e\u0432, \u0447\u0435\u043c \u0431\u044b\u043b\u043e \u0442\u043e\u0432\u0430\u0440\u0430 (completed &gt; initial_stock).\u0421\u043f\u043e\u043a\u043e\u0439\u043d\u044b\u0439 \u0434\u0435\u043d\u044c\u041f\u043e\u043a\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0440\u0435\u0434\u043a\u0430\u044f \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0447\u0442\u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u044e\u0442\u0441\u044f, \u043d\u0430\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439 \u043d\u0435 \u0441\u043f\u043e\u0442\u044b\u043a\u0430\u0435\u0442\u0441\u044f. \u041c\u043e\u0436\u043d\u043e \u0436\u0438\u0442\u044c \u0438 \u0434\u0443\u043c\u0430\u0442\u044c, \u0447\u0442\u043e BEGIN\u2026COMMIT \u0432\u0441\u0451 \u0440\u0435\u0448\u0438\u043b. \u041f\u043e\u0440\u0430 \u043b\u043e\u043c\u0430\u0442\u044c.100 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout\u042f \u0432\u044b\u0441\u0442\u0430\u0432\u0438\u043b \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 100 \u0435\u0434\u0438\u043d\u0438\u0446 \u0438 \u043f\u043e\u0434\u043d\u044f\u043b PARALLEL=100: \u0441\u0442\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432 \u0433\u043e\u043d\u044f\u0442 \u0446\u0435\u043f\u043e\u0447\u043a\u0443 create order \u2192 checkout. \u0416\u0434\u0430\u043b \u0447\u0435\u0441\u0442\u043d\u044b\u0439 \u043d\u043e\u043b\u044c \u0438 \u0440\u043e\u0432\u043d\u043e 100 Completed.\u041d\u0435 \u0442\u0443\u0442-\u0442\u043e \u0431\u044b\u043b\u043e:completed=100  stock=98  initial_stock=100assert \u00ab\u043d\u0435\u0442 oversell\u00bb: \u0437\u0435\u043b\u0451\u043d\u044b\u0439 (Completed &lt;= initial_stock)Assert \u0434\u043e\u0432\u043e\u043b\u0435\u043d: Completed \u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0441\u0442\u0430. Oversell \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043d\u0435\u0442. \u0410 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 98: \u0438\u0437 100 \u0435\u0434\u0438\u043d\u0438\u0446 \u0432 \u043e\u0441\u0442\u0430\u0442\u043a\u0435 \u043e\u0442\u0440\u0430\u0437\u0438\u043b\u043e\u0441\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 2, \u0445\u043e\u0442\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u043e\u0432 100. \u0420\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0443\u0447\u0451\u0442\u0430 &#8212; 98 (stock + completed = 198 \u2260 100). \u042d\u0442\u043e \u043a\u0430\u043a \u0440\u0430\u0437 lost update \u043d\u0430 \u0442\u043e\u043c \u0441\u0430\u043c\u043e\u043c product.Stock -= order.Quantity \u0438\u0437 \u043d\u0430\u0438\u0432\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 Completed &lt;= initial_stock.\u0412 Grafana \u043d\u0430 Endpoint \u0434\u043b\u044f POST \u2026\/checkout \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b: \u043f\u0430\u0447\u043a\u0430 200-\u0445, \u043e\u0448\u0438\u0431\u043e\u043a 0%. HTTP \u0438 assert \u0437\u0435\u043b\u0451\u043d\u044b\u0435, \u0430 \u0441\u043a\u043b\u0430\u0434 \u0443\u0436\u0435 \u0432\u0440\u0451\u0442.\u0420\u0438\u0441. Naive \/ Endpoint, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 100 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 checkout\u041d\u0430 Resources \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0438\u043a.\u0420\u0438\u0441. Naive \/ Resources, \u0442\u043e\u0442 \u0436\u0435 \u043f\u0440\u043e\u0433\u043e\u043d300 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u043d\u0430 100 \u0435\u0434\u0438\u043d\u0438\u0446\u0416\u0451\u0441\u0442\u0447\u0435 (INITIAL_STOCK=100, PARALLEL=300): \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0442\u043e\u0432\u0430\u0440\u0430. Assert \u0443\u0436\u0435 \u043e\u0440\u0451\u0442:assert: completed=300 stock=99 initial_stock=100assert: OVERSELL detected (Completed &gt; initial_stock)\u041d\u0430\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u0434 \u0443\u0436\u0435 \u043f\u0440\u043e\u0434\u0430\u0451\u0442 \u00ab\u0442\u0440\u0438\u0441\u0442\u0430 \u0438\u0437 \u0441\u0442\u0430\u00bb. \u0417\u0434\u0435\u0441\u044c \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0435 \u0431\u0435\u0434\u044b: oversell \u0438 \u0440\u0430\u0437\u0432\u0430\u043b\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0443\u0447\u0451\u0442. \u041e\u0441\u0442\u0430\u0442\u043e\u043a \u0441\u0442\u0430\u043b 99, \u0442\u043e \u0435\u0441\u0442\u044c \u0432 Stock \u043e\u0442\u0440\u0430\u0437\u0438\u043b\u043e\u0441\u044c \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0430 1, \u043f\u0440\u0438 300 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430\u0445. \u0420\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 &#8212; 299 (300 + 99 \u2260 100).\u0412\u0435\u0440\u043d\u0451\u043c\u0441\u044f \u043a \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u043c\u0435\u0442\u043e\u0434\u0430. \u041e\u0431\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u0435\u043b\u0430\u044e\u0442 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435:\u0447\u0438\u0442\u0430\u044e\u0442 Product \u2192 \u0432\u0438\u0434\u044f\u0442, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Stock = 1;\u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 if (product.Stock &lt; order.Quantity) &#8212; \u043e\u0431\u043e\u0438\u043c \u00ab\u0445\u0432\u0430\u0442\u0438\u0442\u00bb;\u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 Stock = 0 \u0438 \u0433\u043e\u0442\u043e\u0432\u0438\u0442 Completed;\u043e\u0431\u0430 \u043f\u0438\u0448\u0443\u0442 \u0432 \u0411\u0414. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 UPDATE \u043f\u043e Id \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u043b\u0430\u0434\u0451\u0442 \u0441\u0432\u043e\u0451 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u0435\u0440\u0445 \u0447\u0443\u0436\u043e\u0433\u043e &#8212; \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435 \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0441\u0447\u0435\u0437\u0430\u0435\u0442 (lost update). \u0410 \u0440\u0430\u0437 \u043e\u0431\u0430 \u0443\u0441\u043f\u0435\u043b\u0438 \u0441\u0442\u0430\u0442\u044c Completed \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 &#8212; \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0435\u0449\u0451 \u0438 oversell.\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e:\u0412\u0440\u0435\u043c\u044f   Request A              Request BT1      Stock=1 \u2192 OKT2                             Stock=1 \u2192 OKT3      Stock=0, CompletedT4                             \u0442\u043e\u0436\u0435 Completed\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u043e\u0434\u0438\u043d HTTP-\u0437\u0430\u043f\u0440\u043e\u0441 \u2260 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e read-modify-write. \u041f\u043e\u0440\u0430 \u0447\u0438\u043d\u0438\u0442\u044c.\u0412\u0430\u0440\u0438\u0430\u043d\u0442 1. \u041b\u043e\u043a \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 (AppLock)\u041e\u043a, \u043d\u0430\u0438\u0432\u043d\u044b\u0439 RMW \u043c\u044b \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u0432 \u0434\u0435\u043b\u0435. \u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c: \u0440\u0430\u0437 \u0433\u043e\u043d\u043a\u0430 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435, \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c checkout \u043f\u043e productId \u0447\u0435\u0440\u0435\u0437 SemaphoreSlim.var gate = ProductLocks.GetOrAdd(productId, _ =&gt; new SemaphoreSlim(1, 1));await gate.WaitAsync(ct);try { return await naive.CheckoutAsync(orderId, userId, ct); }finally {&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-503207","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/503207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=503207"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/503207\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=503207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=503207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=503207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}