The Data Studio

"Object-Oriented", "NoSQL" and "Document" Databases

"Object-Oriented" - What Does It Mean?

Object-oriented means a variety of things in different contexts and to different people and organisations. It always seemed to me that the best application of the object-oriented concepts was in writing windowing user interfaces, such as the desktop interfaces for Apple's MacOS, Microsoft's Windows and the Gnome desktop for Linux systems. In these systems, multiple windows are usually open at the same time. They operate independently of one another most of the time, but they can send messages to one another. Each window is an object, and they operate in a consistent way, inheriting their appearance and behaviour from a higher-level generic window type.

But, in the development of almost all business applications, including applications for commercial, public and charitable organisations, I have never found the object-oriented concepts to be useful. One of these concepts - inheritance - is available in my favourite database (PostgreSQL) and my favourite programming language (Java). I do inherit from ("extend") the LinkedList class in Java, but I never found any other good use for inheritance in anything I developed. When I saw it used in other people's developments it always seemed to make things more complicated. I am aware, of course, that in Java, inheritance is built into most of the thousands of useful classes that exist in the standard libraries, but I am only ever using objects from one level in these libraries.

There are many database systems that claim to support object-oriented features. I consider this to be a big mistake and I will try, here, to explain why. MongoDB is very popular and is described as an object-oriented database and, alternatively, as a document database, and as a NoSQL database. The MongoDB documentation is extensive, and nicely-written, but the approach of MongoDB is very problematic, especially in terms of efficiency and data quality. These things affect productivity too, again, in a negative way. I have also worked on other NoSQL databases, in most depth on Hadoop implementations and Cassandra. These had the same problems as MongoDB, but unlike MongoDB, they did not even have good documentation.

So what is the problem?

MongoDB emphasises the storage of objects, which they refer to as documents, being data structures as they are used by applications. Each document reflects the way that a few things are linked together in a particular application. They suggest that this "reduces the need for expensive joins". I have never understood why they get so upset about joins, which are neither complicated nor expensive. What joins do, is to enable us to link together the fundamental objects in a database, in many different ways to support the different uses that different combinations of objects have.

In practice, parts of a MongoDB document are usually also used in other types of documents, so the individual fields have to be repeated in all the document types in which they occur. This leads to duplication, which wastes space, and more importantly can cause inconsistencies when the field name is spelled, capitalised or punctuated differently.

Documents contain metadata (name and optionally data-type) as well as data for every field in every record.

Documents in one collection can contain different sets of fields.

Documents can contain arrays, and other documents, nested within them.

When you create a new application, or modify an existing one, with MongoDB, you have to look at many different documents containing overlapping, potentially inconsistent, versions, sub-sets and supersets of the data you want. Even the documents in one collection can be different from one another, so, to produce a robust application, you have to consider all the variations that exist now and may exist during the life of your application. The consistency of relational tables makes building applications far more productive.

MongoDB claims high performance But see the benchmark which compares MongoDB with PostgreSQL. In almost all cases, PostgreSQL is much better than MongoDB. One area showed MongoDB as being more efficient in data storage than PostgreSQL. But this was using the JSON data-type in PostgreSQL. If the data had been structured appropriately for PostgreSQL, being in tables rather than JSON documents, PostgreSQL would have been far more efficient. If you try to play a game to someone else's rules, you are less likely to win.

In a relational database table the metadata is stored once for the table, and all records in that table have the same structure. In MongoDB and other JSON-style databases the metadata is repeated in every record and for every field. Look at the example below, where the metadata takes up about as much space as the data.

As well as wasting space, this approach increases code and execution time since every record (or "document") has to be parsed to see what fields it contains. We use computer systems to process large amounts of repetitive data, millions of records with the same format, because that is what computers are good at. If we allow multiple formats in the same "document" we increase the amount of work that has to be done, significantly. And if we don't know what is in a particular document until we read it, we are less likely to have anticipated all the possibilities, and that will lead our program to fail, on particular data, or to process it wrongly. Infinite flexibility is not our goal; instead we need to provide robust and reliable processing of defined scenarios.

MongoDB claims ease-of-use. Mainly, this is because a document looks like the data used for a particular application process. But most databases serve the needs of several different groups of users, so several different views are required, and this means that parts of the data have to be included in several different documents, or one document has to include everything. In the real data model for a medium-sized UK charity, the individual donor had sixteen related objects, most of which can have multiple occurrences for one donor. The JSON object to describe all this would be a monster. Models for commercial or government organisation are usually more complex.

An Example

Here is a diagram of a small part of the database supporting a charity. It is much simplified for the purposes of this example.

Relational schema to be compared with object equivalent

And here is some data for this data-model, stored in JSON.

{
  "first_name": "William", 
  "last_name": "Oakeshot",
  "date_of_birth": "23-Jun-1952",
  "date_of_death": "07-Jun-2018",
  "address":
  [
    {
      "street address": "18,Summerfield Drive",
      "town": "Tenbury",
      "postcode": "HX58 6RL",
      "email": "willo@instruimus.com",
      "phone_number": "07855 553551"
    }
  ],
  "correspondence":
  [
    {
      "sent_date": "15-Mar-2011",
      "received_date": "",
      "medium": "email",
      "direction": "sent",
      "subject": "sponsored run"
    },
    {
      "sent_date": "15-Mar-2011",
      "received_date": "",
      "medium": "email",
      "direction": "sent",
      "subject": "legacy"
    },
    {
      "sent_date": "",
      "received_date": "06-Apr-2012",
      "medium": "phone",
      "direction": "received",
      "subject": "legacy"
    }    
  ],
  "legacy":
  [
    {
      "will_date": "21-May-2014",
      "stage": "signed", 
      "amount": "£2,000"
    }
  ],
  "campaign_action":
  [
    {
      "campaign_title":"Forever European",
      "start_date": "13-Jun-2016",
      "end_date": "13-Jun-2016"
  ]
}

This document could serve the needs of those users in the charity who are dealing with legacies, and those who are dealing with routine correspondence, and those who are organising campaigns that require action from their supporters.

Each of these groups will get documents that include data that is of no interest to them, because it is handled by the other groups. Dragging data that is not needed out of the database is inefficient; it includes data transfers that could be avoided.

A more serious point is that legacies involve data that will usually be sensitive. Here we are talking about money that people wish to be given to charities after their death (not "legacy systems" which are just old computer systems). Legacies involve people's saved money, and are usually discussed when people are aware of their closeness to death. So data about legacies should be handled by a small team of users who have been trained to deal with such sensitive issues in a sympathetic manner, and who can be trusted with confidential data.

So we would need separate documents for legacies, and we could do it like this:

Unrestricted document:

{
  "first_name": "William", 
  "last_name": "Oakeshot",
  "date_of_birth": "23-Jun-1952",
  "date_of_death": "07-Jun-2018",
  "address":
  [
    {
      "street address": "18,Summerfield Drive",
      "town": "Tenbury",
      "postcode": "HX58 6RL",
      "email": "Bricen@instruimus.com",
      "phone_number": "07855 553551"
    }
  ],
  "non_legacy_correspondence":
  [
    {
      "sent_date": "15-Mar-2011",
      "received_date": "",
      "medium": "email",
      "direction": "sent",
      "subject": "sponsored run"
    }
  ],
  "campaign_action":
  [
    {
      "campaign_title":"Forever European",
      "start_date": "13-Jun-2016",
      "end_date": "13-Jun-2016"
  ]
}

Document restricted to legacy department only:

{
  "first_name": "William", 
  "last_name": "Oakeshot",
  "date_of_birth": "23-Jun-1952",
  "date_of_death": "07-Jun-2018",
  "address":
  [
    {
      "street address": 18,Summerfield Drive
      "town": 18,Summerfield Drive
      "postcode": HX58 6RL
      "email"; willo@instruimus.com
      "phone_number": 07855 553551
    }
  ],
  "legacy_correspondence":
  [
    {
      "sent_date": "15-Mar-2011",
      "received_date": "",
      "medium": "email",
      "direction": "sent",
      "subject": "legacy"
    },
    {
      "sent_date": "",
      "received_date": "06-Apr-2012",
      "medium": "phone",
      "direction": "received",
      "subject": "legacy"
    }    
  ],
  "legacy":
  [
    {
      "will_date": "21-May-2014",
      "stage": "signed", 
      "amount": "£2,000"
    }
  ]
}

Most database sytems, relational, object, or others, provide some access control, so the legacy document above could be restricted to only the users in the legacy department. In MongoDB these documents could be in a separate collection and the whole collection could be restricted.

This could work, but it causes us to have the name and address duplicated. Duplication wastes space, but what is far more important is that duplicated data leads to other problems. If the same infomation exists in two or more places, then when it changes, it is very frequent that not all copies get changed, and then we cannot tell which is the correct one, or we can fail to realise that we are not looking at the latest version, so we send a letter to the wrong address, or we try to contact a dead person, which can be very distressing for their survivors.

So Is It Easier?

I suppose that when you write your very first application which accesses a database, then the data representations using JSON or similar notations may look easier. "Easier" is a word that is used frequently in describing non-relational, or NoSQL databases. If it applies at all, then it soon becomes untrue as we get into real-world applications. In terms of formatting, people complain about the commas in lists of things in SQL, and it is a common error to miss out a comma where there should be one or to add an extra comma by mistake at the end of a list. But really this is trivial compared to the forest of double-quotes, commas, colons, square brackets and curly brackets in JSON.

And formatting is not the main issue. What is far more important is consistency and reliability of data. With relational databases, all records in a table have the same columns, and columns can (and should) have data-types which allow only valid data to be stored in those columns. The data-type applies to that column in every record in the table. So, when you look at a transaction value, it is a number, usually positive for credits and negative for debits, so you can just add up all the transactions to get the current balance. In MongoDB you could, in a collection of transactions, have a document that had no transaction value at all, or the transaction value could be non-numeric. So when you try to sum up all the transaction values, you get an error or, worse still, a silent failure.

Scalability

One more thing... "Horizontal scaling" seems to be a big thing for the NoSQL and object databases, although it is not a necessary feature for them, and it is not exclusive to them either - it can be applied to other database systems. "Horizontal scaling" means distributing data across multiple machines. This idea has been around for at least 45 years.

A distributed database is one that is spread across multiple machines. Data on disks - including solid-state drives (SSDs) is stored in blocks. Block sizes vary between systems, but typically a block would hold between 10 and 100 records. In a distributed database, the blocks are distributed across multiple disks on multiple servers. Each server with its disks is called a "node". Usually each block is stored on more than one node, so that, if a node fails, the whole file can be recreated from the blocks on other nodes. This makes the file system resilient. It is typical to have three copies of each block on different nodes.

This is a sledge-hammer to crack a nut. Non-distributed relational databases are also highly resilient, and, in practice, seem to need recovering less often than NoSQL databases.

Distributed databases have become fashionable, but they are needed only in extreme cases which are outside the requirements of very nearly all organisations.

Distributed databases have significant drawbacks.

When all the disk storage is on one machine, it is possbile to apply multiple changes to the data in the database on the disks, and to ensure that all these changes are safely stored on the disks, or none of them are. This is the concept of a transaction. A simple transaction could be one where I transfer £10 from my bank account to yours. This will update my account (reducing it by £10) and your account (increasing it by £10). If both of those changes happen, then we are both happy, but if either one fails we require the other one to fail too. If one succeeds and one fails then one of us has lost £10.

Transactions can be safely recorded like this when one machine is controlling all the disk storage.

But when transactions are applied with parts of the transaction on different nodes, things get a lot more complex. Transactions have to be committed in a two-phase protocol to have some hope that the change has been applied successfully. Two-phase commit is much more complex than the simple commit performed when everything is on one node, and things can go wrong even when this complex protocol is used.

A related issue is the allocation of identifiers (or "keys"). On a single node it is easy to have a central key generator which guarantees that no two records get the same key. This is fast and efficient as well as being reliable and the keys generated are usually 4-byte integers (from 1 to about 2 billion). 8-byte integers can also use the same mechanism and that will give enough distinct keys for any dataset that you will ever have.

A central source for the next identifier is not feasible in a distributed system, so these systems use large random numbers instead. The numbers used are usually UUIDs. (Microsoft, being hostile to standards, calls these "GUID"s.)

A UUID looks like this: 2c885f6c-8e5b-456f-8a9f-44dc52c7fd96.

Microsoft's own documentation says:

"Because GUIDs values are long and obscure, they are not meaningful for users. If randomly generated GUIDs are used for key values and you insert a lot of rows, you get random I/O into your indexes, which can negatively impact performance. GUIDs are also relatively large when compared to other data types. In general we recommend using GUIDs only for very narrow scenarios for which no other data type is suitable."   [my emphasis].

And yet, Microsoft forces its NoSQL Dataverse solution on customers with databases as small as 30GB. GUIDs are a prominent feature of the Dataverse, and act as a drag-anchor on the work of everyone involved in these systems.

Conclusion

There really is no good reason to use NoSQL or object databases. Real relational databases such as PostgreSQL can handle databases, up to many terabytes in size, efficiently and reliably with powerful tools for developers. The best of these databases are free.