Skip to content

Instantly share code, notes, and snippets.

@colbymorrison
Last active September 20, 2022 15:20
Show Gist options
  • Save colbymorrison/648332d13c9277b2a0e2d9431c184e70 to your computer and use it in GitHub Desktop.
Save colbymorrison/648332d13c9277b2a0e2d9431c184e70 to your computer and use it in GitHub Desktop.
DAO Flushing Investigation
  • Tl;dr We probably want to keep some level of manual flushing, but there are a lot of flushes we can remove!

Upstream Code Outside of sarsoft/upstream/src/org/sarsoft/online/jobs

  • Currently, we create Hibernate Sessions with FlushMode.MANUAL (code pointer and code pointer). This mode delegates all flushing operations to us.

    • Initially I thought about changing our Sessions to have FlushMode.COMMIT or FlushMode.AUTO, both of which automatically flush before each Transaction commit. However, this looks to generate flushes that contain no actual changes, which we want to avoid.
  • The methods of GenericHibernateDao execute their queries with GenericHibernateDao::execute. exectue wraps each query in a Transaction.

    • GenericHibernateDao::flush itself calls execute. So each call to dao.flush() that we remove will save us a Transaction.
  • We only want to Flush on queries that update the db. All such queries will go through GenericHibernateDao::save or GenericHibernateDao::delete.

    • These methods have a flush parameter that controlls if we flush or not.
    • Of the Public methods of GenericHibernateDao which call save and delete, only saveMapObject and deleteMapObject call with flush=False.
  • Therefore, we can immediatley make the following changes:

    • Remove dao.flush() calls where dao is always a SQLiteDao: Example. SQLiteDao::flush is a noop.
    • Remove dao.flush() calls after calls to methods of GenericHibernateDao that are not saveMapObject or deleteMapObject. These methods will call GenericHibernateDao::[save/delete] with flush=True, so there is no need for a manual flush afterward. Example.
    • Confirmed with Hibernate logging. In these cases we're doing a Trasaction containing a Flush that has no updates.
    • Ex: 2022-09-19 12:53:21 AbstractFlushingEventListener [DEBUG] Flushed: 0 insertions, 0 updates, 0 deletions to 12 objects 2022-09-19 12:53:21 AbstractFlushingEventListener [DEBUG] Flushed: 0 (re)creations, 0 updates, 0 removals to 87 collections
  • What to do with GenericHibernateDao::[save/delete]MapObject?

    • Consider the case were [save/delete]MapObject is called once, followed by dao.flush(). Example.
      • We can save a Transaction here by making a version of saveMapObject that calls save with flush=True.
    • The cases that are more interesting are where we perform several [save/delete]MapObject calls, followed by a flush afterward, Example.
      • This allows us "queue up" many updates in the Hibernate persistence context, then flush them at once.
      • I think this is good behavior to keep as it reduces the number of flushes.
      • There are some places we can cut down on flushes here as well.
      • Another way to deal with this would be providing a mechanism to put all of these calls within one transaction. However, that change would be more work and I'm not sure how much gain would come of it.
  • These Job classes all behave similarly. They create a session with FlushMode.COMMIT, start a transaction, call some dao methods, and commit a transaction.
  • However, each dao method will run its own transaction within execute and performs a flush for the update methods that are called
  • So, we can probably change the FlushMode to MANUAL here.
  • Furthermore, since each individual query runs its own transaction, do we need to wrap everything in an outer transaction?

Suggested Plan

  • Keep the flush parameter and behavior of the GenericHibernateDao::[save/delete] methods to keep the "queuing" behaviour of GenericHibernateDao::[save/delete]MapObject
  • Go ahead with creating a PR removing unnecessary flushes for both the non-job and job code.

Notes

  • To address the question on the card. I don't think the related objects case of Map and Account is relevant. We don't save maps to the UserAccount object and instead get all maps in the CollaborativeMapObject table tagged with the current UserAccount.
  • We setup the Hibernate Sessions within Spring using Spring's OpenSessionInViewFilter. Docs
    • Quoting the docs, Spring assumes that OpenSessionInViewFilter will be used in combination with service layer transactions that care for the flushing: The active transaction manager will temporarily change the flush mode to FlushMode.AUTO during a read-write transaction, with the flush mode reset to FlushMode.MANUAL at the end of each transaction.
    • If using their system, we'd annotate methods that update the db (i.e. GenericHibernateDao::[save/delete]) with @Transactional(read-only=False) and otther GenericHibernateDao methods with @Transactional(read-only=True). That would achieve the same behaviour we want to achieve of only flushing on updates.
    • I don't think we need to do this at all, but I came across it in my investigation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment