Hier, j'étais assis tranquillement, comme d'habitude je ne dérange personne. Ici, à partir de deux contacts différents, ils envoient presque simultanément un lien vers le tweet bien connu sur JSON de SQL. L'un des messages ressemblait à ceci:
C'était déjà un défi direct. Je ne pouvais pas l'ignorer. J'ai donc décidé de raconter une histoire qui évoque encore en moi des sentiments ambivalents. Trois ans plus tard.
Ă cette Ă©poque bĂ©nie, tout le monde rĂȘvait de crypto, s'engageait dans des ICO et sculptait des Ă©changes cryptographiques. C'Ă©tait vraiment quelque chose de nouveau. J'avais de l'expĂ©rience dans la crĂ©ation de systĂšmes de gestion d'actifs classiques (actions, obligations, etc.). Le problĂšme Ă©tait qu'il se formait autour des systĂšmes comptables. Je voulais me rĂ©aliser en crĂ©ant un Ă©change. Sans surprise, j'ai plongĂ© joyeusement dans ce chaudron bouillant.
Tel est le contexte. Il y avait beaucoup de choses intéressantes, mais aujourd'hui, je veux vous parler d'un cas spécifique - comment nous avons créé notre matcher.
Matcher, c'est le cĆur de l'Ă©change. C'est en lui que se dĂ©roulent les transactions. Dans le stĂ©rĂ©otype classique, il s'agit d'un sous-systĂšme haute performance. Mais cela est vrai pour les grands Ă©changes. LĂ , les applications ouvertes pour l'achat et la vente se comptent par centaines de milliers.
â â . . â , . â .
.
Java . , , . , . ââ . .
. , , ââ . . , 270 . RabbitMQ. âŠ.
. . 2 . , , . , , .
. . .. , ⊠, .. . - :
! ()
, . . ⊠. .
, . , . , . â .
, . , 100, 5. 100 7 . , .
SQL . , . MySQL . , MySQL . .. . :
SET depth_sell.`limit` = depth_sell.`limit` - IF(
((@tofill := IF(
depth_sell.`limit` <= @limit,
depth_sell.`limit`,
@limit
))
+ (@limit := @limit - @tofill)),
@tofill,
@tofill
),
depth_sell.`executed` = @tofill
WHERE @limit > 0;
@limit := @limit - @tofill
() .
IN MEMORY . .. . .
. â . . , , .
CREATE TABLE transactions
(
id INT AUTO_INCREMENT PRIMARY KEY,
moment TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
side1 INT NOT NULL,
side2 INT NOT NULL,
price BIGINT NOT NULL,
volume BIGINT NOT NULL
);
CREATE TABLE depth_buy
(
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
order_id BIGINT NOT NULL,
type INT DEFAULT '0' NOT NULL,
market INT DEFAULT '0' NOT NULL,
account INT NOT NULL,
price BIGINT DEFAULT '0' NOT NULL,
`limit` BIGINT DEFAULT '0' NOT NULL,
taker INT,
rev_price BIGINT NOT NULL,
executed BIGINT
) ENGINE = MEMORY;
CREATE TABLE depth_sell
(
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
order_id INT NOT NULL,
type INT DEFAULT '0' NOT NULL,
market INT DEFAULT '0' NOT NULL,
account INT NOT NULL,
price BIGINT DEFAULT '0' NOT NULL,
`limit` BIGINT DEFAULT '0' NOT NULL,
taker INT,
rev_price BIGINT NOT NULL,
executed BIGINT
) ENGINE = MEMORY;
CREATE PROCEDURE `make_order_v2`(IN order_id INT,
IN order_type INT,
IN order_account INT,
IN order_market INT,
IN order_limit BIGINT,
IN order_price BIGINT)
BEGIN
START TRANSACTION;
SET @limit := order_limit;
IF order_type = 21 THEN
UPDATE depth_sell
INNER JOIN (
SELECT id
FROM depth_sell
WHERE market = order_market
AND depth_sell.price <= order_price
ORDER BY depth_sell.price + id ASC
) source ON depth_sell.id = source.id
SET depth_sell.taker = order_id,
depth_sell.`limit` = depth_sell.`limit` - IF(
((@tofill := IF(
depth_sell.`limit` <= @limit,
depth_sell.`limit`,
@limit
))
+ (@limit := @limit - @tofill)),
@tofill,
@tofill
),
depth_sell.`executed` = @tofill
WHERE @limit > 0;
INSERT INTO transactions (moment, side1, side2, price, volume)
SELECT now(), depth_sell.id, order_id, depth_sell.price, depth_sell.executed
FROM depth_sell
WHERE depth_sell.`taker` = order_id;
DELETE
FROM depth_sell
WHERE market = order_market
AND depth_sell.`limit` = 0;
IF @limit > 0 THEN
INSERT INTO depth_buy (order_id, type, market, account, price, rev_price, `limit`)
VALUES (order_id, order_type, order_market, order_account, order_price, -order_price, @limit);
END IF;
ELSE
UPDATE depth_buy
INNER JOIN (
SELECT id
FROM depth_buy
WHERE market = order_market
AND depth_buy.price >= order_price
ORDER BY depth_buy.rev_price - id ASC
) source ON depth_buy.id = source.id
SET depth_buy.taker = order_id,
depth_buy.`limit` = depth_buy.`limit` - IF(
((@tofill := IF(
depth_buy.`limit` <= @limit,
depth_buy.`limit`,
@limit
))
+ (@limit := @limit - @tofill)),
@tofill,
@tofill
),
depth_buy.`executed` = @tofill
WHERE @limit > 0;
INSERT INTO transactions (moment, side1, side2, price, volume)
SELECT now(), depth_buy.id, order_id, depth_buy.price, depth_buy.executed
FROM depth_buy
WHERE depth_buy.`taker` = order_id;
DELETE
FROM depth_buy
WHERE market = order_market
AND depth_buy.`limit` = 0;
IF @limit > 0 THEN
INSERT INTO depth_sell (order_id, type, market, account, price, rev_price, `limit`)
VALUES (order_id, order_type, order_market, order_account, order_price, -order_price, @limit);
END IF;
END IF;
COMMIT;
END;
CREATE PROCEDURE do_load_matcher_v2(IN market INT)
BEGIN
DECLARE count INT DEFAULT 100000;
WHILE count > 0 DO
call make_order_v2(
count,
IF(count % 2 = 0, 21, 22),
1,
market,
FLOOR(1 + (RAND() * 1000)),
FLOOR(1 + (RAND() * 1000))
);
SET count = count - 1;
END WHILE;
END;
, .. . :
- depth_buy â ;
- depth_sell â ;
- transaction â = .
make_order_v2 :
- order_id â .
- order_type â . 21 â , 22 â .
- order_account â ( ).
- order_market â . .
- order_limit â . . , 100. .. 1.15btc 115.
- order_price â . .
, . â . , . .
, .
, . . 100 . 95 . 1.46 . 570 . .
, . . , . . .. .
, ? :
- . .
- , , . .
:
- . , . .
- . . .
Bien sĂ»r, je ne partage pas l'optimisme de l'auteur du message original selon lequel tout peut ĂȘtre fait via un SGBD, mais l'expĂ©rience montre que tout peut ĂȘtre fait via un SGBD. Si tu veux. Par consĂ©quent, vous devez faire attention Ă vos dĂ©sirs. Sinon, vous devrez ressentir une douleur mentale pendant des annĂ©es.
Tout bon!
Tweet original