6

I need an ORM that is suitable for stateful application. I'm going to keep entities between requests in low-latency realtime game server with persistent client connections. There is an only 1 server instance connected to database so no data can be changed from "outside" and the server can rely on its cache.

When user remotely logs in to the server its whole profile is loaded to server memory. Several higher-level services are also created for each user to operate profile data and provide functionality. They can also have internal fields (state) to store temporary data. When user wants to change his signature he asks corresponding service to do so. The service tracks how frequently user changes his signature and allows it only once per ten minutes (for example) - such short interval is not tracked in db, this is a temporary state. This change should be stored to db executing only 1 query: UPDATE users SET signature = ... WHERE user_id = .... When user logs off it's unloaded from server memory after minutes/hours of inactivity. Db here is only a storage. This is what I call stateful.

  1. Some entities are considered "static data" and loaded only once at application start. Those can be referenced from other "dynamic" entities. Loading "dynamic" entity should not require reloading referenced "static data" entity.
  2. Update/Insert/Delete should set/insert/delete only changed properties/entities even with "detached" entity.
  3. Write operations should not each time load data from database (perform Select) preliminary to detect changes. (A state can be tracked in dynamically generated inheritor.) I have a state locally, there is no sense to load anything. I want to continue tracking changes even outside of connection scope and "upload" changes when I want.
  4. While performing operations references of persisted objects should not be changed.
  5. DBConnection-per-user is not going to work. The expected online is thousands of users.
  6. Entities from "static data" can be assigned to "dynamic" enitity properties (which represent foreign keys) and Update should handle it correctly.

Now I'm using NHibernate despite it's designed for stateless applications. It supports reattaching to session but that looks like very uncommon usage, requires me to use undocumented behavior and doesn't solve everything.

I'm not sure about Entity Framework - can I use it that way? Or can you suggest another ORM?

If the server will recreate (or especially reload) user objects each time user hits a button it will eat CPU very fast. CPU scales vertically expensively but have small effect. Contrary if you are out of RAM you can just go and buy more - like with horizontal scaling but easier to code. If you think that another approach should be used here I'm ready to discuss it.

Vlad
  • 3,001
  • 1
  • 22
  • 52
  • Not sure why you say Nhibernate was designed for stateless apps. NH's session seems pretty stateful to me. – Simon Mourier Sep 24 '15 at 14:18
  • 3
    Not much of a difference between the two wrt applicable architecture. But this question is too broad and opinion-based, the bounty doesn't fix that. And, what's uncommon and undocumented about session.Merge, SaveOrUpdate, etc.? – Gert Arnold Sep 24 '15 at 20:00
  • @GertArnold those methods use Select, see #3. I have to use Session.Lock (to reattach, perform changes and commit) which have some undocumented behavior (it allows me to ignore some checks when I call it twice - only the first throws). – Vlad Sep 24 '15 at 21:50
  • @SimonMourier session is meant to exist only for a short period of time. the unit of work pattern is *not* meant to be stateful. – Vlad Sep 24 '15 at 21:52
  • 2
    It's the other way around. Session is designed for use in stateful scenarios, not to be used 'in a short period of time'. Of course in stateless environment such as the web, you bound one session per request, but session is suited for desktop scenarios. In other terms, session design does not presume its usage. – Simon Mourier Sep 25 '15 at 06:28
  • 2
    @SimonMourier see http://stackoverflow.com/questions/3754592/how-to-keep-long-running-nhibernate-session-data-consistent top answer "keep in mind that NH wasn't designed to be used with long-living ISessions". also there are difficults with passing objects around e.g. when I have my "static objects" from one session and "dynamic objects" from another. – Vlad Sep 25 '15 at 06:33
  • @SimonMourier also each active ISession represents an opened Db connection. but the game can have thousands active users! updated the question – Vlad Sep 25 '15 at 06:39
  • ORMs have nothing to do with stateful or stateless applications - in fact, you'll have to define what you mean "stateful". A database *is* state. Besides, a Session or DbContext live only as long as you want them to live. The shorter is typically better. I think there is confusion about what state, session and *transaction* mean. – Panagiotis Kanavos Sep 25 '15 at 13:54
  • @PanagiotisKanavos when user remotely logs in to my server its whole profile is loaded to server memory and maintained as objects. there are also higher level services created for each user which operate profile data and can also use their internal fields for temporary data. when user wants to change his nickname he asks corresponding service to do so. internally such change should be also stored to db. this is what I call stateful. db here is only storage, not state. – Vlad Sep 25 '15 at 15:06
  • @Vlad: after reading your requirements, I'd say you need a smart 2nd level caching provider for NH rather than keeping entries between requests. This is because you seem to mainly focus on performance and this is what the 2nd level caching is all about. – Wiktor Zychla Oct 01 '15 at 12:14
  • @WiktorZychla 2nd level cache is for caching *data*, not *objects*. each query creates a new object from cached data. I have a stateful system around each user and this system references data objects so those references should not change (e.g. some of them are used in many-to-one collections so they can't be put in "container" with stable reference). – Vlad Oct 01 '15 at 12:26
  • @WiktorZychla looks like using 2nd level cache I can just `Merge` when I want to save changed entities. it's not bad but still why do I need to spend resources on comparing everything (with collections, subcollections, ...)? I just want to continue tracking changes even outside of connection scope and flush them when I want. – Vlad Oct 01 '15 at 12:36

2 Answers2

1

Yes, you can use EF for this kind of application. Please keep in mind, that on heavy load you will have some db errors time to time. And typically, it's faster to recover after errors, when you application track changes, not EF. By the way, you can use this way NHibernate too.

Sandre
  • 498
  • 6
  • 10
  • Can you clarify in what exact way I can use NHibernate for that? And for EF - are there any examples of such usage? I thought EF have entities strictly attached to their DbContext the same way as NH with ISession. – Vlad Oct 01 '15 at 11:27
  • By default EF attached entities to session. To disable this behavior your can use .AsNoTracking(). For NHibernate it can be session.Evict(yourObj). – Sandre Oct 01 '15 at 12:27
  • but how then it will detect changes when I need to `UPDATE`? doing another `SELECT`? this is what I want to avoid. – Vlad Oct 01 '15 at 12:30
  • `Merge` is an option but comparing everything, each collection and subcollection elements with each "flush" is also not very good thing. – Vlad Oct 01 '15 at 12:39
1

I have used hibernate in a stateful desktop application with extremely long sessions: the session starts when the application launches, and remains open for as long as the application is running. I had no problems with that. I make absolutely no use of attaching, detaching, reattaching, etc. I know it is not standard practice, but that does not mean it is not doable, or that there are any pitfalls. (Edit: but of course read the discussion below for possible pitfalls suggested by others.)

I have even implemented my own change notification mechanism on top of that, (separate thread polling the DB directly, bypassing hibernate,) so it is even possible to have external agents modify the database while hibernate is running, and to have your application take notice of these changes.

If you have lots and lots of stuff already working with hibernate, it would probably not be a good idea to abandon what you already have and rewrite it unless you are sure that hibernate absolutely won't do what you want to accomplish.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • 1
    "DBConnection-per-user is not going to work. The expected online is thousands of users." Also I can't use single session for the whole multithreaded application. And I need to unload users (with all corresponding entities) from memory when their cache time expires. – Vlad Oct 01 '15 at 11:22
  • You do not have to use one db connection per user, and as a matter of fact I don't even think you can, since that would imply keeping one `SessionManager` instantiated per active user. You just need to implement a layer between your application and hibernate, so that all users can access the same instance of `SessionManager`. If that's not an option, then you need to instantiate session managers on a need basis, and do a lot of attaching and detaching, but as far as I know, that is also perfectly doable with hibernate. That's precisely why it supports attaching and detaching. – Mike Nakis Oct 01 '15 at 11:27
  • By SessionManager do you mean ISessionFactory? Do you know that each ISession (at least once it's used to query anything) uses a seperate db connection? – Vlad Oct 01 '15 at 11:31
  • Okay, I am familiar with Hibernate, not NHibernate, where a SessionManager is what you get from SessionManagerFactory, so it appears that in NHibernate they have simplified the terminology, and an ISession is what you get from an ISessionFactory. So, I am talking about an ISession, not an ISessionFactory. – Mike Nakis Oct 01 '15 at 11:34
  • Yes, each session uses a separate db connection, and this is the reason why they recommend sessions to have short lifetimes in NHibernate: so that you only need as many concurrent connections as you have concurrent sessions. So, at a coarse time scale you may have thousands of concurrent players, but at a fine time scale you may have only hundreds of sessions. – Mike Nakis Oct 01 '15 at 11:36
  • So, if you do not want sessions with a short life span, you can still use hibernate with a single session per machine servicing thousands of players, you just have to synchronize (or otherwise manage) your different players' access to that one session. – Mike Nakis Oct 01 '15 at 11:37
  • The same ISession can't be used for the whole application. I need multithreading (I can `lock` with `Monitor` of course but it will harm performance) and "I need to unload users (with all corresponding entities) from memory when their cache time expires". ISession caches all state without any expiration timer. – Vlad Oct 01 '15 at 11:38
  • Locking is not the only way to achieve multithreading. You can also use message passing, which, when used properly, (passing only immutable entities around,) requires no locking whatsoever other than the tiny bit of locking done by the objects that implement the message queues. – Mike Nakis Oct 01 '15 at 11:40
  • For the unloading of users I have no solution. In hibernate they recommend that you power-cycle the session factory every once in a while, which is lame. I don't know if EntityFramework does anything better. – Mike Nakis Oct 01 '15 at 11:42
  • Anyway, with locking or another synchronization I can't have multiple db operations (transactions) in parallel which is really bad. – Vlad Oct 01 '15 at 11:43
  • 1
    You could have N hibernate sessions running on N threads fetching messages from queues and therefore achieving parallelization by N. Where N would be something like 10. – Mike Nakis Oct 01 '15 at 11:44
  • 2
    @MikeNakis: a single session per app is suitable only for relatively small databases or when user sessions are short. When the db is huge and/or the user works for a long time in a single session, the longer the user uses the app, the larger the first level cache is. Sooner or later it consumes all the memory and the app becomes unresponsive. This should be mentioned when you say about potential pitfalls. – Wiktor Zychla Oct 01 '15 at 12:07
  • @WiktorZychla point noted. That's why hibernate needs power-cycling. I do not know why they don't use soft references, but I suppose they must have their reasons. – Mike Nakis Oct 01 '15 at 12:12