سؤال

Introduction

I'm writing a trading program in C++. The program involves having user balances and orderbooks. The orderbook is basically a set of buy and sell orders. When submitting an order, say a buy order, the matching engine searches whether there's a sell order with that price or lower. If that's the case, a trade happen. A trade subtracts the quantities of the smaller order, and the remaining quantity is added to the order book for the next buyer/seller.

The context

When a user submits an order, the C++ program:

  1. Reads the database to see whether the user has sufficient balance.
  2. Performs the trade in memory (in the program)
  3. Modifies the database where in one transaction, it reads the balance, reads the orderbook, modifies them and persists them + writes log entries in the database (2 entries).

As you see, for a trading program, there's zero tolerance for atomicity and consistency. Otherwise there will be double-spending.

However, I can tolerate caching with only one layer over the database, which will ensure consistency and atomicity (through programming) but will postpone persistence.

I'm using PostgreSQL for my program. Also I use indexes for all my accesses of the database. Changing indexing algorithm from the default only worsened the performance. There's very little normalization there. The only normalization I did is separating the balances table from the users table. Everything else is non-separable.

The problem

It's very slow. I can barely do 100 transactions/second on an Intel 4930k (8 core, 16 threads, overclocked to 4.5 GHz), and I need 1000 transactions per second at least.

I'm not sure where the problem is coming from, but I heard that databases are not so good at modifying data. So, how can this system be optimized without endangering ACID compliance?

My solution, about which I want to ask

I'd like to implement a software layer over the database, which is the only layer allowed to access the database directly. This layer will cache the database in memory and periodically flush all the changes to the database for persistence. But then doing this makes me feel I'm doing what the database is supposed to be doing.

Is this a common thing to be done? Am I planning the right solution?


Edits from comments:

I can afford to lose transactions that are in memory only. The reasoning is that a trade at any point of time should have happened or not happened. It doesn't matter if it happened in a certain moment or one second after that. My question is kind of checking whether caching this way is valid solution and whether it's worth it. It seems like a valid solution. I'm just not sure how much it might help and why databases don't do that (or aren't configurable to do that)?

Apparently I misunderstand the word normalization above. I understand normalization to be "splitting tables to make them more meaningful", so for performance, it's worse to normalize (again, I may have misunderstood it). The only table that is split is the one I mentioned above, so instead of writing to 4 tables I'm writing to 4 tables and reading the users table. That's why I'm not very convinced that splitting that table is the reason. But maybe I have to revisit the design. I'm not an expert there.

I'm not sure I can provide DML because I'm using ODB as ORM. I'll have to see whether I can extract them. I'll provide the DDL statements soon when I get where the system is (in a few hours).

Does using an ORM mean it's hopeless? If I lose 20-30% of performance, I could live with it (for now). Should I expect losing 10 times (from 1000 tps to 100) the performance compared to manually writing queries? Seems excessive.

pgbench -t 10000 results in 2350 transaction/s on a single thread (my application uses a single thread because every order book can take a single thread, which is fine).

Edit 2:

I chose the answer below because it implements the second layer solution I'm looking for. But I still don't have the best number of transactions per second, which means I still have to study the schemas and operations I have.

هل كانت مفيدة؟

المحلول

Your question seems to be logically inconsistent, as tolerating a latency in persistence and "Actually I can afford to lose transactions that are in memory only" by definition violates the "D" in "ACID".

If you really want to be just "ACI" compliant, you can turn off "synchronous_commit".

Note that this means you could report to the traders that their trade executed, but then later find out that oops, it actually didn't. It will still be atomic and consistent, as both sides of the trade and both changes in cash balances will have disappeared together. But this could still be a bad customer experience.

If you decide you can't run with "synchronous_commit" off, you should at least do the test with it turned off. The outcome of the test will indicate how you should proceed for further optimization.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى dba.stackexchange
scroll top