1

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.

2

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.

  • Thank you very much for the keyword "synchronous_commit" (+1). That was what I was looking for and what my caching layer would do. Async commit would be perfect, assuming the commit to the database will be done immediately and not cached for another hour or so + the short term cache is consistent. The idea is that the bad customer experience you mentioned will happen only if postgres crashes or something similar that is very irregular. This is not supposed to happen, not even once a year. That's why I believe it's OK. It's a trade-off. I'll run my tests today with it and provide an update. – The Quantum Physicist Mar 20 at 15:18
  • @TheQuantumPhysicist I suggest you read the entire article on asynchronous commit, including warnings. Your saying "This is not supposed to happen, not even once a year. That's why I believe it's OK" tells me you don't have much experience with real world applications. – mustaccio Mar 20 at 17:17
  • @mustaccio Actually I read the whole thing before your comment, and saw that warning. But see, a planned shutdown is always OK, because the application will be set to "paused" before shutting down PostgreSQL. I believe if the database has to crash twice a weak, then that's a failure that should be solved. I've been running databases on websites for years (MySQL). They never crash. I don't know how different this is. I don't know what you mean with "real world applications", but I'm definitely not the expert, so you may be right. – The Quantum Physicist Mar 20 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.