I wanted to do some experimenting with MongoDB, but I wasn’t really happy with any of the sample data I could find in the web. So I decided that I would translate the MySQL “Sakila” schema into MongoDB collections as part of the learning process.

For those that don’t know, Sakila is a MySQL sample schema that was published about 8 years ago. It’s based on a DVD rental system. OK, not the most modern data ever, but DVDs are still a thing aren’t they??

You can get the MongoDB version of Sakilia here. To load, use unpack using tar zxvf sakilia.tgz then use mongoimport to load the resulting JSON documents. On windows you should be able to double click on the file to get to the JSON.

The Sakila database schema is shown below. There are 16 tables representing a fairly easy to understand inventory of films, staff, customers and stores.

When modelling MongoDB schemas, we partially ignore our relational modelling experience – “normalization” is not the desired end state. Instead of driving our decision on the nature of the data, we drive it on the nature of operations. The biggest decision is which “entities” get embedded within documents, and which get linked. I’m not the best person to articulate these principles – the O’Reilly book “MongoDB Applied Design Patterns” does a pretty good job and this presentation is also useful.

My first shot at mapping the data – which may prove to be flawed as I play with MongoDB queries – collapsed the 16 tables into just 3 documents: FILMS, STORES and CUSTOMERS. ACTORS became a nested document in FILMS, STAFF and INVENTORY were nested into STORES, while RENTALS and PAYMENTS nested into CUSTOMERS. Whether these nestings turn out to be good design decisions will depend somewhat on the application. Some operations are going to be awkward while others will be expedited.

Here’s a look at the FILMS collection:

Here is STORES:

And here is CUSTOMERS:

Looks like I have to fix some float rounding issues on customers.rentals.payments.amount .

The code that generates the schema is here. It’s pretty slow, mainly because of the very high number of lookups on rentals and payments. It would be better to bulk collect everything and scan through it but it would make the code pretty ugly. If this were Oracle I’m pretty sure I could make it run faster but with MySQL SQL tuning is much harder.

Code is pretty straight forward. To insert a MongoDB document we get the DBCollection, then create BasicDBObjects which we insert into the DBCollection. To nest a documnet we create a BasicDBList and insert BasicDBObjects into it. Then we add the BasicDBList to the parent BasicDBObject. The following snippit illustrates that sequence. It's mostly boilerplate code, with the only human decision being the nesting structure.

I’ve been looking for an excuse to muck about with scala for a while now. So I thought i’d do a post similar to those I’ve done the past for .NET, python, perl and R. Best practices for Java were included in my book Oracle Performance Survival Guide (but I’d be more than happy to post them if anyone asks).

One of the great things about scala is that it runs in the JVM, so we can use the Oracle JDBC drivers to access Oracle. These drivers are very mature and support all the best programming practices.

Best practices for programming Oracle in any language require at least the following:

Use bind variables appropriately.

Ensure you are performing array fetch when retrieving more than one row

Ensure that you use array insert when doing bulk inserts

You can get the scala program which contains the code snippets below here.

Connecting

If you’ve ever used Oracle with JDBC, you’ll find things very familiar. Here’s a snippet that connects to an oracle database with username,password, host and service specified on the command line (assumes the default 1521 port, but of course this could be parameterized as well):

1: import java.sql.Connection

2: import java.sql.ResultSet

3:

4: import oracle.jdbc.pool.OracleDataSource

5:

6: object guyscala2 {

7: def main(args: Array[String]) {

8: if (args.length != 4) {

9: println("Arguments username password hostname serviceName")

10: System.exit(1)

11: }

12:

13: val ods = new OracleDataSource()

14: ods.setUser(args(0))

15: ods.setPassword(args(1))

16: ods.setURL("jdbc:oracle:thin:@" + args(2)+":1521/"+args(3))

17: val con = ods.getConnection()

18: println("Connected")

Using Bind variables

As in most languages, it's all to easy to omit bind variables in scala. Here's an example where the variable value is simply concatenated into a SQL string

1: for (cust_id <- 1 to rows) {

2: val s1 = con.createStatement()

3: s1.execute("UPDATE customers SET cust_valid = 'Y'"

4: + " WHERE cust_id = " + cust_id)

5:

6: s1.close()

7: }

On line 3 we build up a SQL statement concatenating the value we want into the string and immediately exeucte it. Each execution is a unique SQL statement which requires parsing, optimization and caching in the shared pool.

Here’s an example using bind variables and a prepared Statement:

1: val s2 = con.prepareStatement(

2: "UPDATE customers SET cust_valid = 'Y'"

3: + " WHERE cust_id = :custId")

4:

5: for (cust_id <- 1 to rows) {

6: s2.setInt(1, cust_id)

7: s2.execute()

8: }

9:

10: s2.close()

Slightly more complex: we prepare a statement on line 1, associate the bind variable on line 6, then execute on line 7. It might get tedious if there are a lot of bind variables, but still definitely worthwhile. Below we see the difference in execution time when using bind variables compared with concanating the variables into a string. Bind variables definitely increase execution time.

As well as the reduction in execution time for the individual application, using bind variables reduces the chance of latch and/or mutex contention for SQL statements in the shared pool – where Oracle caches SQL statements to avoid re-parsing. If many sessions are concurrently trying to add new SQL statements to the shared pool, then some may have to wait on the library cache mutex. Historically, this sort of contention has been one of the most common causes of poor application scalability – applications which did not use bind variables risked strangling on library cache latch or mutex as the SQL exectuion rate increased.

Exploiting the array interface

Oracle can retrieve rows either from the database one at a time, or can retrieve rows in “batches” sometimes called “arrays”. Array fetch refers to the mechanism by which Oracle can retrieve multiple rows in a single fetch operation. Fetching rows in batches reduces the number of calls issued to the database server, and can also reduce network traffic and logical IO overhead. Fetching rows one at a time is like moving thousands of people from one side of a river to another in a boat with all but one of the seats empty - it’s incredibly inefficient.

Fetching rows using the array interface is simple as can be and in fact enabled by default - though with a small default batch size of 10. The setFetchSize method of the connection and statement objects sets the number of rows to be batched. Unfortunately, the default setting of 10 is often far too small – especially since there is typically no degradation even when the fetch size is set very large – you get diminishing, but never negative, returns as you increase the fetch size beyond the point at which every SQL*NET packet is full

Here’s a bit of code that sets the fetch size to 1000 before executing the SQL:

While the default setting of 10 is clearly better than any lower value, it’s still more than 6 times worse than a setting of 100 or 200.

Inserting data is another situation in which we normally want to consider the array interface. In this case we need to change our code structure a bit more noticeably. Here’s the code we probably would write if we didn’t know about array processing:

1: val insSQL = "INSERT into arrayinsertTest" +

2: " (cust_id,cust_first_name,cust_last_name,cust_street_address) " +

3: " VALUES(:1,:2,:3,:4)"

4: val insStmt = con.prepareStatement(insSQL)

5: val startMs = System.currentTimeMillis

6: var rowCount = 0

7: while (rs.next()) {

8: insStmt.setInt(1, rs.getInt(1))

9: insStmt.setString(2, rs.getString(2))

10: insStmt.setString(3, rs.getString(3))

11: insStmt.setString(4, rs.getString(4))

12: rowCount += insStmt.executeUpdate()

13: }

14: val elapsedMs = System.currentTimeMillis - startMs

15: println(rowCount + " rows inserted - " + elapsedMs + " ms")

16: con.commit()

We prepare the statement on line 4, bind the values to be inserted on lines 8-11, then execute the insert on line 12.

WIth a few minor changes, this code can perform array inserts:

1: val insSQL = "INSERT into arrayinsertTest" +

2: " (cust_id,cust_first_name,cust_last_name,cust_street_address) " +

3: " VALUES(:1,:2,:3,:4)"

4: val insStmt = con.prepareStatement(insSQL)

5: val startMs = System.currentTimeMillis

6: var rowCount = 0

7: while (rs.next()) {

8: insStmt.setInt(1, rs.getInt(1))

9: insStmt.setString(2, rs.getString(2))

10: insStmt.setString(3, rs.getString(3))

11: insStmt.setString(4, rs.getString(4))

12: insStmt.addBatch()

13: rowCount += 1

14: if (rowCount % batchSize == 0) {

15: insStmt.executeBatch()

16: }

17: }

18:

19: val elapsedMs = System.currentTimeMillis - startMs

20: println(rowCount + " rows inserted - " + elapsedMs + " ms")

21: con.commit()

On line 12, we now call the addBatch() method instead of executeUpdate(). Once we’ve added enough rows to our batch (defined by the batchsize constant in the above code) we can call executeBatch() to insert the batch.

Array insert gives about the same performance improvements as array fetch. For the above example I got the performance improvement below:

To be fair, the examples above are a best case scenario for array processing – the Oracle database was running in an Amazon EC2 instance in the US, while I was running the scala code from my home in Australia! So the round trip time was as bad as you are ever likely to see. Nevertheless, you see pretty impressive performance enhancements from simply increasing array size in the real world all the time.

Conclusion

If you use JDBC to get data from Oracle RDBMS within a scala project, then the principles for optimization are the same as for Java JDBC – use preparedStatements, bind variables and array processing. Of course there’s a lot more involved in optimizing database queries (SQL Tuning, indexing, etc), but these are the three techniques that vary significantly from language to language. The performance delta from these simple techniques are very significant and should represent the default pattern for a professional database programmer.

I seem to be getting a lot of surprising performance results lately on our X-2 quarter rack Exadata system, which is good – the result you don’t expect is the one that teaches you something new.

This time, I was looking at using a temporary tablespace based on flash disks rather than spinning disks. In the past – using Fusion IO PCI cards, I found that using flash for temp tablespace was very effective in reducing the overhead of multi-pass sorts:

However, when I repeated these tests for Exadata, I got very disappointing results. SSD based temp tablespace actually lead to marginally worse performance:

Looking in depth at a particular point (the 500K SORT_AREA_SIZE point), we can see that although the SSD based temp tablespace has marginally better read times, it involves a significantly higher write overhead:

I can understand the higher read overhead (at least partially). It’s Yet Another time when sequential write operations to an SSD device have provided disappointing performance. However, it’s strange to see such poor read performance. How can a spinning disk serve blocks up at effectively the same latency an SSD?

So I dumped all the direct path read waits from a 10046 trace and plotted them logarithmically:

We can see in this chart, that the SDD based tablespace suffers from a small “spike” of high latencies between 600-1000 us (eg .6-1 ms). These are extremely high latencies for an SSD ! What could be causing them? Garbage collection being caused by the almost writes to the temp tablespaces? There was negliglbe concurrent activity on the system and the table concerned had flash cache disabled so for now that is my #1 theory.

For that matter, why are the HDD reads times so low? An average disk read latency of 500 us for a spinning disk is unreasonably low, is the storage cell somehow buffering temporary tablespace IO?

As always I’m wondering if there’s someone with more expertise in Exadata internals who could shed some light on all of this!

I’ve been doing some work on the Exadata Smart Flash Cache recently and came across a situation in which setting CELL_FLASH_CACHE to KEEP will significantly slow down smart scans on a table.

If we create a table with default settings, then the Exadata Smart Flash Cache (ESFC) will not be involved in smart scans, since by default only small IOs get cached. If we want the ESFC to be involved, we need to set the CELL_FLASH_CACHE to KEEP. Of course, we don’t expect immediate improvements, since we expect that the next smart scan will need to populate the cache before subsequent scans can benefit.

HOWEVER, what I’m seeing in practice is that the next smart scan following an ALTER TABLE … STORAGE(CELL_FLASH_CACHE KEEP) is significantly degraded, while subsequent scans get a performance boost. Here’s an example of what I observe:

The big increase in CELL IO time is in an increase in both the number and latency of cell smart table scans. The wait stats for the first scan with a default setting looked like this:

I’m at a loss to understand why there would be such a high penalty for the initial smart scan with CELL_FLASH_CACHE KEEP setting. You expect some overhead from constructing and storing the result set blocks in the cache, but an IO penalty of 200=300% seems way too high. Anybody seen anything like this or have a clear explanation?

@kevinclosson was kind enough to genlty correct my embarassing misconception about how the Exadata Smart Flash Cache (ESFC) deals with smart scans. Somehow I had got the impression that the ESFC was storing parts of the smart scan results - kind of like the result set cache. I really can't remember where I got that impression but as Kevin pointed out, its completely incorrect. What is being cached are the underlying table blocks - and you can clearly see this using LIST FLASHCACHECONTENT and looking at the flash hit rates for other queries.

So the overhead then is easier to understand since we are looking at placing all the blocks for a reasonably large table into the flash cache. This involves a large number of write operations over a very short period of time which in turn probably requires some on the fly garbage collection and signifcant write amplification.

The conclusion to draw is probably NOT to set CELL_FLASH_CACHE KEEP for a table of significant size unless you know for sure that it will be re-scanned within a short period of time. If the blocks age out of the cache and have to be reloaded you'll probably see a degradation rather than an improvement in scan performance.

I get a lot of push back on these findings – often on theoretical grounds from Flash vendors (“our SSD use advanced caching and garbage collection that support high rates of sequential IO”) or from people who say that they’ve used flash for redo and it “worked fine”.

Unfortunately, every single test I do comparing performance of redo on flash and HDD shows redo with little or no advantage and in some cases with a clear disadvantage.

One argument for flash SSD that I’ve heard is that while for the small transactions I use for testing flash might not have the advantage but for “big” redo writes – such as those associated with LOB updates – flash SSD would work better. The idea is that the overhead of garbage collection and free page pool processing is less with big writes since you don’t hit the same flash SSD pages in rapid succession as you would with smaller writes. On the other hand a reader who knows more about flash than I do (flashdba.com) recently commented: “in foreground garbage collection a larger write will require more pages to be erased, so actually will suffer from even more performance issues.”

It’s taken me a while to get around to testing this, but I tried on our Exadata X-2 recently with a test that generates a variable amount of redo and then commits. The relationship between the size of the redo and redo log sync time is shown below

I’m now putting on my flame retardant underwear in anticipation of some dispute over this data…. but, this suggests that while SSD and HDD (at least on Exadata) are about at parity for small writes, flash degrades much more steeply than HDD as the size of the redo entry increases. Regardless of whether the redo is on flash or HDD, there’s a break at the 1MB point which corresponds to log buffer flush threshold. When a redo entry is only slightly bigger than 1MB then the chances are high that some of it will have been flushed already – see Redo log sync time vs redo size for a discussion of this phenomenon.

The SSD redo files were on an ASM disk group carved out of the Exadata flash disks - see Configuring Exadata flash as grid disk to see how I created these. Also the redo logs were created with 4K blocksize as outlined in Using SSD for redo on Exadata - pt 2. The database was in NoarchiveLog mode. Smart flash logging was disabled. As far as I can determine, there was no other significant activity on the flash disks (the grid disks were supporting all the database tablespaces, so if anything the SSD had the advantage).

Why are we seeing such a sharp dropoff in performance for the SSD as the redo write increases in size? Well one explanation was given by flashdba in this comment thread. It has to do with understanding what happens when a write IO which modifies an existing block hits a flash SSD. I tried to communicate my limited understanding of this process in Fundamentals of Flash SSD Technology. Instead of erasing the existing page, the flash controller will pull a page off a “free list” of pages and mark the old page as invalid. Later on, the garbage collection routines will reorganize the data and free up invalid pages. In this case, it’s possible that no free blocks were available because garbage collection fell behind during the write intensive workload. The more blocks written by LGWR, the more SSD pages had to be erased during these un-optimized writes and therefore the larger the redo log write the worse the performance of the SSD.

Any other theories and/or observations?

I hope soon to have a Dell system with Dell express flash so as I can repeat these tests on a non-exadata system. The F20 cards used in my X-2 are not state of the art, so it’s possible that different results could be obtained with a more recent flash card, or with a less contrived workload.

However, yet again I’m gathering data that suggests that using flash for redo logs is not worthwhile. I’d love to argue the point but even better than argument would be some hard data in either direction….